Chisel参数化生成器(从Scala讲起)

动机

要想使得Chisel模块成为代码生成器,就必须要有一些东西来告诉生成器如何执行这个工作。这一节会介绍模块的参数化,涉及多种方法和Scala语言特性。参数传递实现的丰富会直接传递到生成的电路的丰富。参数应该提供有用的默认值、容易设置,且不会设置成非法或无意义的值。对于更复杂的系统,如果能够以一种不影响其他模块用法的方法在局部重载,那就很有用。

参数传递

Chisel为写硬件生成器提供了强大的构造。生成器是接受一些电路参数并生成一个电路描述的程序。本节将会讨论Chisel生成器如何获取参数。

例子:参数化的Scala对象

每个Chisel中的Mudule都也是Scala的类,Scala类都可以向下面这样参数化:

class ParameterizedScalaObject(param1: Int, param2: String) {println(s"I have parameters: param1 = $param1 and param2 = $param2")
}
val obj1 = new ParameterizedScalaObject(4,     "Hello")
val obj2 = new ParameterizedScalaObject(4 + 2, "World")

执行结果为:

I have parameters: param1 = 4 and param2 = Hello
I have parameters: param1 = 6 and param2 = World

例子:参数化的Chisel对象

Chisel模块也可以以同样的方式参数化。下面的模块有其所有输入和输出的宽度作为参数。运行下面的代码可以打印生成Verilog代码。修改参数可以看到输出的改变。

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.testclass ParameterizedWidthAdder(in0Width: Int, in1Width: Int, sumWidth: Int) extends Module {require(in0Width >= 0)require(in1Width >= 0)require(sumWidth >= 0)val io = IO(new Bundle {val in0 = Input(UInt(in0Width.W))val in1 = Input(UInt(in1Width.W))val sum = Output(UInt(sumWidth.W))})// a +& b includes the carry, a + b does notio.sum := io.in0 +& io.in1
}object MyModule extends App {println(getVerilogString(new ParameterizedWidthAdder(1, 4, 6)))
}

输出如下:

module ParameterizedWidthAdder(input        clock,input        reset,input        io_in0,input  [3:0] io_in1,output [5:0] io_sum
);wire [3:0] _GEN_0 = {{3'd0}, io_in0}; // @[MyModule.scala 16:20]wire [4:0] _io_sum_T = _GEN_0 + io_in1; // @[MyModule.scala 16:20]assign io_sum = {{1'd0}, _io_sum_T}; // @[MyModule.scala 16:10]
endmodule

前面的代码中有一些require(...)语句。这些是预展开的断言,在希望生成器只在一定的参数化或一些参数化互斥或无意义时很有用。上面的代码就检查了宽度应该是非负数。

在仿真时有单独的断言构造,为assert(...)

使用参数化模块排序

下面的代码块是个参数化排序,和前面的Sort4类似。和上面参数化加法器的例子不同,这个例子的输入输出是固定的。这里的参数会控制模块内部的硬件生成。

例子:参数化的4输入排序

