本节的目的
通过上一章的学习,你应该处理Scala我们已经知道了,我们可以开始看看如何实现硬件。Chisel全称嵌入Scala硬件结构语言(onstructingardwaren acalambeddedanguage)。这意味着它是Scala领域专用语言(DSL),允许您同时使用同一代码Scala和Chisel编程。理解什么代码Scala什么代码是Chisel以后再讨论这一点很重要。在这一节中,我们只需要Chisel看作是实现Verilog更好的方法。在这一节中,我们将看到一个完整的部分Chisel我们现在只需要大致掌握模块及其测试的一些要点,然后我们就会看到更多的例子。
val path = System.getProperty("user.dir") "/source/load-ivy.sc" interp.load.module(ammonite.ops.Path(java.nio.file.FileSystems.getDefault().getPath(path)))
如上一章所述,下面的句子在Scala中导入Chisel:
import chisel3._ import chisel3.util._ import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester} import chisel3._ import chisel3.util._ import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
第一个Chisel模块
本节将介绍实现一个简单的硬件模块、测试用例以及如何操作它。它可能包含很多你不理解的东西,不需要太担心细节,我们将继续回到这个例子来加强你所学到的东西。
**例子:模块** 类似于Verilog,我们可以在Chisel中定义模块(module)。下面的例子是一个名字Passthrough
的ChiselModule
,它有一个4比特输入,名称为in
,还有一个4比特的输出out
。输入该模块的组合电路in
连接到输出out
,所以out
由in
驱动。
// Chisel代码:定义一个模块 class Passthrough extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) io.out := io.in } defined class Passthrough
上面的代码包含了很多东西我们将详细解释代码的每一行:
class Passthrough extends Module {
我们定义了一个叫做模块的模块Passthrough
。Module
是一个Chisel所有的硬件模块都必须从内部建筑中继承。
val io = IO(...)
一个叫我们io
所有输入输出端口都在常量中声明。这个常量的名称必须称为io
,而且必须是类IO
对象或实例。这一类需要实例化Bundle:IO(_instantiated_bundle_)
。
new Bundle { val in = Input(...) val out = Output(...) }
我们声明了一种新的硬件结构类型(Bundle),它包含信号in
和out
,这两个信号分别有方向Input和Output。
UInt(4.W)
我们声明了一个信号的硬件类型,它是宽度为4的无符号整数。
io.out := io.in
在这里连接输入端口和输出端口io.in
会驱动io.out
。注意:=
操作符是一个操作符表示右边的信号驱动左边的信号,具有方向性。
硬件结构语言(HCL)巧妙之处在于,我们可以使用底层编程语言Scala作为脚本语言。例如,上述定义Chisel我们可以在模块后使用它Scala调用Chisel编译器来将Chisel的Passthrough
描述转换成Verilog的Passthrough
描述。这一步叫。
// Scala代码:将Chisel设计展开成Verilog设计 //下面生成的代码比较复杂。如果你不明白,你不必太担心 println(getVerilog(new Passthrough))
[info] [0.002] Elaborating design... [info] [0.605] Done elaborating. Total FIRRTL Compile Time: 476.9 ms module cmd2HelperPassthrough( input clock, input reset, input [3:0] io_in, output [3:0] io_out ); assign io_out = io_in; // @[cmd2.sc 6:10] endmodule
注意上面生成的Verilog模块叫做cmd<#>HelperPassthrough
,这是因为在jupyter里面的关系。在正常环境为Passthrough
。但这是一个重要的教训:尽管Chisel硬件模块或其他硬件组件的名称将尽可能保留,但不能保证总是这样。
**例:模块生成器(Generator)** 如果我们将Scala我们可以在这个例子中看到知识的应用Chisel模块是由Scala类别实现。就像其他任何东西一样Scala类一样,我们可以使用一些参数来构造Chisel模块。在这个例子中,我们定义了一个叫做PassthroughGenerator
有一种类叫做width
用于描述输入输出端口的宽度:
// Chisel代码,它包含一个构造参数来描述端口的宽度 class PassthroughGenerator(width: Int) extends Module { val io = IO(new Bundle { val in = Input(UInt(width.W)) val out = Output(UInt(width.W)) }) io.out := io.in } // 我们现在可以用它来生成不同宽度的模块 println(getVerilog(new PassthroughGenerator(10))) println(getVerilog(new PassthroughGenerator(20))) [info] [0.000] Elaborating design... [info] [0.077] Done elaborating. Total FIRRTL Compile Time: 29.1 ms module cmd4HelperPassthroughGenerator( input clock, input reset, input [9:0] io_in, output [9:0] io_out ); assign io_out = io_in; // @[cmd4.sc 6:10] endmodule [info] [0.000] Elaborating design... [info] [0.005] Done elaborating. Total FIRRTL Compile Time: 27.8 ms module cmd4HelperPassthroughGenerator( input clock, input reset, input [19:0] io_in, output [19:0] io_out ); assign io_out = io_in; // @[cmd4.sc 6:10] endmodule defined class PassthroughGenerator
注意上面生成的Verilog会根据width
参数值使用不同的输入/输出位宽。我们可以深入了解这是如何工作的,因为Chisel模块是普通的Scala所以我们可以使用Scala类的构造函数来参数化我们的设计。
这里的可以这样参数化的原因是因为我们使用了Scala,而不是Chisel。Chisel并没有别的API来使得我们可以参数化一个设计。然而我们却可以依赖Scala来使得参数化成为可能。
在这里,因为PassthroughGenerator
并不只是用来描述一个设计,它其实描述了以width
为参数的一系列设计,所以我们将Passthrough
称为一个。
测试您的硬件
没有测试,任何的硬件模块或生成器都不能称为一个完整的设计。本教程也会介绍Chisel内置的测试功能。下面的例子是一个Chisel测试,它将值传递给模块Passthrough
的输入端口in
,并检查输出端口out
上是否能看到相同的值。
**例子:一个测试** 这里有一些高级的Scala语法。但是,除了poke
和expect
语句之外,您不需要理解其他任何东西。您可以将其余代码视为简单的样板来编写这些简单的测试。
// Scala代码:调用驱动(Driver)来实例化 Passthrough 和 PeekPokeTester,并且运行测试。
// 如果不理解下面代码的话不要担心;这是非常复杂的Scala代码。
// 可以简单地把它想象成运行Chisel测试的样板
val testResult = Driver(() => new Passthrough()) {
c => new PeekPokeTester(c) {
poke(c.io.in, 0) // 将输入设置成 0
expect(c.io.out, 0) // 检查输出是否为 0
poke(c.io.in, 1) // 将输入设置成 1
expect(c.io.out, 1) // 检查输出是否为 1
poke(c.io.in, 2) // 将输入设置成 2
expect(c.io.out, 2) // 检查输出是否为 2
}
}
assert(testResult) // Scala代码:如果testResult等于false的话,,这里会抛出异常
println("SUCCESS!!") // Scala代码:到这里,我们的测试已经通过了
[info] [0.000] Elaborating design...
[info] [0.068] Done elaborating.
Total FIRRTL Compile Time: 19.0 ms
Total FIRRTL Compile Time: 10.5 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1561161438550
test cmd2HelperPassthrough Success: 3 tests passed in 5 cycles taking 0.017532 seconds
[info] [0.007] RAN 0 CYCLES PASSED
SUCCESS!!
testResult: Boolean = true
上面发生了什么?这个测试接受一个类型为Passthrough
的模块,驱动它的输入,并且检查它的输出。我们调用poke
来驱动输入,调用expect
来检查输出。如果不使用expect
来比较期望值的话(没有断言的话),也可以使用peek
来读出输出值。
如果所有的expect
语句都为真,上面的代码会返回真(见testResult
)。
**练习:写一个您自己的测试** 写两个测试,一个用来测试位宽为10的PassthroughGenerator
,另一个测试位宽为20的PassthroughGenerator
。至少检查两个值:0和最大值。注意这里三个问号???
在Scala中有特殊含义。直接运行下面的代码会有未被实现NotImplementedError
的错误。将下面的???
替换成您自己的代码:
val test10result = ???
val test20result = ???
assert((test10result == true) && (test20result == true))
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
val test10result = Driver(() => new PassthroughGenerator(10)) {
c => new PeekPokeTester(c) {
poke(c.io.in, 0)
expect(c.io.out, 0)
poke(c.io.in, 1023)
expect(c.io.out, 1023)
}
}
val test20result = Driver(() => new PassthroughGenerator(20)) {
c => new PeekPokeTester(c) {
poke(c.io.in, 0)
expect(c.io.out, 0)
poke(c.io.in, 1048575)
expect(c.io.out, 1048575)
}
}
看一看生成的Verilog/FIRRTL
如果您无法理解Chisel描述的硬件并且习惯于阅读Verilog或FIRRTL(Chisel的中间语言相当于Verilog的可综合子集),那么您可以尝试看看所生成的Verilog。
以下是生成Verilog(之前已经看过),以及FIRRTL的例子:
// 查看生成的 Verilog 代码
println(getVerilog(new Passthrough))
[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 20.5 ms
module cmd2HelperPassthrough(
input clock,
input reset,
input [3:0] io_in,
output [3:0] io_out
);
assign io_out = io_in; // @[cmd2.sc 6:10]
endmodule
// 查看生成的 FIRRTL 代码
println(getFirrtl(new Passthrough))
[info] [0.000] Elaborating design...
[info] [0.070] Done elaborating.
;buildInfoPackage: chisel3, version: 3.2-SNAPSHOT, scalaVersion: 2.12.6, sbtVersion: 1.2.7
circuit cmd2HelperPassthrough :
module cmd2HelperPassthrough :
input clock : Clock
input reset : UInt<1>
output io : {flip in : UInt<4>, out : UInt<4>}
io.out <= io.in @[cmd2.sc 6:10]
解释一下用“printf”来调试
并不总是最好的办法,但是通常可以作为调试的第一步。
因为Chisel生成器是用来生成硬件的程序,需要好好区分是在生成电路的时候打印还是在运行电路的时候打印。
下面是想要打印的三种常见的场景:
- Chisel生成器在生成电路的时候打印
- 在运行电路仿真的时候打印
- 在测试的时候打印
println
是Scala内嵌的打印函数。它用于电路仿真的时候打印,因为生成的电路是FIRRTL或者Verilog,而不是Scala。
下面的代码展示了这三种场景:
class PrintingModule extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
io.out := io.in
printf("Print during simulation: Input is %d\n", io.in)
// chisel printf 的被解析的字符串有自己的语法
printf(p"Print during simulation: IO is $io\n")
println(s"Print during generation: Input is ${io.in}")
}
class PrintingModuleTester(c: PrintingModule) extends PeekPokeTester(c) {
poke(c.io.in, 3)
step(5) // 每次运行Chisel电路会打印
println(s"Print during testing: Input is ${peek(c.io.in)}")
}
chisel3.iotesters.Driver( () => new PrintingModule ) { c => new PrintingModuleTester(c) }
[info] [0.000] Elaborating design...
Print during generation: Input is UInt<4>(IO in unelaborated cmd9$Helper$PrintingModule)
[info] [0.027] Done elaborating.
Total FIRRTL Compile Time: 30.7 ms
Total FIRRTL Compile Time: 18.8 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1561161468619
Print during simulation: Input is 3
Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3)
[info] [0.013] Print during testing: Input is 3
test cmd9HelperPrintingModule Success: 0 tests passed in 10 cycles taking 0.022628 seconds
[info] [0.014] RAN 5 CYCLES PASSED
defined class PrintingModule
defined class PrintingModuleTester
res9_2: Boolean = true