如何将运算符作为参数传递



我正试图将一个运算符传递给一个模块,以便可以通用地构建该模块。我传递一个两输入运算符参数,然后在归约运算中使用它。如果我用一个具体的运算符替换传递的参数,这可以正常工作

传递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硬件类型(例如UIntSIntVecBundle和扩展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。例如,如果genUInt(8.W),则Scala将T推断为UInt。这随后将CCD_ 36约束为CCD_。逐位AND就是这样一个函数,所以我们可以将一个匿名函数传递给AND两个UInts:(_ & _)

既然我们有了抽象的、类型参数化的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上看到我上面介绍的代码的完整、可运行版本。

我希望有人觉得这个例子有用!

最新更新