和前面的Sort4不同,这里的实现会参数化为降序或升序排序。

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.test/** Sort4 sorts its 4 inputs to its 4 outputs */
class Sort4(ascending: Boolean) extends Module {val io = IO(new Bundle {val in0 = Input(UInt(16.W))val in1 = Input(UInt(16.W))val in2 = Input(UInt(16.W))val in3 = Input(UInt(16.W))val out0 = Output(UInt(16.W))val out1 = Output(UInt(16.W))val out2 = Output(UInt(16.W))val out3 = Output(UInt(16.W))})// this comparison funtion decides < or > based on the module's parameterizationdef comp(l: UInt, r: UInt): Bool = {if (ascending) {l < r} else {l > r}}val row10 = Wire(UInt(16.W))val row11 = Wire(UInt(16.W))val row12 = Wire(UInt(16.W))val row13 = Wire(UInt(16.W))when(comp(io.in0, io.in1)) {row10 := io.in0            // preserve first two elementsrow11 := io.in1}.otherwise {row10 := io.in1            // swap first two elementsrow11 := io.in0}when(comp(io.in2, io.in3)) {row12 := io.in2            // preserve last two elementsrow13 := io.in3}.otherwise {row12 := io.in3            // swap last two elementsrow13 := io.in2}val row21 = Wire(UInt(16.W))val row22 = Wire(UInt(16.W))when(comp(row11, row12)) {row21 := row11            // preserve middle 2 elementsrow22 := row12}.otherwise {row21 := row12            // swap middle two elementsrow22 := row11}val row20 = Wire(UInt(16.W))val row23 = Wire(UInt(16.W))when(comp(row10, row13)) {row20 := row10            // preserve the first and the forth elementsrow23 := row13}.otherwise {row20 := row13            // swap the first and the forth elementsrow23 := row10}when(comp(row20, row21)) {io.out0 := row20            // preserve first two elementsio.out1 := row21}.otherwise {io.out0 := row21            // swap first two elementsio.out1 := row20}when(comp(row22, row23)) {io.out2 := row22            // preserve first two elementsio.out3 := row23}.otherwise {io.out2 := row23            // swap first two elementsio.out3 := row22}
}object MyModule extends App {// Here are the testerstest(new Sort4(true)) { c => c.io.in0.poke(3.U)c.io.in1.poke(6.U)c.io.in2.poke(9.U)c.io.in3.poke(12.U)c.io.out0.expect(3.U)c.io.out1.expect(6.U)c.io.out2.expect(9.U)c.io.out3.expect(12.U)c.io.in0.poke(13.U)c.io.in1.poke(4.U)c.io.in2.poke(6.U)c.io.in3.poke(1.U)c.io.out0.expect(1.U)c.io.out1.expect(4.U)c.io.out2.expect(6.U)c.io.out3.expect(13.U)c.io.in0.poke(13.U)c.io.in1.poke(6.U)c.io.in2.poke(4.U)c.io.in3.poke(1.U)c.io.out0.expect(1.U)c.io.out1.expect(4.U)c.io.out2.expect(6.U)c.io.out3.expect(13.U)}test(new Sort4(false)) { c =>c.io.in0.poke(3.U)c.io.in1.poke(6.U)c.io.in2.poke(9.U)c.io.in3.poke(12.U)c.io.out0.expect(12.U)c.io.out1.expect(9.U)c.io.out2.expect(6.U)c.io.out3.expect(3.U)c.io.in0.poke(13.U)c.io.in1.poke(4.U)c.io.in2.poke(6.U)c.io.in3.poke(1.U)c.io.out0.expect(13.U)c.io.out1.expect(6.U)c.io.out2.expect(4.U)c.io.out3.expect(1.U)c.io.in0.poke(1.U)c.io.in1.poke(6.U)c.io.in2.poke(4.U)c.io.in3.poke(13.U)c.io.out0.expect(13.U)c.io.out1.expect(6.U)c.io.out2.expect(4.U)c.io.out3.expect(1.U)}println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
}

测试通过。

选项和默认参数

有时候函数会返回值,有时候则不会。除了不能返回值时报错以外,Scala还有个机制来将其编码到类型系统中来。

例子:错误的映射索引调用

在下面的例子中有个映射(Map),包含几个键值对(Key-Value Pairs)。如果我们尝试访问一个丢失的键值对,就会得到一个运行时错误。

val map = Map("a" -> 1)
val a = map("a")
println(a)
val b = map("b")
println(b)

输出:

java.util.NoSuchElementException: key not found: b

例子:得到不确定的切片

然而,Map提供了另一种方式来访问一个键值对,是通过get方法。使用get方法会返回一个抽象类Option的值。Option有两个子类,SomeNone

val map = Map("a" -> 1)
val a = map.get("a")
println(a)
val b = map.get("b")
println(b)

输出为:

Some(1)
None

类型分别为:

map: Map[String, Int] = Map("a" -> 1)
a: Option[Int] = Some(1)
b: Option[Int] = None

后面的章节中会看到,Option极其重要,因为它可以让用户用匹配语句来检查Scala类型或值。

例子:getOrElse

Map一样,Option也有一个get方法,如果在None上调用的话就会报错。对于这种实例,我们可以用getOrElse提供一个默认值,即如果有值就返回值,没有就返回该方法的参数值。

val some = Some(1)
val none = None
println(some.get)          // Returns 1
// println(none.get)       // Errors!
println(some.getOrElse(2)) // Returns 1
println(none.getOrElse(2)) // Returns 2

输出:

1
1
2

其中类型分别为:

some: Some[Int] = Some(1)
none: None.type = None

具备默认值的参数的Option

