计算机组成原理实验 2 环境指南(非官方)
以下指南适合有 Linux/macOS 环境的同学使用。
笔者心态:对 Verilog 感到不适,并且想折腾一些花样。
实验需要的工具
- Verilator
- Chisel
- GTKWave
安装 C/C++ 编译环境
实验工具安装需要
- gcc
- git
- g++
- make
通过以下命令可以安装
# Debian/Ubuntu
sudo apt install build-essential git device-tree-compiler
# Fedora
sudo dnf install build-essential git device-tree-compiler
如果配置环境过程中出现 "command not found." 可能是有依赖的工具没装,此时可以利用搜索引擎。
macOS 需要安装 Homebrew 作为包管理器,请查阅 Tuna Mirrors 的安装指南 。
安装 Verilator
使用包管理器安装:
# Debian/Ubuntu
sudo apt install verilator
# Fedora/CentOS
sudo dnf install verilator verilator-devel
# macOS
brew install verilator
但是有一些 Linux 的发行版,他会将包拆的比较奇怪,导致没法找到 verilated.mk
之类的情况发生。
亦或者是版本比较老,则可以使用编译安装:
# 先 cd 到一个文件夹中,推荐 cd 到 $HOME/Downloads
git clone https://github.com/verilator/verilator
cd verilator
autoconf # 生成 ./configure 文件
./configure --prefix=$HOME/app/verilator # 配置 makefile 和安装路径
make -j `nproc` # 多核编译
make install # 这会安装到 $HOME/app/verilator 中
echo 'export verilator_ROOT=$HOME/app/verilator' >> .bashrc # 追加环境变量
如果想要其他支持,请查阅 Verilator 的安装文档 。
Verilator 在编译过程中,需要用到 GNU 方言,因此推荐使用 Linux。
使用 SDKMAN 安装 Scala/Java/sbt
SDKMAN 是一个用于管理 Java 相关开发环境的软件。可以通过下面这个命令安装:
curl -s "https://get.sdkman.io" | bash
安装过程中,注意看提示,会要求在安装完成后配置环境变量。
通过 SDKMAN 安装 Scala/Java/sbt:
sdk install java
sdk install scala 2.12.13
sdk install sbt
安装 GTKWave
# Debian/Ubuntu
sudo apt install gtkwave
# Fedora
sudo dnf install gtkwave
macOS 请看 附录:macOS-安装-GTKWave
一个 Chisel 与 Verilator 结合的例子
下面这个例子可以在 这里 找到。
创建 Chisel 项目
Chisel 是 Scala 的一个库并且目前为止并没有一个真正的 Chisel IDE。我们可以通过官方的 Chisel-template 创建我们的项目。
git clone https://github.com/chipsalliance/chisel-template.git <my-chisel-project>
编写 Chisel 代码
chisel-template 项目下天然的提供了一个 GCD 硬件 module。
/// 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
}
object GCD extends App {
ChiselStage.emitSystemVerilogFile(
new GCD,
firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info")
)
}
上面这个 class GCD
继承了 Chisel 的 Module 抽象类。在这个类里面,我们描述了 GCD 模块的行为。等价于 SystemVerilog 中的 module GCD();
。object GCD
表示创建了一个名为 GCD 的单例,这个单例继承自 App,起到程序入口的作用,这里用于发射 SystemVerilog 代码。
然后我们通过在命令行中输入 sbt "runMain gcd.GCD"
来启动 SystemVerilog 代码的发射 。最后,我们就可以在项目的根目录下找到生成的 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
- 这里是 Scala 教程:https://docs.scala-lang.org/zh-cn/
- 这里是 Chisel 教程:https://www.chisel-lang.org/docs/cookbooks/cookbook
使用 Verilator C++ 调试 Chisel 生成的 SystemVerilog
我们可以用 C++ 编写一个 golden model 作为对照。
// 一个正确的 gcd 实现
int gcd(uint16_t a, uint16_t b)
{
while (b != 0) {
int remainder = a % b;
a = b;
b = remainder;
}
return a;
}
下面我们进行了硬件模块的仿真。这里的 #include "VGCD.h"
是在使用 Verilator 将 SystemVerilog 代码编译成 C++ 类的过程中自动生成的头文件。这个头文件包含了编译后的 C++ 类,用于在仿真环境中模拟硬件行为。
#include "VGCD.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
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;
}
编译 C++ 和 SystemVerilog
verilator -Wall --cc --trace -Iobj_dir -Wno-UNUSEDSIGNAL GCD.sv --exe tb.cxx # 会生成 obj_dir 文件夹,这是将 SystemVerilog 编译成了 C++ 的 class
make -C obj_dir -f VGCD.mk
这样,我们就使用 Verilator 完成了 C++ 和 SystemVerilog 的编译。我们可以在 obj_dir
文件夹中找到 VGCD
可执行文件。通过在命令行中执行 ./obj_dir/VGCD
,我们可以运行这个可执行文件。执行完成后,如果看到输出中显示 success
字样,这表示仿真成功。同时,在项目的根目录下会生成一个名为 gcd.vcd
的文件。我们可以使用 VSCode 的插件 wavetrace(付费) 来打开这个 .vcd
文件,以便查看波形数据。
详细的用法查阅 Verilator 的使用教程 。
使用 GTKWave 打开波形仿真文件
gtkwave gcd.vcd
使用 tcl 脚本
每次使用 GTKWave 调试波形图时,我们需要执行一系列重复的步骤:打开程序、选择信号、然后使用 append/insert 将信号添加到视图中。 这很不优雅。尤其是:当我们需要频繁的调试:打开 GTKWave -> 选中信号 -> a/i -> 看波形 -> 改代码 -> make run -> 打开 GTKWave -> 选中信号 -> a/i -> 看波形 -> ...
然而,我们可以通过自动化来简化这个流程。具体做法是编写一个 tcl 脚本,这样每次打开 GTKWave 时,脚本会自动将所有必要的信号添加到显示界面中。这样可以提高调试效率。
创建一个 load_all_waves.tcl
文件,写入以下内容,保存。
# 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
:
# 可以通过 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
附录
macOS 安装 GTKWave
macOS 下安装 GTKWave 是一件非常令人疑惑的事情。macOS 有着地狱一般的向下兼容问题。
GTKWave 并没有被 Homebrew 官方收录,但可以使用第三方仓库 randomplum/gtkwave 安装:
brew tap randomplum/gtkWave
brew install --HEAD randomplum/gtkwave/gtkwave
然而,此仓库中维护的 GTKWave 并不包含 TCL 脚本支持,无法使用上文提到的便利方法,因此并不推荐使用这种方式安装。
Nix 是 Mac 上不同于 Homebrew 的其他包管理器,可以用于安装带 TCL 脚本支持的 GTKWave:
Nix
Nix 是 Linux 发行版 NixOS 的包管理器,具有强大而可复现的环境管理功能; 它也可以脱离 NixOS, 单独作为一些其他 Linux distro 以及 macOS 的包管理器使用。
通过这行命令安装 Nix。安装过程中请注意看提示。
sh <(curl -L https://nixos.org/nix/install)
下面是通过 Nix 安装 GTKWave 的方法:
# i 表示 install
# A 表示 attr 这告诉 nix-env 通过它的属性名来选择软件包
nix-env -iA nixpkgs.gtkwave
实际上 Nix 不仅仅能起到包管理的作用,它几乎可以完成任何项目的完整环境复现。对 Nix 和 NixOS 感兴趣的同学可以额外阅读 Nix 中文指南
远程开发的 GTKWave 的问题
远端机器上通常没有桌面环境,当我们连接到远端机器开发时,肯定是不能指望直接在远端机器上执行 gtkwave <.vcd>
就能在本地弹出一个窗口出来的。
自然,我们可以为远端机器安装桌面环境和配置 X-Forward。
但倘若只是想用 GTKWave 查看波形,那么将波形文件以某种方式下载到本地,再使用本地的 GTKWave 打开查看显然是更合理的选择。
但每次重新运行仿真都要手动通过 VSCode 或 scp 下载波形文件确实令人烦躁,那么应该如何快捷地将远端的某个文件下载或同步到本地呢?
我们可以使用 SSHFS 或者基于 SSHFS 的其他软件(例如 CyberDuck, MountainDuck 等)将远端的目录直接挂载到本地,
每次本地打开挂载点内文件时自动从远端拉取文件内容,从而便利我们在本地使用 GTKWave 查看波形。
安装 SSHFS
Windows/Linux
# Windows
winget install -h -e --id "WinFsp.WinFsp"
# Ubuntu/Debian
sudo apt install sshfs
# Fedora/Centos
sudo dnf install sshfs
macOS 安装 SSHFS
macOS 安装 SSHFS 相对来说比较复杂,需要开启 macOS 的内核拓展。
可以在这里下载 macOS 下 SSHFS 的相关软件 macFUSE 和 SSHFS
使用 macFUSE 需要开启内核拓展,请看这个教程 Getting Started with macFUSE
使用 SSHFS
sshfs <user>@<ip>:<远端目录> <挂载点>
例如在笔者的电脑上是这样挂载的
sshfs wangfiox@192.168.1.1:/home/wangfiox/Documents/organ ~/Document/workspace/organ
我们可以通过下面这个命令验证
ll organ
# 输出结果如下
# total 48
# -rw-r--r-- 1 wangfiox staff 9B May 25 19:30 README.md
# drwxr-xr-x 1 wangfiox staff 32B May 26 19:04 archive
# drwxr-xr-x 1 wangfiox staff 16B May 26 18:49 hitsz
# drwxr-xr-x 1 wangfiox staff 162B May 25 19:30 lab1
# drwxr-xr-x 1 wangfiox staff 272B May 29 20:22 lab2
# drwxr-xr-x 1 wangfiox staff 96B May 25 19:30 rars
Chisel(Scala) IDE 的选择
Chisel 只是 Scala 中的一个库。因此,只要 IDE 能支持 Scala,那么自然也是支持 Chisel 了。 但是一些 IDE 如 VSCode/JetBrains IDEA 会对 Chisel 语法有更加好的 highlight 支持。
经观察:对于稍微大一些的 Chisel 项目 (小学期级别), VSCode + Metals 会很卡,建议使用 JetBrain IDEA
VSCode 对 Chisel(Scala) 的支持
下面是笔者使用的 VSCode 插件
- Chisel Syntax
- Scala (Metals)
- Scala Snippets
- Scala Syntax (official)
JetBrains IDEA 对 Chisel(Scala) 的支持
下面是笔者使用的 JetBrain IDEA 插件
- Scala