preface
In the fall semester of my sophomore year,
I took a required course,
named Digital Logic Design,
and the lab in this course was writing verilog, an hdl,
which was very torturous.
I knew Computer Organization in the spring semester of my sophomore year,
and the CPU Design project for the summer semester
are plus version of Digital Logic Design,
so I wanted to escape from the plight.
Then, during the that winter vaction, I learned UVM,
and at the same time
I learned the language of chisel.
Any sufficiently advnaced technology is indistinguishable from magic.
(先进的科技无异于魔法)
环境
推荐使用 unix-like OS, 需要安装的软件:
sbt/mill (二选一即可, 当然也可以都安装), verilator
- sbt, mill 在 scala 中的地位, 就像是 cmake/meson 之于 c++, 都是组织代码的工具. 与 c++ 不同的是: sbt/mill 会帮你下载 scala.
- verilator 是用于仿真的, 下面再进一步介绍
上面是核心软件, 当然啦, sbt/mill 依赖于 openjdk;
verilator 依赖于 c++ 环境, bison, flex … 这些依赖请自行安装.
不同的 unix-like OS 有不同的安装方法, 请根据自己的 OS 来安装, 这里就不枚举了.
Tips: 可以先不急着装环境, 我在 chisel-verilator-example 中提供了 devcontainer.
example
下面这个例子可以在:这里, chisel-verilator-example找到
创建 Chisel 项目
Chisel 是 Scala 的一个库并且目前为止并没有一个真正的 Chisel IDE。我们可以通过官方的 Chisel-template 创建我们的项目。
1
|
git clone https://github.com/chipsalliance/chisel-template.git <my-chisel-project>
|
编写 Chisel 代码
chisel-template 项目下天然的提供了一个 GCD 硬件 module。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
//# chisel-verilator-example/src/main/scala/gcd/GCD.scala
package gcd
import Chisel3._
// _root_ disambiguates from package Chisel3.util.circt if user imports Chisel3.util._
import _root_.circt.stage.ChiselStage
/**
* Compute GCD using subtraction method.
* Subtracts the smaller from the larger until register y is zero.
* value in register x is then the GCD
*/
class GCD extends Module {
val io = IO(new Bundle {
val value1 = Input(UInt(16.W))
val value2 = Input(UInt(16.W))
val loadingValues = Input(Bool())
val outputGCD = Output(UInt(16.W))
val outputValid = Output(Bool())
})
val x = Reg(UInt())
val y = Reg(UInt())
when(x > y) { x := x - y }
.otherwise { y := y - x }
when(io.loadingValues) {
x := io.value1
y := io.value2
}
io.outputGCD := x
io.outputValid := y === 0.U
}
/**
* Generate Verilog sources and save it in file GCD.v
*/
object GCD extends App {
ChiselStage.emitSystemVerilogFile(
new GCD,
args = Array("--target-dir", "generated"),
firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info")
)
}
|
上面这个 class GCD extends Module
继承了 Chisel 的 Module 抽象类。在这个类里面,我们实现了 GCD 模块。
对应的也就是 Verilog 中的 module GCD();
。在这个 class GCD
中,我们描述了 GCD 的行为。
除了一个 class GCD
,下面还有一个 object GCD
单例。这个单例继承了 App, App 相当于是 Scala 中的 class Main
。
object GCD
中,我们执行了 sv file 的发射。然后我们可以通过执行 sbt "runMain gcd.GCD"
运行 sv file 的发射。
最后,我们就可以在 generated 目录下看到对应的 GCD.sv 文件啦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
//# chisel-verilator-example/generated/GCD.sv
// Generated by CIRCT firtool-1.62.0
module GCD(
input clock,
reset,
input [15:0] io_value1,
io_value2,
input io_loadingValues,
output [15:0] io_outputGCD,
output io_outputValid
);
reg [15:0] x;
reg [15:0] y;
always @(posedge clock) begin
if (io_loadingValues) begin
x <= io_value1;
y <= io_value2;
end
else if (x > y)
x <= x - y;
else
y <= y - x;
end // always @(posedge)
assign io_outputGCD = x;
assign io_outputValid = y == 16'h0;
endmodule
|
使用 Verilator C++ 调试 Chisel 生成的 Verilog
下面是一个 C++ 下的 testbench
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
//# chisel-verilator-example/src/main/scala/gcd/GCD.scala
#include "VGCD.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
#include <cstdlib>
#include <iostream>
// 一个正确的 gcd 实现
int gcd(uint16_t a, uint16_t b)
{
while (b != 0) {
int remainder = a % b;
a = b;
b = remainder;
}
return a;
}
int main(int argc, char** argv)
{
Verilated::commandArgs(argc, argv);
Verilated::traceEverOn(true); // 启用波形跟踪
size_t fail_cnt = 0;
size_t success_cnt = 0;
auto dut = std::make_unique<VGCD>();
VerilatedVcdC* vcd = new VerilatedVcdC();
dut->trace(vcd, 99); // 设定跟踪级别
vcd->open("gcd.vcd"); // 打开VCD文件
// 重置设备
dut->reset = 1;
dut->clock = 0;
for (int i = 0; i < 5; i++) {
dut->clock = !dut->clock;
dut->eval();
vcd->dump(10 * i); // 记录时间点
}
dut->reset = 0;
srand(time(NULL));
uint16_t x = rand();
uint16_t y = rand();
// 主仿真循环
for (int cycle = 0; cycle < 400; cycle++) {
dut->io_loadingValues = (cycle == 5);
dut->io_value1 = x;
dut->io_value2 = y;
dut->clock = 1;
dut->eval();
vcd->dump(10 * cycle + 5);
dut->clock = 0;
dut->eval();
vcd->dump(10 * cycle + 10);
dut->io_loadingValues = 0;
}
// 收集结果和清理
uint16_t top_z = dut->io_outputGCD;
dut->final();
vcd->close(); // 关闭VCD文件
if (uint16_t z = gcd(x, y); z == top_z) {
std::cout << "success" << std::endl;
} else {
std::cout << "fail" << std::endl;
std::cout << "x: " << (int)x << std::endl;
std::cout << "y: " << (int)y << std::endl;
std::cout << "gcd(x, y): " << (int)z << std::endl;
std::cout << "dut(x, y): " << (int)top_z << std::endl;
}
return 0;
}
|
build cxx and Verilog with make
1
2
3
|
# $(pwd) == chisel-verilator-example
verilator -Wall --cc --trace -Iobj_dir -Wno-UNUSEDSIGNAL generated/GCD.sv --exe src/test/cxx/tb.cxx # 会生成 obj_dir 文件夹,这是将 Verilog 编译成了 C++ 的 class
make -C obj_dir -f VGCD.mk
|
这样我们就用 Verilator 编译完成了 C++ 和 Verilog。我们可以在 obj_dir 文件夹下面找到 VGCD 可执行文件。
我们可以通过 ./VGCD
执行可执行文件。执行完成以后,可以看到输出 success
字样并且在项目的根目录下生成了 gcd.vcd
文件。
我们可以使用 VSCode 的 wavetrace 插件(付费)打开 .vcd
文件。
build cxx and Verilog with cmake
我在 chisel-verilator-example 下提供了一个 CMakeLists.txt,
这与上面 build with make 本质上是一样的, 但是用了封装的更好的工具.
1
2
3
4
|
# generate makefile in build
cmake -S . -B build
# build
cmake --build build
|
拍案: cmake 做的事情是: 根据 CMakeLists.txt 生成 makefile 或者是 .ninja
,
这与 chisel 做的事情差不多: 将更好拓展的 scala 转换成 verilog.
类似的, 还有 typescript 与 javascript …
使用 GTKWave 打开波形仿真文件
使用 tcl 脚本
每次我们使用 GTKWave 调试波形图的时候。每次打开需要一个步骤:选中信号,然后 append/insert。
这很不优雅。尤其是:当我们需要频繁的调试。打开 GTKWave -> 选中信号 -> a/i -> 看波形 -> 改代码 -> make run -> 打开 GTKWave ->
选中信号 -> a/i -> 看波形 -> …
但是,实际上,“选中信号 -> a/i” 这个步骤我们可以实现的更加自动化一些 —— 编写 tcl 脚本!
实现打开 GTKWave 的时候,就将所有的信号 append/insert 到屏幕上
创建一个 load_all_waves.tcl
文件,写入以下内容,保存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# load_all_waves.tcl
# Add all signals
set nfacs [ gtkwave::getNumFacs ]
set all_facs [list]
for {set i 0} {$i < $nfacs } {incr i} {
set facname [ gtkwave::getFacName $i ]
lappend all_facs "$facname"
}
set num_added [ gtkwave::addSignalsFromList $all_facs ]
puts "num signals added: $num_added"
# Zoom full
gtkwave::/Time/Zoom/Zoom_Full
# Print
# set dumpname [ gtkwave::getDumpFileName ]
# gtkwave::/File/Print_To_File PDF {Letter (8.5" x 11")} Minimal $dumpname.pdf
|
然后我们可以通过下面这个命令打开.tcl
和.vcd
1
2
3
4
|
# 可以通过 gtkwave -h 知道参数的含义:
# -S, --script=FILE. specify Tcl command script file for execution
# -f, --dump=FILE. specify dumpfile name
gtkwave -S load_all_waves.tcl -f gcd.vcd
|
后记
devcontainer
https://containers.dev
这是一段 youtube 上 vscode 官方的视频.
more example about cmake/make of verilator
https://github.com/verilator/verilator/tree/master/examples
scala tutorial
https://docs.scala-lang.org/zh-cn/
chisel tutorial
https://www.chisel-lang.org/docs/cookbooks/cookbook
https://github.com/schoeberl/chisel-book
CSE 228A - Agile Hardware Design, Winter 2024, UC Santa Cruz
verilator
https://itsembedded.com/dhd_list/
chisel-verilator-example
https://github.com/KINGFIOX/chisel-verilator-example