当对象或函数有很多参数的时候,每次都指定全部显然是又麻烦又易错。在很前面,我们介绍了命名参数和参数默认值。有的时候,参数并没有一个好的默认值。Option可以和默认值None一起使用在这些场合。

例子:可选的Reset

下面展示了一个代码块,延迟它的输入一个时钟周期。如果resetValue = None,即默认值,寄存器将会无法得到复位值并被初始化为辣鸡值。这避免了普遍但丑陋的情况,即用常规范围之外的值来指示None,比如使用-1作为复位值来指示某个寄存器没有复位。

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.testclass DelayBy1(resetValue: Option[UInt] = None) extends Module {val io = IO(new Bundle {val in  = Input( UInt(16.W))val out = Output(UInt(16.W))})val reg = if (resetValue.isDefined) { // resetValue = Some(number)RegInit(resetValue.get)} else { //resetValue = NoneReg(UInt())}reg := io.inio.out := reg
}object MyModule extends App {println(getVerilogString(new DelayBy1))println(getVerilogString(new DelayBy1(Some(3.U))))
}

输出如下:

module DelayBy1(input         clock,input         reset,input  [15:0] io_in,output [15:0] io_out
);
`ifdef RANDOMIZE_REG_INITreg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INITreg [15:0] reg_; // @[MyModule.scala 15:8]assign io_out = reg_; // @[MyModule.scala 18:10]always @(posedge clock) beginreg_ <= io_in; // @[MyModule.scala 17:7]end
// Register and memory initialization
...
endmodulemodule DelayBy1(input         clock,input         reset,input  [15:0] io_in,output [15:0] io_out
);
`ifdef RANDOMIZE_REG_INITreg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INITreg [15:0] reg_; // @[MyModule.scala 13:12]assign io_out = reg_; // @[MyModule.scala 18:10]always @(posedge clock) beginif (reset) begin // @[MyModule.scala 13:12]reg_ <= 16'h3; // @[MyModule.scala 13:12]end else beginreg_ <= io_in; // @[MyModule.scala 17:7]endend
// Register and memory initialization
...
endmodule

两者的区别在于,前者未对reset信号进行相应,而后者在时钟上升沿收到reset信号后,将寄存器的值复位为16'h3

match/case语句

Scala的匹配概念贯穿整个Chisel语言,应该作为每个Chisel程序员的基本理解内容。Scala提供了match运算符,支持以下功能:

  1. 对于候选项的简单测试,类似于C语言的swtich语句;
  2. 对值的ad-hoc组合的更复杂的测试;
  3. 当一个变量的类型未知或待指定时,基于该变量的类型采取行为,例如:
    1. 变量来自一个异构的列表:val mixedList = List(1, "string", false)
    2. 或已知变量是一个超类的成员,但不知道是指定的哪个子类;
  4. 根据一个正则表达式从一个字符串中提取子串;

例子:值匹配

下面的例子,基于匹配到的变量的值,我们可以执行不同的case语句:

// y is an integer variable defined somewhere else in the code
val y = 7
/// ...
val x = y match {case 0 => "zero" // One common syntax, preferred if fits in one linecase 1 =>        // Another common syntax, preferred if does not fit in one line."one"        // Note the code block continues until the next casecase 2 => {      // Another syntax, but curly braces are not required"two"}case _ => "many" // _ is a wildcard that matches all values
}
println("y is " + x)

输出为:

y is many

例子中,match操作符检查可能的值,对于每种情况都会返回一个字符串。还有几点需要注意的是:

  1. 每个=>操作符之后的代码块都会延续到右大括号或者下一个case语句之前;
  2. match语句会按顺序进行搜索,一旦匹配就不会检查后续的情况了;
  3. _下划线是通配符,用于匹配前面没有匹配上的任何其他情况;

例子:多值匹配

多个值可以同时进行匹配。这里有个简单的例子,是一个由match语句和几个值构造的真值表:

def animalType(biggerThanBreadBox: Boolean, meanAsCanBe: Boolean): String = {(biggerThanBreadBox, meanAsCanBe) match {case (true, true) => "wolverine"case (true, false) => "elephant"case (false, true) => "shrew"case (false, false) => "puppy"}
}
println(animalType(true, true))

输出如下:

wolverine

例子:类型匹配

Scala是一种强类型的语言,所以每个对象在执行的时候都是知道类型的。我们可以使用match语句来利用这个信息指示控制流:

val sequence = Seq("a", 1, 0.0)
sequence.foreach { x =>x match {case s: String => println(s"$x is a String")case s: Int    => println(s"$x is an Int")case s: Double => println(s"$x is a Double")case _ => println(s"$x is an unknown type!")}
}

输出为:

a is a String
1 is an Int
0.0 is a Double

代码中case s中的s是待输入的对象,写成a也行,x也行。

例子:类型匹配和擦除

类型匹配有一些限制。因为Scala是在JVM上运行的,而JVM不会维护复合(polymorphic)类型,所以不能在运行时匹配,因为会被擦除。下面的例子会总是匹配第一个条件语句,因为[String][Int][Double]复合类型被擦除了,case语句只会匹配上Seq

val sequence = Seq(Seq("a"), Seq(1), Seq(0.0))
sequence.foreach { x =>x match {case s: Seq[String] => println(s"$x is a String")case s: Seq[Int]    => println(s"$x is an Int")case s: Seq[Double] => println(s"$x is a Double")}
}

输出为(编译或运行会提示警告信息):

<console>:15: warning: non-variable type argument String in type pattern Seq[String] (the underlying of Seq[String]) is unchecked since it is eliminated by erasurecase s: Seq[String] => println(s"$x is a String")^
<console>:16: warning: non-variable type argument Int in type pattern Seq[Int] (the underlying of Seq[Int]) is unchecked since it is eliminated by erasurecase s: Seq[Int]    => println(s"$x is an Int")^
<console>:17: warning: non-variable type argument Double in type pattern Seq[Double] (the underlying of Seq[Double]) is unchecked since it is eliminated by erasurecase s: Seq[Double] => println(s"$x is a Double")^
<console>:16: warning: unreachable codecase s: Seq[Int]    => println(s"$x is an Int")List(a) is a String
List(1) is a String
List(0.0) is a String

例子:可选的复位匹配

下面这个例子展示了相同的DelayBy1模块,但是用match语句替换了if/else

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.testclass DelayBy1(resetValue: Option[UInt] = None) extends Module {val io = IO(new Bundle {val in  = Input( UInt(16.W))val out = Output(UInt(16.W))})val reg = resetValue match {case Some(r) => RegInit(r)case None    => Reg(UInt())}reg := io.inio.out := reg
}object MyModule extends App {println(getVerilogString(new DelayBy1))println(getVerilogString(new DelayBy1(Some(3.U))))
}

输出如下:

module DelayBy1(input         clock,input         reset,input  [15:0] io_in,output [15:0] io_out
);
`ifdef RANDOMIZE_REG_INITreg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INITreg [15:0] reg_; // @[MyModule.scala 14:24]assign io_out = reg_; // @[MyModule.scala 17:10]always @(posedge clock) beginreg_ <= io_in; // @[MyModule.scala 16:7]end
// Register and memory initialization
...
endmodulemodule DelayBy1(input         clock,input         reset,input  [15:0] io_in,output [15:0] io_out
);
`ifdef RANDOMIZE_REG_INITreg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INITreg [15:0] reg_; // @[MyModule.scala 13:28]assign io_out = reg_; // @[MyModule.scala 17:10]always @(posedge clock) beginif (reset) begin // @[MyModule.scala 13:28]reg_ <= 16'h3; // @[MyModule.scala 13:28]end else beginreg_ <= io_in; // @[MyModule.scala 16:7]endend
// Register and memory initialization
...
endmodule

效果是一样的。

有可选字段的IO

有时候我们会希望IO接口可选地被包含或排外。也许有一些内置语句可以在调试的时候很好用,但是你希望在生成器用于系统的时候隐藏它们。也许你的生成器有一些输入不需要在每种场合都连接,因为有一个有意义的默认值。

例子:有选项的可选IO

可选的bundle字段是一种获得此功能的方法。下面的例子中,展示了一个单比特加法器,进位输入接口是可选的。如果有进位输入接口,io.carryIn会有Some[UInt]类型并包括在IO中。如果进位输入接口不被包含,io.carryIn会有None类型并从IO中排除:

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.testclass HalfFullAdder(val hasCarry: Boolean) extends Module {val io = IO(new Bundle {val a = Input(UInt(1.W))val b = Input(UInt(1.W))val carryIn = if (hasCarry) Some(Input(UInt(1.W))) else Noneval s = Output(UInt(1.W))val carryOut = Output(UInt(1.W))})val sum = io.a +& io.b +& io.carryIn.getOrElse(0.U)io.s := sum(0)io.carryOut := sum(1)
}object MyModule extends App {test(new HalfFullAdder(false)) { c =>require(!c.hasCarry, "DUT must be half adder")// 0 + 0 = 0c.io.a.poke(0.U)c.io.b.poke(0.U)c.io.s.expect(0.U)c.io.carryOut.expect(0.U)// 0 + 1 = 1c.io.b.poke(1.U)c.io.s.expect(1.U)c.io.carryOut.expect(0.U)// 1 + 1 = 2c.io.a.poke(1.U)c.io.s.expect(0.U)c.io.carryOut.expect(1.U)// 1 + 0 = 1c.io.b.poke(0.U)c.io.s.expect(1.U)c.io.carryOut.expect(0.U)}test(new HalfFullAdder(true)) { c =>require(c.hasCarry, "DUT must be full adder")c.io.carryIn.get.poke(0.U)// 0 + 0 + 0 = 0c.io.a.poke(0.U)c.io.b.poke(0.U)c.io.s.expect(0.U)c.io.carryOut.expect(0.U)// 0 + 0 + 1 = 1c.io.b.poke(1.U)c.io.s.expect(1.U)c.io.carryOut.expect(0.U)// 0 + 1 + 1 = 2c.io.a.poke(1.U)c.io.s.expect(0.U)c.io.carryOut.expect(1.U)// 0 + 1 + 0 = 1c.io.b.poke(0.U)c.io.s.expect(1.U)c.io.carryOut.expect(0.U)c.io.carryIn.get.poke(1.U)// 1 + 0 + 0 = 1c.io.a.poke(0.U)c.io.b.poke(0.U)c.io.s.expect(1.U)c.io.carryOut.expect(0.U)// 1 + 0 + 1 = 2c.io.b.poke(1.U)c.io.s.expect(0.U)c.io.carryOut.expect(1.U)// 1 + 1 + 1 = 3c.io.a.poke(1.U)c.io.s.expect(1.U)c.io.carryOut.expect(1.U)// 1 + 1 + 0 = 2c.io.b.poke(0.U)c.io.s.expect(0.U)c.io.carryOut.expect(1.U)}println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
}

测试通过。

例子:有零宽网线的可选IO

对于Option另一种实现类似功能的方法是零宽度的网线(zero-width wire)。Chisel类型允许零宽。一个零宽的IO接口会从生成的Verilog代码中修剪掉,所有尝试使用零宽值的都会得到一个常数0。如果0是有意义的默认值,那用零宽网线就很好,因为消除了匹配选项或调用getOrElse的需要。

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.testclass HalfFullAdder(val hasCarry: Boolean) extends Module {val io = IO(new Bundle {val a = Input(UInt(1.W))val b = Input(UInt(1.W))val carryIn = Input(if (hasCarry) UInt(1.W) else UInt(0.W))val s = Output(UInt(1.W))val carryOut = Output(UInt(1.W))})val sum = io.a +& io.b +& io.carryInio.s := sum(0)io.carryOut := sum(1)
}object MyModule extends App {println("// Half Adder:")println(getVerilogString(new HalfFullAdder(false)))println("\n\n// Full Adder:")println(getVerilogString(new HalfFullAdder(true)))
}

输出如下:

// Half Adder:
module HalfFullAdder(input   clock,input   reset,input   io_a,input   io_b,output  io_s,output  io_carryOut
);wire [1:0] _sum_T = io_a + io_b; // @[MyModule.scala 15:18]wire [2:0] sum = {{1'd0}, _sum_T}; // @[MyModule.scala 15:26]assign io_s = sum[0]; // @[MyModule.scala 16:14]assign io_carryOut = sum[1]; // @[MyModule.scala 17:21]
endmodule// Full Adder:
module HalfFullAdder(input   clock,input   reset,input   io_a,input   io_b,input   io_carryIn,output  io_s,output  io_carryOut
);wire [1:0] _sum_T = io_a + io_b; // @[MyModule.scala 15:18]wire [1:0] _GEN_0 = {{1'd0}, io_carryIn}; // @[MyModule.scala 15:26]wire [2:0] sum = _sum_T + _GEN_0; // @[MyModule.scala 15:26]assign io_s = sum[0]; // @[MyModule.scala 16:14]assign io_carryOut = sum[1]; // @[MyModule.scala 17:21]
endmodule

隐式

编程的时候经常会需要很多样板代码。为了处理这种使用情况,Scala引入了implicit这个关键词,允许编译器用一些语法糖。因为很多事发生在一幕之后,隐式可以表现得很有魔力。这一节有一些基本的例子来解释他们是什么东西,通常什么时候会用到。

隐式参数

有时候你的代码需要从一系列很深的调用中以某种顺序访问一个顶层变量。不用手动让这个变量穿过每个函数调用,你可以使用隐式参数来完成这件事。

例子:隐式的猫

下面的例子中,我们可以显式或隐式传递猫的数量:

object CatDog {implicit val numberOfCats: Int = 3//implicit val numberOfDogs: Int = 5def tooManyCats(nDogs: Int)(implicit nCats: Int): Boolean = nCats > nDogsval imp = tooManyCats(2)    // Argument passed implicitly!val exp = tooManyCats(2)(1) // Argument passed explicitly!
}
CatDog.imp
CatDog.exp

输出为:

res16_1: Boolean = true
res16_2: Boolean = false

发生甚么事了?!首先,我们定义了一个隐式值numberOfCats。在给定的范围内,只能有一个给定类型的隐式值。然后,我们定义了一个函数,接受两个参数,第一个是任何显式的参数,第二个是任何隐式的参数。当我们调用tooManyCats的时候,我们可以省略第二个隐式参数,让编译器自己去找,也可以显式给定一个值,这个值可以和隐式值不一样。

有两种情况使用隐式参数会失败:

  1. 范围内定义了两个及以上给定类型的隐式值;
  2. 编译器找不到函数调用必须的那个隐式值;

例子:隐式日志

下面的代码展示了一种利用隐式参数的可能用于在Chisel生成器中实现日志的方法(在Scala中有更好的日志方法):

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.testobject MyModule extends App {sealed trait Verbosityimplicit case object Silent extends Verbositycase object Verbose extends Verbosityclass ParameterizedWidthAdder(in0Width: Int, in1Width: Int, sumWidth: Int)(implicit verbosity: Verbosity)extends Module {def log(msg: => String): Unit = verbosity match {case Silent =>case Verbose => println(msg)}require(in0Width >= 0)log(s"// in0Width of $in0Width OK")require(in1Width >= 0)log(s"// in1Width of $in1Width OK")require(sumWidth >= 0)log(s"// sumWidth of $sumWidth OK")val io = IO(new Bundle {val in0 = Input(UInt(in0Width.W))val in1 = Input(UInt(in1Width.W))val sum = Output(UInt(sumWidth.W))})log("// Made IO")io.sum := io.in0 + io.in1log("// Assigned output")}println(getVerilogString(new ParameterizedWidthAdder(1, 4, 5)))println(getVerilogString(new ParameterizedWidthAdder(1, 4, 5)(Verbose)))
}

输出的时候,隐式的就会给参数Silentlog()函数就什么都不会做,给了Verbose参数之后,log()就会输出日志。输出如下:

module ParameterizedWidthAdder(input        clock,input        reset,input        io_in0,input  [3:0] io_in1,output [4:0] io_sum
);wire [3:0] _GEN_0 = {{3'd0}, io_in0}; // @[MyModule.scala 29:22]wire [3:0] _io_sum_T_1 = _GEN_0 + io_in1; // @[MyModule.scala 29:22]assign io_sum = {{1'd0}, _io_sum_T_1}; // @[MyModule.scala 29:12]
endmodule// in0Width of 1 OK
// in1Width of 4 OK
// sumWidth of 5 OK
// Made IO
// Assigned output
module ParameterizedWidthAdder(input        clock,input        reset,input        io_in0,input  [3:0] io_in1,output [4:0] io_sum
);wire [3:0] _GEN_0 = {{3'd0}, io_in0}; // @[MyModule.scala 29:22]wire [3:0] _io_sum_T_1 = _GEN_0 + io_in1; // @[MyModule.scala 29:22]assign io_sum = {{1'd0}, _io_sum_T_1}; // @[MyModule.scala 29:12]
endmodule

隐式转换

和隐式参数一样,隐式函数(也叫做隐式转换)用于减少样板代码。具体来说,他们用于自动转换一个Scala对象到另个一Scala对象。

例子:隐式转换

下面的例子中,我们有两个类,AnimalHumanAnimal有一个species(物种)字段,但是Human没有。然而,通过实现一个隐式转换,我们也可以在Human上调用species

class Animal(val name: String, val species: String)
class Human(val name: String)
implicit def human2animal(h: Human): Animal = new Animal(h.name, "Homo sapiens")
val me = new Human("Adam")
println(me.species)

输出为:

Homo sapiens

一般来说,隐式会让代码变得难懂,所以建议实在没办法的时候再用。

先试试inheritancetrait或者是方法重载。

Mealy状态机生成器例子

Mealy状态机与Moore有限状态机不同,Mealy有限状态机的输出不但与当前状态有关,而且与输入信号的当前值有关。

Mealy有限状态机的输出直接受输入信号的当前值影响,而输入信号可能在一个时钟周期内任意时刻变化,这使得Mealy有限状态机对输入的响应发生在当前时钟周期,比Moore有限状态机对输入信号的响应要早一个周期。因此,输入信号的噪声可能影响在输出的信号。——百度百科

下面的例子展示了一个单比特输入的Mealy状态机的生成器。可以跟读代码看看到底发生了什么:

import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.testobject MyModule extends App {// Mealy machine hascase class BinaryMealyParams(// number of statesnStates: Int,// initial states0: Int,// function describing state transitionstateTransition: (Int, Boolean) => Int,// function describing outputoutput: (Int, Boolean) => Int) {require(nStates >= 0)require(s0 < nStates && s0 >= 0)}class BinaryMealy(val mp: BinaryMealyParams) extends Module {val io = IO(new Bundle {val in = Input(Bool())val out = Output(UInt())})val state = RegInit(UInt(), mp.s0.U)// output zero if no statesio.out := 0.Ufor (i <- 0 until mp.nStates) {when (state === i.U) {when (io.in) {state  := mp.stateTransition(i, true).Uio.out := mp.output(i, true).U}.otherwise {state  := mp.stateTransition(i, false).Uio.out := mp.output(i, false).U}}}}// example from https://en.wikipedia.org/wiki/Mealy_machineval nStates = 3val s0 = 2def stateTransition(state: Int, in: Boolean): Int = {if (in) {1} else {0}}def output(state: Int, in: Boolean): Int = {if (state == 2) {return 0}if ((state == 1 && !in) || (state == 0 && in)) {return 1} else {return 0}}val testParams = BinaryMealyParams(nStates, s0, stateTransition, output)test(new BinaryMealy(testParams)) { c =>c.io.in.poke(false.B)c.io.out.expect(0.U)c.clock.step(1)c.io.in.poke(false.B)c.io.out.expect(0.U)c.clock.step(1)c.io.in.poke(false.B)c.io.out.expect(0.U)c.clock.step(1)c.io.in.poke(true.B)c.io.out.expect(1.U)c.clock.step(1)c.io.in.poke(true.B)c.io.out.expect(0.U)c.clock.step(1)c.io.in.poke(false.B)c.io.out.expect(1.U)c.clock.step(1)c.io.in.poke(true.B)c.io.out.expect(1.U)c.clock.step(1)c.io.in.poke(false.B)c.io.out.expect(1.U)c.clock.step(1)c.io.in.poke(true.B)c.io.out.expect(1.U)}println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
}

测试通过。

简单说一下:

  1. BinaryMealyParams类包含了Mealy机的一些参数,包括状态数、初始化状态、状态转换函数和输出函数,并限定了状态个数和初始状态的取值;
  2. BinaryMealy类定义了Mealy机,以BinaryMealyParams类为参数,定义了执行状态转换和输出的行为(如何调用);
  3. 下面根据Wikipedia上面的例子,给定了一个Mealy机的参数,然后构造了参数类,进而构造了Mealy机并测试。

Chisel教程——08.Chisel参数化生成器(从Scala讲起)相关推荐

  1. Chisel教程——02.Chisel环境配置和第一个Chisel模块的实现与测试

    Chisel环境配置和第一个Chisel模块的实现与测试 动机 现在已经对Scala有一定的了解了,可以开始构造一些硬件了.Chisel的全称为Constructing Hardware In a S ...

  2. Chisel教程——03.Chisel中的组合逻辑(结尾附上Chisel3 Cheat Sheet)

    Chisel组合逻辑 这一节将会介绍如何使用Chisel组件来实现组合逻辑. 本节将会演示三种基本的Chisel类型(UInt,无符号整数:SInt,有符号整数:Bool,布尔值)可以如何连接和操作. ...

  3. Chisel教程——14.(完结篇)Scala和Chisel中的数据类型

    (完结篇)Scala和Chisel中的数据类型 完结篇开头的碎碎念 这是这个系列的最后一篇文章了,官方的Chisel-Bootcamp中后面还有FIRRTL相关的内容,但设计一个RISC-V CPU这 ...

  4. 吃透Chisel语言.32.Chisel进阶之硬件生成器(一)——Chisel中的参数化

    Chisel进阶之硬件生成器(一)--Chisel中的参数化 Chisel区别于其他硬件描述语言的最强大的地方在于,我们可以用Chisel写硬件生成器.对于老一点的硬件描述语言,比如VHDL和Veri ...

  5. 吃透Chisel语言.33.Chisel进阶之硬件生成器(二)——Chisel组合逻辑电路生成:以BCD编码表为例

    Chisel进阶之硬件生成器(二)--Chisel组合逻辑电路生成:以BCD编码表为例 上一篇文章我们学习了两种类型的变量在Chisel之中的使用,然后分别介绍了Chisel中四种参数化的方法,对于我 ...

  6. 吃透Chisel语言.18.Chisel模块详解(五)——Chisel中使用Verilog模块

    Chisel模块详解(五)--Chisel中使用Verilog模块 上一篇文章讲述了用函数实现轻量级模块的方法,可以大幅度提升编码效率.Chisel中也提供了一些好用的函数,方便我们编写代码,也方便C ...

  7. 吃透Chisel语言.38.Chisel实战之以FIFO为例(三)——几种FIFO的变体的Chisel实现

    Chisel实战之以FIFO为例(三)--几种FIFO的变体的Chisel实现 上一篇文章对实现串口通信的最小系统做了简单阐述,然后从发送端开始,实现了基于单字节FIFO缓冲区的发送端,接着类似地实现 ...

  8. ExtJS 4.2 教程-08:布局系统详解

    ExtJS 4.2 系列教程导航目录: ExtJS 4.2 教程-01:Hello ExtJS ExtJS 4.2 教程-02:bootstrap.js 工作方式 ExtJS 4.2 教程-03:使用 ...

  9. [译]Vulkan教程(08)逻辑设备和队列

    [译]Vulkan教程(08)逻辑设备和队列 Introduction 入门 After selecting a physical device to use we need to set up a  ...

最新文章

  1. 算法(4)数据结构:堆
  2. Ceph分布式存储高性能设计
  3. 圈子 | 精彩预告:P2P金融那些事儿
  4. MATLAB-1:入门基础
  5. 请设计输出实数的格式,包括:⑴一行输出一个实数;⑵一行内输出两个实数;⑶一行内输出三个实数。实数用quot;6.2fquot;格式输出。
  6. Php学习准备(环境)
  7. php遍历文件夹下文件内容_PHP递归遍历指定文件夹内的文件实现方法
  8. 编程语言流行指数:Python 稳居宝座,Java 滑坡!
  9. android Fragment 笔记
  10. 完整版linux下android源码下载、编译、模拟器启动运行
  11. ipython怎么安装_ipython安装(python3.6.1)(转载)
  12. sai钢笔图层怎么移动某条线?
  13. 全自动采集小说模板源码+送7条可用7大采集规则
  14. 用for循环输出俄文的“字母表”
  15. Java8 Lambda表达式(三)Lambda表达式与Stream API
  16. HEVC新特点一览(3)
  17. Fourth4: NAT VRRP | Cloud computing
  18. 怎么修改mysql的默认端口号
  19. PostgreSQL 查表、所属schema、字段、字段类型、注释等信息
  20. 理财启蒙必读书籍《小钱狗狗》心得

热门文章

  1. Python学好了,飞机票你还买不到么?
  2. 移动端获得微信openid_构建用于移动设备的OpenID Connect流
  3. SN及Mac地址烧录
  4. php公众号发送客服消息
  5. 将“活雷锋” 搬到网上 ——记百姓网CEO王建硕
  6. Windows服务器流量异常排查分析(Nginx日志分析):Web Log Expert 和 GlassWire 的使用
  7. wmv怎么转换成mp4?
  8. DELL R720服务器诊断
  9. 研究了11种实时聊天软件,我发现都具备这些功能…
  10. vue使用Ant design的input输入框,去掉边框和获取焦点时的边框