Featured image of post unofficial environment in the lab of computer organization

unofficial environment in the lab of computer organization

using chisel in the lab

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 Mainobject 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 打开波形仿真文件

1
gtkwave gcd.vcd

使用 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