Как передать оператор в качестве параметра - PullRequest
1 голос
/ 22 февраля 2020

Я пытаюсь передать оператор в модуль, чтобы модуль мог быть собран в общем. Я передаю параметр оператора с двумя входами и затем использую его в операции сокращения. Если я заменю переданный параметр конкретным оператором, это будет нормально.

Как правильно передать оператор Chisel / UInt / Data в качестве параметра модуля?

  val io = IO(new Bundle {
    val a = Vec(n, Flipped(Decoupled(UInt(width.W))))
    val z = Decoupled(UInt(width.W))
  })
  val a_int = for (n <- 0 until n) yield DCInput(io.a(n))
  val z_int = Wire(Decoupled(UInt(width.W)))

  val all_valid = a_int.map(_.valid).reduce(_ & _)
  z_int.bits := a_int.map(_.bits).reduce(_ op _)
...

Ответы [ 2 ]

2 голосов
/ 25 февраля 2020

Вот причудливый Scala способ сделать это

import chisel3._
import chisel3.tester._
import chiseltest.ChiselScalatestTester
import org.scalatest.{FreeSpec, Matchers}

class ChiselFuncParam(mathFunc: UInt => UInt => UInt) extends Module {
  val io = IO(new Bundle {
    val a = Input(UInt(8.W))
    val b = Input(UInt(8.W))
    val out = Output(UInt(8.W))
  })

  io.out := mathFunc(io.a)(io.b)
}

class CFPTest extends FreeSpec with ChiselScalatestTester with Matchers {
  def add(a: UInt)(b: UInt): UInt = a + b
  def sub(a: UInt)(b: UInt): UInt = a - b

  "add works" in {
    test(new ChiselFuncParam(add)) { c =>
      c.io.a.poke(9.U)
      c.io.b.poke(5.U)
      c.io.out.expect(14.U)
    }
  }
  "sub works" in {
    test(new ChiselFuncParam(sub)) { c =>
      c.io.a.poke(9.U)
      c.io.b.poke(2.U)
      c.io.out.expect(7.U)
    }
  }
}

Хотя может быть понятнее просто передать строковую форму оператора и затем использовать простые Scala if s для управления соответствующая генерация кода. Что-то вроде

class MathOp(code: String) extends Module {
  val io = IO(new Bundle {
    val a = Input(UInt(8.W))
    val b = Input(UInt(8.W))
    val out = Output(UInt(8.W))
  })

  io.out := (code match {
    case "+" => io.a + io.b
    case "-" => io.a - io.b
    // ...
  })
}
0 голосов
/ 26 февраля 2020

Чик уже дал хороший ответ, но я хочу привести еще один пример, чтобы проиллюстрировать и объяснить некоторые действительно мощные функции Chisel и Scala для проектирования аппаратного обеспечения. Я знаю, что вы (Гай), вероятно, знаете большую часть этого, но я хотел бы предоставить подробный ответ всем, кто сталкивался с этим вопросом.

Я начну с полного примера и затем выделю некоторые используемые функции ,

class MyModule[T <: Data](n: Int, gen: T)(op: (T, T) => T) extends Module {
  require(n > 0, "reduce only works on non-empty Vecs")
  val io = IO(new Bundle {
    val in  = Input(Vec(n, gen))
    val out = Output(gen)
  })
  io.out := io.in.reduce(op)
}

[T <: Data] Это называется Тип параметра (T) с Верхний предел типа (<: Data). Это позволяет нам сделать Module generi c для типа оборудования, с которым мы его параметризовали. Мы даем T верхнюю границу Data (это тип от Chisel ), чтобы сообщить Scala, что это тип оборудования, который мы можем использовать для генерации оборудования с помощью Chisel. Верхняя граница означает, что это должен быть подтип данных, который включает в себя все типы аппаратных долот (например, UInt, SInt, Vec, Bundle и пользовательские классы, которые расширяются Bundle). Это точно так же, как конструкторы долота, такие как Reg(...), параметризованы .

Вы заметите, что существует несколько списков параметров , (n: Int, gen: T) и (op: (T, T) => T). Первый аргумент n: Int - это простой целочисленный параметр. Второй аргумент, gen: T, является нашим обобщенным c типом T и, таким образом, подтипом Data, который служит шаблоном для оборудования, которое мы будем генерировать внутри модуля.

Второй список параметров (op: (T, T) => T) является функцией . В качестве функционального языка программирования функции являются значениями в Scala и, таким образом, могут использоваться в качестве аргументов так же, как наш Int аргумент. (T, T) => T читает как функцию двух аргументов, оба типа T, которые возвращают T. Помните, что T - это наш тип оборудования, который является подклассом Data. Поскольку op находится во втором списке параметров, это говорит Scala, что он должен вывести T из gen, а затем использовать тот же T для op. Например, если gen равно UInt(8.W), Scala выводит T как UInt. Это тогда заставляет op быть функцией типа (UInt, UInt) => UInt. Побитовое И является такой функцией, поэтому мы можем передать анонимную функцию в И два UInt s: (_ & _).

Теперь, когда у нас есть наш абстрактный тип с параметризацией MyModule класс, как мы на самом деле используем это? Выше я привел фрагменты того, как использовать его с UInt с, но давайте посмотрим, как получить некоторый фактический Verilog:

object MyMain extends App {
  println(chisel3.Driver.emitVerilog(new MyModule(4, UInt(8.W))(_ & _)))
}

В качестве альтернативы, мы можем параметризовать MyModule с более сложным типом:

class MyBundle extends Bundle {
  val bar = Bool()
  val baz = Bool()
}

object MyMain extends App {
  def combineMyBundle(a: MyBundle, b: MyBundle): MyBundle = {
    val w = Wire(new MyBundle)
    w.bar := a.bar && b.bar
    w.baz := a.baz && b.baz
    w
  }
  println(chisel3.Driver.emitVerilog(new MyModule(4, new MyBundle)(combineMyBundle)))
}

Нам также пришлось определить функцию типа (MyBundle, MyBundle) => MyBundle, которую мы сделали с combineMyBundle.

Вы можете увидеть полную, работоспособную версию кода, который я представил выше на Scast ie.

Надеюсь, кто-то найдет этот пример полезным!

...