我正试图将一个运算符传递给一个模块,以便可以通用地构建该模块。我传递一个两输入运算符参数,然后在归约运算中使用它。如果我用一个具体的运算符替换传递的参数,这可以正常工作
传递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 _)
...
以下是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)
}
}
}
尽管只传入字符串形式的运算符,然后使用简单的Scalaif
来控制适当的代码生成可能会更清楚。类似的东西
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
// ...
})
}
Chick已经提供了一个很好的答案,但我想提供另一个例子来说明和解释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]
这被称为具有类型上界(<: Data
(的类型参数(T
(。这使我们能够使Module
通用于我们参数化它的硬件类型。我们给T
一个Data
(来自Chisel的类型(的上界,告诉Scala这是一个我们可以用来用Chisel生成硬件的硬件类型。上限意味着它必须是Data的子类型,其中包括所有Chisel硬件类型(例如UInt
、SInt
、Vec
、Bundle
和扩展Bundle
的用户类(。这与Reg(...)
等Chisel构造函数的参数化方式完全相同。
您会注意到有多个参数列表,(n: Int, gen: T)
和(op: (T, T) => T)
。第一个参数n: Int
是一个简单的整数参数。第二个参数gen: T
是我们的泛型类型T
,因此是Data
的一个子类型,用作我们将在模块内生成的硬件的模板。
第二个参数列表(op: (T, T) => T)
是函数。作为一种函数式编程语言,函数是Scala中的值,因此可以用作参数,就像我们的Int
参数一样。(T, T) => T
作为两个参数的函数读取,这两个参数都属于T
类型,返回一个T
。请记住,T
是我们的硬件类型,它是Data
的子类。因为op
在第二个参数列表中,这告诉Scala它应该从gen
推断出T
,然后对op
使用相同的T
。例如,如果gen
是UInt(8.W)
,则Scala将T
推断为UInt
。这随后将CCD_ 36约束为CCD_。逐位AND就是这样一个函数,所以我们可以将一个匿名函数传递给AND两个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
所做的。
您可以在Scastie上看到我上面介绍的代码的完整、可运行版本。
我希望有人觉得这个例子有用!