3 Build Process and Testing 21
3.1 Building your Project with sbt . . . . . . . . . . . . . . . . . . . . .21
3.1.1 Source Organization . . . . . . . . . . . . . . . . . . . . . . . 21
3.1.2 Running sbt . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.1.3 Tool Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2 Testing with Chisel . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2.1 PeekPokeTester . . . . . . . . . . . . . . . . . . . . . . . . . .24
3.2.2 Using ScalaTest . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2.3 Waveforms . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2.4 printf Debugging . . . . . . . . . . . . . . . . . . . . . . . . .31
3.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.3.1 A Minimal Project . . . . . . . . . . . . . . . . . . . . . . . . 32
3.3.2 A Testing Exercise . . . . . . . . . . . . . . . . . . . . . . . .34
3 Build Process and Testing
第三章 构建过程和测试
To get started with more interesting Chisel code we first need to learn how to compile Chisel programs,
how to generate Verilog code for execution in an FPGA,
and how to write tests for debugging and to verify that our circuits are correct.
在开始展开更多有意思的Chisel代码之前,我们首先需要学习怎么样去编译一个Chisel程序,
怎么样产生可以在一个FPGA中运行的Verilog代码,
而且,还要学习怎么样些测试程序来调试和验证你的电路时正确的。
Chisel is written in Scala, so any build process that supports Scala is possible with a Chisel project.
Chisel库时用Scala写的,所以任何支持构建Scala项目的过程都能够构建Chisel项目。
One popular build tool for Scala is sbt, which stands for the Scala interactive build tool.
一个比较流行的构建Scala的工具时sbt,它可以替代交互式的Scala构建工具。
Besides driving the build and test process, sbt also downloads the correct version of Scala and the Chisel libraries.
除了能够主导构建和测试过程,sbt还能够下载正确的Scala和Chisel库的版本。
3.1 Building your Project with sbt
3.1 使用sbt构建你的项目
The Scala library that represents Chisel and the Chisel testers are automatically downloaded
during the build process from a Maven repository.
实现了Chisel和Chisel Tester的Scala库,在构建过程中,会自动从Maven 仓库下载下来。
The libraries are referenced by build.sbt.
这些库会在build.sbt中被引用。
It is possible to configure build.sbt with latest.release to always use the most actual version of Chisel.
把build.sbt配置成latest.release 这样可以总是使用最新版本的Chisel。
However, this means on each build the version is looked up from the Maven repository.
然而,这个意思是说,每次构建,都要从Maven仓库查找最新的版本。
This lookup needs an Internet connection for the build to succeed.
这样的话,在构建过程中,构建的机器需要连在因特网上才能成功构建。
Better use a dedicated version of Chisel and all other Scala libraries in your build.sbt.
更好的方式是使用一个专门的Chisel版本,而且其他的Scala库放在你的build.sbt文件中。
Maybe sometimes it is also good to be able to write hardware code and test it without an Internet connection.
可能在有些时候,离线编写硬件代码并且测试它是不错的尝试。
For example, it is cool to do hardware design on a plane.
例如,在飞机飞上天后,在很高的水平上,喝着飞机上巴黎水瓶,同时进行硬件设计是挺酷的吧。
3.1.1 Source Organization
3.1.1 源码组织
sbt inherits the source convention from the Maven build automation tool. Maven also
organizes repositories of open-source Java libraries.1
sbt 继承了Maven自动构建工具的习惯。Maven以用来管理开源java库的仓库。
Figure 3.1 shows the organization of the source tree of a typical Chisel project. The
root of the project is the project home, which contains build.sbt.
Figure 3.1 展示了一个典型的Chisel项目的源码树的组织方式。源码的根节点是项目的home,这里也保存着build.sbt文件。
It may also include a Makefile for the build process, a README, and a LICENSE file. Folder src contains all source code.
这个文件夹中可能同时存放着构建过程的Makefile文件,还有一个README文件,以及一个许可证文件。
src文件夹包含着所有的项目源代码。
From there it is split between main, containing the hardware sources and test containing testers.
在这个文件夹里有飞出来了两个文件夹,main文件夹中存放了硬件的源代码,而test文件夹中存放着测试代码。
//---注解----------------------------------------
1That is also the place where you downloaded the Chisel library on your first build: https://mvnrepository.com/artifact/edu.berkeley.cs/chisel3.
如下网址是你第一次构建项目时下载Chisel的地方: https://mvnrepository.com/artifact/edu.berkeley.cs/chisel3.
//---注解----------------------------------------
Figure 3.1: Source tree of a Chisel project (using sbt)
Figure 3.1:使用sbt构建的Chisel项目的源码树
Chisel inherits from Scala, which inherits from Java the organization of source in packages.
chisel继承自Scala,而Scala又继承了Java用package组织源码的方式。
Packages organize your Chisel code into namespaces. Packages can also contain sub-packages.
一些Package 把你的Chisel源码组织进一些命名空间。包还可以包含子包。
The folder target contains the class files and other generated files.
target文件夹包含了class类型的文件,以及其他的生成文件。
I recommend to also use a folder for generated Verilog files, which is usually call generated.
我推荐对于生成的Verilog代码文件也要用一个单独的文件夹来保存,一般取文件夹名字为generated。
To use the facility of namespaces in Chisel,
you need to declare that a class/module is defined in a package, in this example in mypacket:
为了使用Chisel中的命名空间设施,你需要声明,一个类或者module 时定义在一个package中的,在这个示例里是在mapacket中声明的:
package mypack
import chisel3._
class Abc extends Module {
val io = IO(new Bundle{})
}
Note that in this example we see the import of the chisel3 packet to use Chisel classes.
注意,在这个示例中,我们导入了chisel3的包来使用Chisel的类。
To use the module Abc in a different context (packet name space), the components of packet mypacket need to be imported.
为了在一个不同的上下文中使用刚刚定义的这个Abc module,这个包需要被导入到那个上下文中。
The underscore (_) acts as wildcard, meaning that all classes of mypacket are imported.
这里的下划线是作为通配符,表示导入了mypacket中的所有类。
import mypack._
class AbcUser extends Module {
val io = IO(new Bundle{})
val abc = new Abc()
}
It is also possible to not import all types from mypacket, but use the fully qualified name
mypack.Abc to refer to the module Abc in packet mypack.
不导入包中的全部类型也是可能的,但是这时候需要使用全称,maypack.Abc,来引用mypack这个包中特定Abc module,例如:
class AbcUser2 extends Module {
val io = IO(new Bundle{})
val abc = new mypack.Abc()
}
It is also possible to import just a single class and create an instance of it:
也可以只引入包中的一个类,并且创建这个类的实例对象,例如:
import mypack.Abc
class AbcUser3 extends Module {
val io = IO(new Bundle{})
val abc = new Abc()
}
3.1.2 Running sbt
3.1.2 运行sbt
A Chisel project can be compiled and executed with a simple sbt command:
可以只用一个简单的命令sbt来编译并运行一个Chisel项目,如下:
$ sbt run
This command will compile all your Chisel code from the source tree and searches
for classes that contain an object that includes a main method, or simpler that extends App.
这个命令将会编译你的源码树中的全部Chisel代码,并且寻找那些包含了一个main方法的类的对象,或者简单地继承了App类的对象。
If there is more than one such object, all objects are listed and one can be selected.
如果这里存在着不止一个这样的对象,那么,所有的对象都会被罗列出来,但是只有一个可以被选中。
You can also directly specify the object that shall be executed as a parameter to sbt:
你也可以直接指定这个对象,这时候需要在命令行参数中指定这个对象,例如:
$ sbt "runMain mypacket.MyObject"
Per default sbt searches only the main part of the source tree and not the test part.2
sbt默认的搜索,旨在main源码树里进行,不会默认跑到test源码树中。
However, Chisel testers, as described here, contain a main, but shall be placed in the test part of the source tree.
然而,Chiseltester,正如这里所描述的,包含了一个main方法,但是,应该放在源码树的test部分。
//---注解------------------------------------
2This is a convention form Java/Scala that the test folder contains unit tests and not objects with a main.
这是一个从Java/Scala流传下来的习俗,在test文件夹里存访单元测试,但是不存放带有main方法的object。
//---注解------------------------------------
To execute a main in the tester tree use following sbt command:
运行一个tester源码树中的main方法,要使用如下的sbt命令:
$ sbt "test:runMain mypacket.MyTester"
Now that we know the basic structure of a Chisel project and how to compile and run
it with sbt, we can continue with a simple testing framework.
到现在,我们已经知道Chisel项目的基本结构,以及如何使用sbt命令编译和运行它,
我们可以通过一个简单的测试框架来继续讲解Chisel的知识。
3.1.3 Tool Flow
3.1.3 涉及到工具链流程
Figure 3.2 shows the tool flow of Chisel.
Figure 3.2 展示了这个Chisel的工具流程
The digital circuit is described in a Chisel class shown as Hello.scala.
就像在第一章的练习中的Hello.scala中的代码那样,数字逻辑电路是使用一个Chisel类来描述的。
The Scala compiler compiles this class, together with the Chisel and Scala libraries,
and generates the Java class Hello.class that can be executed by a standard Java virtual machine (JVM).
Scala的编译器把这个类跟Chisel和Scala的库编译到一起,生成了Hello.class这个可以运行在JVM中的Java类。
Executing this class with a Chisel driver generates the so-called flexible intermediate representation
for RTL (FIRRTL), an intermediate representation of digital circuits.
用Chisel驱动来执行这个类,将会生成所谓的FIRRTL文件,这是一种数字电路的中间表示语言。
In our example the file is Hello.fir.
在我们的示例中,它是Hello.fir文件。
The FIRRTL compiler performs transformations on the circuit.
FIRRTL编译器对电路实施了转换。
Treadle is a FIRRTL interpreter to simulate the circuit.
Treadle是一个FIRRTL解释器,可以仿真这个电路。
Together with the Chisel tester it can be used to debug and test Chisel circuits.
与Chisel tester一道,它可以对Chisel电路进行调试和测试。
With assertions we can provide test results.
在assertion表达式里,我们可以提供测试结果判断。
Treadle can also generate waveform files (Hello.vcd) that can be viewed with a waveform viewer
(e.g., the free viewer GTKWave or Modelsim).
Treadle 还能够产生波形文件(Hello.vcd),这样的文件可以使用波形查看软件进行观察(例如免费自由的GTKWave或者ModelSim)。
One FIRRTL transformation, the Verilog emitter, generates Verilog code for synthesis (Hello.v).
一次FIRRTL转换,一个Verilog生成器,能够产生Verilog代码来进行综合(Hello.v)。
A circuit synthesize tool (e.g., Intel Quartus, Xilinx Vivado, or an ASIC tool) synthesizes the circuit.
用一个电路综合工具就可以综合成实际的电路版图了(例如:Intel Quartus, Xilinx Vivado, 或者一个ASIC 工具)。
In an FPGA design flow, the tool generates the FPGA bitstream that is used to configure the FPGA, e.g., Hello.bit.
一个FPGA设计流程中,综合工具生成了FPGA比特流,可以用来配置FPGA,文件名是Hello.bit。
3.2 Testing with Chisel
3.2 用Chisel进行测试
Tests of hardware designs are usually called test benches.
对硬件设计的测试,通常成为test benches。
The test bench instantiates the design under test (DUT), drives input ports, observes output ports,
and compares them with expected values.
测试基台实例化这个设计(DUT),驱动输入端口,观察输出端口的值。
3.2.1 PeekPokeTester
3.2.1 PeekPokeTester
Chisel provides test benches in the form of a PeekPokeTester.
Chisel提供的测试基台的形式是PeekPokeTester。
One strength of Chisel is that it can use the full power of Scala to write those test benches.
Chisel一个牛逼的地方在于,它可以利用Scala全部的功能来些它的测试基台。
One can, for example, code the expected functionality of the hardware in a software simulator and compare the
simulation of the hardware with the software simulation.
一个设计者,比如,可以在一个软件仿真器中编写出硬件的功能,然后对这个硬件设计与这个软件仿真进行对比。
This method is very efficient when testing an implementation of a processor [6].
这个方法对于测试一个处理器的实现是非常有效的。
Figure 3.2: Tool flow of the Chisel ecosystem.
Figure 3.2:Chisel生态系统的工具流
To use the PeekPokeTester, following packages need to be imported:
如果想使用PeekPlkeTester,那么下面这些包是要导入到代码中的:
import chisel3._
import chisel3.iotesters._
Testing a circuit contains (at least) three components:
(1) the device under test (often called DUT),
(2) the testing logic, also called test bench, and
(3) the tester objects that contains the main function to start the testing.
对一个电路做测试的过程,至少包含三个组件:
(1)设计师所设计的功能电路;
(2)进行测试的逻辑,也称为测试基台;
(3)测试对象,它包含一个main方法来启动整个测试流程。
The following code shows our simple design under test.
下面的代码展示了我们简单的设计电路待测模块。
It contains two input ports and one output port, all with a 2-bit width.
它包含了两个输入端口和一个输出端口,每个端口都是2比特宽。
The circuit does a bit-wise AND to it returns on the output:
这个电路做一个按位与的逻辑运算,并且将结果输出到输出端口,代码如下:
class DeviceUnderTest extends Module {
val io = IO(new Bundle {
val a = Input(UInt(2.W))
val b = Input(UInt(2.W))
val out = Output(UInt(2.W))
})
io.out := io.a & io.b
}
The test bench for this DUT extends PeekPokeTester and has the DUT as a parameter for the constructor:
这个测试基台继承了PeekPokeTester,而且把DUT作为构造函数的一个参数:
class TesterSimple(dut: DeviceUnderTest) extends
PeekPokeTester(dut) {
poke(dut.io.a, 0.U)
poke(dut.io.b, 1.U)
step(1)
println("Result is: " + peek(dut.io.out).toString)
poke(dut.io.a, 3.U)
poke(dut.io.b, 2.U)
step(1)
println("Result is: " + peek(dut.io.out).toString)
}
A PeekPokeTester can set input values with poke() and read back output values with peek().
PeekPokeTester类可以用方法poke()来设置输入信号的值,并且用peek()读回输出信号的值。
The tester advances the simulation by one step (= one clock cycle) with step(1).
测试器通过step(1)函数来推进仿真器前进一个时钟周期。
We can print the values of the outputs with println().
我们可以用println()函数来输出output信号的值。
The test is created and run with the following tester main:
这个测试类被下面的main方法创建并运行:
object TesterSimple extends App {
chisel3.iotesters.Driver(() => new DeviceUnderTest()) { c =>
new TesterSimple(c)
}
}
When you run the test, you will see the results printed to the terminal (besides other information):
当你运行这个测试时,你将会看到测试结果打印到终端里(还有一些其他信息省掉了):
[info] [0.004] SEED 1544207645120
[info] [0.008] Result is: 0
[info] [0.009] Result is: 2
test DeviceUnderTest Success: 0 tests passed in 7 cycles
taking 0.021820 seconds
[info] [0.010] RAN 2 CYCLES PASSED
We see that 0 AND 1 results in 0; 3 AND 2 results in 2.
我们看到 0与上1的结果是0;3与上2的结果是2.
Besides manually inspecting printouts, which is an excellent starting point,
we can also express our expectations in the test bench itself with expect(),
having the output port and the expected value as parameters.
除了可以手工检查输出,当然这是一个非常重卓越的起点,我们也可以写出我们期望的输出,
然后让测试基台用expect()函数自己测试,这需要使用输出端口和期待的值作为参数。
The following example shows testing with expect():
下面的例子展示了使用expect()函数进行测试的过程:
class Tester(dut: DeviceUnderTest) extends PeekPokeTester(dut) {
poke(dut.io.a, 3.U)
poke(dut.io.b, 1.U)
step(1)
expect(dut.io.out, 1)
poke(dut.io.a, 2.U)
poke(dut.io.b, 0.U)
step(1)
expect(dut.io.out, 0)
}
Executing this test does not print out any values from the hardware,
but that all tests passed as all expect values are correct.
执行这个测试过程时,不会从硬件打印任何输出,但是所有的测试都成功通过了,因为所有期待的值都是正确的。
[info] [0.001] SEED 1544208437832
test DeviceUnderTest Success: 2 tests passed in 7 cycles
taking 0.018000 seconds
[info] [0.009] RAN 2 CYCLES PASSED
A failed test, when either the DUT or the test bench contains an error,
produces an error message describing the difference between the expected and actual value.
如果一个测试失败了,那么可能是DUT中包含一个错误,或者是测试基台中包含一个错误。
In the following, we changed the test bench to expect a 4, which is an error:
在下面的测试中,我们把测试基台的期待数据改成了4,这是不对的:
[info] [0.002] SEED 1544208642263
[info] [0.011] EXPECT AT 2 io_out got 0 expected 4 FAIL
test DeviceUnderTest Success: 1 tests passed in 7 cycles
taking 0.022101 seconds
[info] [0.012] RAN 2 CYCLES FAILED FIRST AT CYCLE 2
In this section, we described the basic testing facility with Chisel for simple tests.
在这一节中,我们描述了基本的测试设施,可以用于简单的测试。
However, in Chisel, the full power of Scala is available to write testers.
然而, 在Chisel中, Scala的全部功能都可以用于写测试。
3.2.2 Using ScalaTest
3.2.2 使用ScalaTest做Chisel测试
ScalaTest is a testing tool for Scala (and Java), which we can use to run Chisel testers.
ScalaTest是一个用于Scala和Java的测试工具,这里我们使用它做Chisel测试。
To use it, include the library in your build.sbt with the following line:
把如下配置信息写进build.sbt文件中:
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test"
Tests are usually found in src/test/scala and can be run with:
Test程序源码通常可以在 src/test/scala 文件夹下找到,我们可以运行如下命令:
$ sbt test
A minimal test (a testing hello world) to test a Scala Integer addition:
一个最小的测试 (测试中的Hello world) 来测试Scala中的整数加法:
import org.scalatest._
class ExampleSpec extends FlatSpec with Matchers {
"Integers" should "add" in {
val i = 2
val j = 3
i + j should be (5)
}
}
Although Chisel testing is more heavyweight than unit testing of Scala programs,
we can wrap a Chisel test into a ScalaTest class.
虽然比上Scala程序设计中的单元测试,Chisel的测试更加重量级,但是我们可以把Chisel的测试包装进ScalaTest的一个类里面。
For the Tester shown before this is:
之前的测试可以这么做:
class SimpleSpec extends FlatSpec with Matchers {
"Tester" should "pass" in {
chisel3.iotesters.Driver(() => new DeviceUnderTest()) { c =>
new Tester(c)
} should be (true)
}
}
The main benefit of this exercise is to be able to run all tests with a simple sbt test
(instead of a running main). You can run just a single test with sbt, as follows:
这个实例的主要好处是能够在一个简单的sbt 测试中运行所有的测试。你可以用sbt命令运行单个测试,如下:
$ sbt "testOnly SimpleSpec"
3.2.3 Waveforms
3.2.3 波形
Testers, as described above, work well for small designs and for unit testing,
as it is common in software development.
上面的测试方式,对于小型设计和单元测试是不错的,正如软件开发中的做法一样。
A collection of unit tests can also serve for regression testing.
把单元测试集合起来,可以用于回归测试。
However, for debugging more complex designs, one would like to investigate several signals at once.
然而,对于调试复杂的设计,设计者需要同时查看几路信号。
A classic approach to debug digital designs is displaying the signals in a waveform.
调试一个数字逻辑设计的传统方法是显式信号的波形。
In a waveform the signals are displayed over time.
用波形的方式,显式所有的信号随着时间的变化值。
Chisel testers can generate a waveform that includes all registers and all IO signals.
Chisel 测试能够产生所有寄存器和IO信号的波形。
In the following examples we show waveform testers for the DeviceUnderTest
from the former example (the 2-bit AND function).
下面的示例中,我们用波形测试器来测试前面示例中DUT波形,就是那个2比特的与门电路。
For the following example we import following classes:
在下面的示例中,我们导入如下的类库:
import chisel3.iotesters.PeekPokeTester
import chisel3.iotesters.Driver
import org.scalatest._
We start with a simple tester that pokes values to the inputs and advances the clock with step.
在这个简单的测试中,我们调用一些poke函数来提供输入信号的值,并用step函数来推动时钟前进。
We do not read any output or compare it with expect.
我们并不读取输入信号的值,也不将其跟期待值做比较。
class WaveformTester(dut: DeviceUnderTest) extends
PeekPokeTester(dut) {
poke(dut.io.a, 0)
poke(dut.io.b, 0)
step(1)
poke(dut.io.a, 1)
poke(dut.io.b, 0)
step(1)
poke(dut.io.a, 0)
poke(dut.io.b, 1)
step(1)
poke(dut.io.a, 1)
poke(dut.io.b, 1)
step(1)
}
Instead we call Driver.execute with parameters to generate waveform files (.vcd files).
而是调用带参数的Dirver.execute函数,以便生成波形文件(.vcd文件)
class WaveformSpec extends FlatSpec with Matchers {
"Waveform" should "pass" in {
Driver.execute(Array("--generate -vcd-output", "on"), () =>
new DeviceUnderTest()) { c =>
new WaveformTester(c)
} should be (true)
}
}
You can view the waveform with the free viewer GTKWave or with ModelSim.
你可以用free的GTKWave波形查看器或者ModelSim来查看这些vcd文件。
Start GTKWave and select File – Open New Window and navigate to the folder where the Chisel tester put the .vcd file.
操作步骤:在GTKWave的"File"–> "Open New Window",导航到存储了Chisel 测试器输出.vcd波形文件的文件夹。
Per default the generated files are in test run dir then the name of the tester appended with a number.
按照默认设置,这些生成的波形文件都在测试运行的文件夹里,而且它的名字末尾上都附有一个数字。
Within this folder you should be able to find DeviceUnderTest.vcd.
在这个文件夹里你应该能够找到DeviceUnderTest.vcd
You can select the signals from the left side and drag them into the main window.
你也可以选择把波形文件拖拽到主窗口中查看他。
If you want to save a configuration of signals you can do so with File – Write Save File
and load it later with File – Read Save File.
如果你想保存信号的一个配置,你可以通过 "File" –> "Write Save File"来保存,并且时候通过 "File" ->"Read Save File"来重新加载。
Explicitly enumerating all possible input values does not scale.
手工显式地枚举所有可能的输入值是不可行的。
Therefore, we will use some Scala code to drive the DUT.
所以我们将使用一些Scala代码来驱动DUT。
Following tester enumerates all possible values for the 2 2-bit input signals.
下面的测试器用Scala的for循环枚举了所有可能的2个2bite输入信号的值。
class WaveformCounterTester(dut: DeviceUnderTest) extends
PeekPokeTester(dut) {
for (a <- 0 until 4) {
for (b <- 0 until 4) {
poke(dut.io.a, a)
poke(dut.io.b, b)
step(1)
}
}
}
We add a ScalaTest spec for this new tester
我们添加了一个ScalaTest的spec类,来进行这个新的测试器:
class WaveformCounterSpec extends FlatSpec with Matchers {
"WaveformCounter" should "pass" in {
Driver.execute(Array("--generate -vcd-output", "on"), () =>
new DeviceUnderTest()) { c =>
new WaveformCounterTester(c)
} should be (true)
}
}
and execute it with
使用下面的命令运行它:
sbt "testOnly WaveformCounterSpec"
3.2.4 printf Debugging
3.2.4 打印调试
Another form of debugging is the so-called “printf debugging”.
还有一种调试方式是打印调试。
This form comes from simply putting printf statements in C code
to print variables of interest during the execution of the program.
这个方式来源于用类似C语言中的printf函数,在程序运行时,将感兴趣的变量值打印输出。
This printf debugging is also available during testing of Chisel circuits.
这种打印调式亦可以用于测试Chisel电路设计。
The printing happens at the rising edge of the clock.
打印发生在时钟的上升沿。
A printf statement can be inserted just anywhere in the module definition,
as shown in the printf debugging version of the DUT.
一个printf语句可以添加到module定义的任何位置,就如同下面这个打印调试版的DUT这样子:
class DeviceUnderTestPrintf extends Module {
val io = IO(new Bundle {
val a = Input(UInt(2.W))
val b = Input(UInt(2.W))
val out = Output(UInt(2.W))
})
io.out := io.a & io.b
printf("dut: %d %d %d\n", io.a, io.b, io.out)
}
When testing this module with the counter based tester, which iterates over all possible values,
we get following output, verifying that the AND function is correct:
当运行这个基于计数器的测试器时,它将迭代覆盖所有可能的输入值,我们回获得如下的输出值,这也证明了这个与门函数是正确的:
Circuit state created
[info] [0.001] SEED 1579707298694
dut: 0 0 0
dut: 0 1 0
dut: 0 2 0
dut: 0 3 0
dut: 1 0 0
dut: 1 1 1
dut: 1 2 0
dut: 1 3 1
dut: 2 0 0
dut: 2 1 0
dut: 2 2 2
dut: 2 3 2
dut: 3 0 0
dut: 3 1 1
dut: 3 2 2
dut: 3 3 3
test DeviceUnderTestPrintf Success: 0 tests passed in 21 cycles
taking 0.036380 seconds
[info] [0.024] RAN 16 CYCLES PASSED
Chisel printf supports C and Scala style formatting.
Chisel的printf函数,及支持C的格式参数,也支持Scala的。
3.3 Exercises
3.3 练习
For this exercise, we will revisit the blinking LED from chisel-examples and explore Chisel testing.
在这个练习中,我们将会重温第一章中的练习,也就是闪烁的LED电路,以此来探索和巩固Chisel测试的过程。
3.3.1 A Minimal Project
3.3.1 一个最小的项目
First, let us find out what a minimal Chisel project is.
首先,让我们试着找出一个最小的Chisel项目会是什么样的。
Explore the files in the Hello World example. The Hello.scala is the single hardware source file.
慢慢探索一下Hello World示例中的这些文件。其中,Hello.scala是一个单独的硬件设计源文件。
It contains the hardware description of the blinking LED (class Hello) and an App that generates the Verilog code.
它包含了描述让LED闪烁的硬件电路,这在Hello类中;还包含了一个继承了App特质的对象来生成Verilog代码。
Each file starts with the import of Chisel and related packages:
每个文件都开始于导入Chisel和相关的package:
import chisel3._
Then follows the hardware description, as shown in Listing 1.1.
导入包之后,接下来是硬件描述,就如同代码列表1.1中所示。
To generate the Verilog description, we need an application.
要生成Verilog描述代码,我们需要要给应用程序(Scala中包含main方法的对象。)
A Scala object that extends App is an application
that implicitly generates the main function where the application starts.
继承了App特质的一个Scala对象就是一个应用程序,当程序启动时它隐式地产生一个main函数。
The only action of this application is to create a new HelloWorld object
and pass it to the Chisel driver execute function.
这个应用程序的唯一动作就是创建一个HeloWorld对象,并且把它传递到Chisel driver的execute()函数。
The first argument is an array of Strings, where build options can be set (e.g., the output folder).
第一个参数是要给字符串数组,其中包含了一些运行选项的设置,比如输出文件夹是哪个等。
The following code will generate the Verilog file Hello.v.
下面的代码将会生成那个Verilog代码,Hello.v文件。
object Hello extends App {
chisel3.Driver.execute(Array[String](), () => new Hello())
}
Run the generation of the example manually with
可以手动地运行这个生成代码的程序,如下:
$ sbt "runMain Hello"
and explore the generated Hello.v with an editor.
请用一个文本编辑器打开生成的Hello.v文件。
The generated Verilog code may not be very readable, but we can find out some details.
机器生成的Verilog代码的可读性可能不太好,但是我们还是很容易发现一些重要细节。
The file starts with a module Hello, which is the same name as our Chisel module.
在文件的开始出有一个命名为Hello的module,这个Chisel中的module是同名的。
We can identify our LED port as output io_led. Pin names are the Chisel names with a prepended io .
我们可以看出来对应我们的LED的输出端口 io_led. 管脚的名字和Chisel中的名字一样,都是用io_作为前缀的。
Besides our LED pin, the module also contains clock and reset input signals.
除了我们的LED驱动管脚,模块还包含了时钟和重置输入信号。
Those two signals are added automatically by Chisel.
这两个信号是由Chisel自动加入的。
Furthermore, we can identify the definition of our two registers cntReg and blkReg.
而且,我们可以找到我们定义的两个寄存器,cntReg和blkReg(前者用于计时,后者用于寄存led的开关状态)。
We may also find the reset and update of those registers at the end of the module definition.
在module定义的末尾,我们能够找到寄存器的重置代码和更新代码。
Note, that Chisel generates a synchronous reset.
注意,Chisel自动生成的置位功能是同步置位。
For sbt to be able to fetch the correct Scala compiler and the Chisel library, we need a build.sbt:
为了让sbt能够取回正确的Scala编译器和正确的Chisel库,我们需要如下内容的build.sbt文件:
scalaVersion := "2.11.7"
resolvers ++= Seq(
Resolver.sonatypeRepo("snapshots"),
Resolver.sonatypeRepo("releases")
)
libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "3.2.2"
libraryDependencies += "edu.berkeley.cs" %% "chisel -iotesters" % "1.3.2"
Note that in this example, we have a concrete Chisel version number to avoid checking on each run for a new version
(which will fail if we are not connected to the Internet, e.g., when doing hardware design during a flight).
注意,在这个示例中,我们采用了一个固定的Chisel版本号来避免每次运行的时候都要取查找新的版本
(如果我们没有联网的话,将会运行失败,比如,一个工程师在飞机上做硬件设计的时候,特别是做战斗机从别墅到公司的过程)。
Change the build.sbt configuration to use the latest Chisel version by changing the library dependency to
通过修改build.sbt中的库的依赖配置,来获得最新版本的Chisel,如下:
libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "latest.release"
and rerun the build with sbt. Is there a newer version of Chisel available and will it be automatically downloaded?
然后使用sbt重新运行这个构建过程。看一看,这是是否有一个更新的Chisel库的版本可以获得,它会被自动下载下来么?
For convenience, the project also contains a Makefile. It just contains the sbt command,
so we do not need to remember it and can generate the Verilog code with:
为了便利,这个项目中还包含了一个Make file文件。它只是包含了sbt命令,
这样我们不需要记住sbt命令,就能够生成Verilog代码,只要执行make,如下:
make
Besides a README file, the example project also contains project files for different FPGA board.
除了一个README文件之外,示例工程中还包含了针对不同FPGA板子的项目文件。
E.g., in quartus/altde2-115 you can find the two project files to define a Quartus project for the DE2-115 board.
例如,在文件夹 quartus/altde2-115 文件夹中,你能够找到两个项目文件,它定义了一个针对DE2-115板子的Quartus项目。
The main definitions (source files, device, pin assignments) can be found in a plain text file hello.qsf.
主要的定义信息可以从文件hello.qsf中看到,只需要用一个纯文本编辑器打开就可以看到,里面有源文件、设备和管脚等很多赋值。
Explore the file and find out which pins are connected to which signals.
查看这个文件,找找看,哪个管脚接到了哪个信号上了。
If you need to adapt the project to a different board, there is where the changes are applied.
如果你需要改变这个项目到不同的板子上,
If you have Quartus installed, open that project, compile with the green Play button, and then configure the FPGA.
如果你的机器上安装了Quartus,那么打开这个项目,用绿色的Play按钮编译项目,然后配置你的FPGA板子。
Note that the Hello World is a minimal Chisel project.
注意,这个Hello World是一个最小的Chisel项目。
More realistic projects have their source files organized in packages and contain testers.
更加实际的项目回把他们的源文件组成到package中,并且会包含tester。
The next exercise will explore such a project.
下面的练习将会查看一个这样的项目。
3.3.2 A Testing Exercise
3.3.2 一个测试练习
In the last chapter’s exercise, you have extended the blinking LED example with some
input to build an AND gate and a multiplexer and run this hardware in an FPGA.
在上一章的练习中,你已经扩展了第一章的让LED闪烁的示例,添加了一些输入信号,构建了一个与门和一个多路器,并且在FPGA上运行起来。
We will now use this example and test the functionality with a Chisel tester to automate testing
and also to be independent of an FPGA.
现在我们将使用这个示例,并且用Chisel测试器自动化地测试电路的功能,这是独立于具体FPGA的。
Use your designs from the previous chapter and add a Chisel tester to test the functionality.
使用上一章的设计结果,然后添加Chisel测试器来测试电路功能。
Try to enumerate all possible inputs and test the output with except().
试着枚举左右可能的输入,并且用except()函数测试输出值。
Testing within Chisel can speed up the debugging of your design.
在Chisel层面做测试可以提高你的设计的调试速度。
However, it is always a good idea to synthesize your design for an FPGA and run tests with the FPGA.
当然了,用综合工具针对一个FPGA综合你的设计,并且在FPGA上跑起来,永远是个好主意。
There you can perform a reality check on the size of your design (usually in LUTs and
flip-flops) and your performance of your design in maximum clocking frequency.
这样你可以对自己的设计做一个实际的检查,看看有多少LUT和触发器,并且可以知道你的设计在最大的时钟频率下的性能。
As a reference point, a textbook style pipelined RISC processor may consume about 3000
4-bit LUTs and may run around 100 MHz in a low-cost FPGA (Intel Cyclone or Xilinx Spartan).
作为要给参考,一个教科书风格的RISC处理器大概要消耗3000个4比特的LUT,而且在低成本的FPGA上可以跑到大概100MHz,比如在Intel Cyclone or Xilinx Spartan上。