如何将运算符作为参数传递
How to pass a operator as a parameter
我正在尝试将运算符传递给模块,以便可以通用地构建模块。我传递了一个双输入运算符参数,然后在缩减操作中使用它。如果我用具体的运算符替换传递的参数,这就可以了。
将 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)
}
}
}
虽然只传入运算符的字符串形式,然后使用简单的 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
// ...
})
}
Chick 已经提供了一个很好的答案,但我想提供另一个例子来说明和解释 Chisel 和 Scala 在硬件设计方面的一些真正强大的功能。我知道你 (Guy) 可能知道大部分内容,但我想为遇到这个问题的其他人提供详细的答案。
我将从完整示例开始,然后突出显示正在使用的一些功能。
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]
这叫做Type Parameter (T
) with an Upper Type Bound (<: Data
). This allows us to make the Module
generic to the hardware type with which we parameterize it. We give T
an upper bound of Data
(which is a type from Chisel) to tell Scala that this is a hardware type we can use to generate hardware with Chisel. The upper-bound means it must be a subtype of Data, which includes all of the Chisel hardware types (eg. UInt
, SInt
, Vec
, Bundle
and user classes that extend Bundle
). This is the exact same way that the Chisel constructors like Reg(...)
are parameterized。
您会注意到有 multiple parameter lists、(n: Int, gen: T)
和 (op: (T, T) => T)
。第一个参数 n: Int
是一个简单的整数参数。第二个参数 gen: T
是我们的通用类型 T
,因此是 Data
的子类型,用作我们将在模块内生成的硬件的模板。
第二个参数列表(op: (T, T) => T)
是一个函数。作为函数式编程语言,functions are values in Scala, and thus can be used as arguments just like our Int
argument. (T, T) => T
reads as a function of two arguments, both of type T
, that returns a T
. Remember that T
is our hardware type that is a subclass of Data
. Because op
is in a second parameter list, this is telling Scala that it should infer T
from gen
, and then use the same T
for op
. For example, if gen
is UInt(8.W)
, Scala infers T
as UInt
. This then constrains op
to be a function of type (UInt, UInt) => UInt
. Bitwise AND is such a function, so we can pass an anonymous function 到 AND 两个 UInt
:(_ & _)
.
现在我们已经有了参数化的抽象类型 MyModule
class,我们如何实际使用它?上面我给出了如何将它与 UInt
s 一起使用的片段,但让我们看看如何获得一些实际的 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.
上看到我上面提供的代码的完整、可运行版本
我希望有人觉得这个例子有用!
我正在尝试将运算符传递给模块,以便可以通用地构建模块。我传递了一个双输入运算符参数,然后在缩减操作中使用它。如果我用具体的运算符替换传递的参数,这就可以了。
将 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)
}
}
}
虽然只传入运算符的字符串形式,然后使用简单的 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
// ...
})
}
Chick 已经提供了一个很好的答案,但我想提供另一个例子来说明和解释 Chisel 和 Scala 在硬件设计方面的一些真正强大的功能。我知道你 (Guy) 可能知道大部分内容,但我想为遇到这个问题的其他人提供详细的答案。
我将从完整示例开始,然后突出显示正在使用的一些功能。
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]
这叫做Type Parameter (T
) with an Upper Type Bound (<: Data
). This allows us to make the Module
generic to the hardware type with which we parameterize it. We give T
an upper bound of Data
(which is a type from Chisel) to tell Scala that this is a hardware type we can use to generate hardware with Chisel. The upper-bound means it must be a subtype of Data, which includes all of the Chisel hardware types (eg. UInt
, SInt
, Vec
, Bundle
and user classes that extend Bundle
). This is the exact same way that the Chisel constructors like Reg(...)
are parameterized。
您会注意到有 multiple parameter lists、(n: Int, gen: T)
和 (op: (T, T) => T)
。第一个参数 n: Int
是一个简单的整数参数。第二个参数 gen: T
是我们的通用类型 T
,因此是 Data
的子类型,用作我们将在模块内生成的硬件的模板。
第二个参数列表(op: (T, T) => T)
是一个函数。作为函数式编程语言,functions are values in Scala, and thus can be used as arguments just like our Int
argument. (T, T) => T
reads as a function of two arguments, both of type T
, that returns a T
. Remember that T
is our hardware type that is a subclass of Data
. Because op
is in a second parameter list, this is telling Scala that it should infer T
from gen
, and then use the same T
for op
. For example, if gen
is UInt(8.W)
, Scala infers T
as UInt
. This then constrains op
to be a function of type (UInt, UInt) => UInt
. Bitwise AND is such a function, so we can pass an anonymous function 到 AND 两个 UInt
:(_ & _)
.
现在我们已经有了参数化的抽象类型 MyModule
class,我们如何实际使用它?上面我给出了如何将它与 UInt
s 一起使用的片段,但让我们看看如何获得一些实际的 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.
上看到我上面提供的代码的完整、可运行版本我希望有人觉得这个例子有用!