2. 开发环境搭建:Verilator仿真器、GTKWave波形查看、RISC-V GCC工具链、Makefile构建系统
说实话,很多同学学RISC-V,一上来就盯着流水线结构看,结果代码写完了不知道怎么跑、怎么调。我当年也踩过这个坑——花了两周写了个五级流水线,结果仿真环境配了三天还没跑起来。所以这一章,咱们先把家伙事儿备齐。
你要搭建的,是一套完整的RISC-V软核开发流水线。说白了,就是:
- 写代码 → 用Verilog
- 仿真 → 用Verilator
- 看波形 → 用GTKWave
- 编译测试程序 → 用RISC-V GCC
- 自动化构建 → 用Makefile
嗯,这五个工具串起来,就是你后面28章所有实战的基础。咱们一个一个来。
2.1 Verilator仿真器:为什么选它?
你可能听说过VCS、ModelSim、Icarus Verilog。但为什么我坚持用Verilator?
快。 真快。Verilator不是解释执行Verilog,而是把Verilog代码转成C++,然后编译成可执行文件。跑一个RISC-V核的仿真,VCS可能要几十秒,Verilator几秒就完事。你想想看,后面你要反复修改流水线、加调试、跑测试,每次等半分钟和等三秒,体验天差地别。
安装也很简单。Linux下直接:
sudo apt-get install verilator
或者从源码编译(我建议用最新版,4.0以上):
git clone https://github.com/verilator/verilator
cd verilator
autoconf
./configure
make -j$(nproc)
sudo make install
tools/ 文件夹,把Verilator的二进制和库文件单独放一份。这样换机器、换环境,直接拷贝就能用,不用重新编译。
2.2 GTKWave波形查看:调试的“眼睛”
Verilator只管跑仿真,跑完你得看波形吧?GTKWave就是干这个的。
它支持VCD、FST、LXT等多种波形格式。我个人推荐用FST格式——压缩率高,加载快。你想想,一个RISC-V核跑几千条指令,VCD文件可能几百MB,FST能压到十分之一。
安装:
sudo apt-get install gtkwave
使用流程很简单:
- Verilator仿真时,在C++测试台里调用
tfp->open("wave.fst") - 跑完仿真,生成
wave.fst - 用
gtkwave wave.fst打开
Ctrl+Z 撤销缩放,Ctrl+F 搜索信号,鼠标滚轮缩放。我曾经花了一下午才习惯它的操作逻辑……嗯,习惯就好。
2.3 RISC-V GCC工具链:给软核“喂”程序
你的RISC-V核是硬件,但它得跑软件才能验证对不对。这个“软件”就是RISC-V的二进制指令。谁来编译?RISC-V GCC。
你需要的是交叉编译工具链——在x86机器上编译出RISC-V的机器码。常用的有:
- riscv64-unknown-elf-gcc:裸机编译,没有操作系统
- riscv64-linux-gnu-gcc:带Linux系统调用支持
咱们做软核验证,用前者就够了。安装方式:
sudo apt-get install gcc-riscv64-unknown-elf
或者从SiFive官网下载预编译包。我个人习惯用2021年之后的版本,对RV32和RV64支持都比较完善。
写一个简单的测试程序:
// test.c
int add(int a, int b) {
return a + b;
}
int main() {
int x = add(1, 2);
return x;
}
编译:
riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 -o test.elf test.c
然后你可以用 riscv64-unknown-elf-objdump -d test.elf 看反汇编,确认指令对不对。
-march=rv32im 指定指令集架构,-mabi=ilp32 指定ABI。这两个参数必须和你设计的软核一致。我见过有人用rv64编译,结果核是rv32的,跑出来全是非法指令异常。
2.4 Makefile构建系统:一键搞定所有
手动敲命令太累了。每次修改Verilog代码,你得重新跑Verilator编译、重新仿真、重新看波形。所以我们需要Makefile。
一个典型的Makefile结构:
# 工具路径
VERILATOR = verilator
GTKWAVE = gtkwave
RISCV_GCC = riscv64-unknown-elf-gcc
# 源文件
VERILOG_SRC = $(wildcard rtl/*.v)
CXX_SRC = sim/main.cpp
# 目标
all: sim
# 编译测试程序
test.elf: test.c
$(RISCV_GCC) -march=rv32im -mabi=ilp32 -o $@ $<
# 运行Verilator仿真
sim: test.elf $(VERILOG_SRC) $(CXX_SRC)
$(VERILATOR) --cc --exe --build \
--top-module top \
$(VERILOG_SRC) $(CXX_SRC) \
-o Vtop
./obj_dir/Vtop
# 查看波形
wave:
$(GTKWAVE) wave.fst
# 清理
clean:
rm -rf obj_dir wave.fst test.elf
然后你只需要:
make→ 编译测试程序 + 跑仿真make wave→ 打开波形make clean→ 清理中间文件
是不是清爽多了?
--build 参数,结果Verilator只生成C++代码没编译,折腾了半小时才发现。所以每次改Makefile,先跑一遍 make clean 再全量编译,避免缓存问题。
2.5 整体流程:一张图看懂
下面这张图,把整个开发环境的工作流串起来了。你从写Verilog和C测试程序开始,经过Verilator编译、仿真,生成波形文件,最后用GTKWave分析。同时,RISC-V GCC负责把C测试程序编译成二进制,喂给仿真器里的软核。
2.6 工具版本与兼容性
不同版本的工具可能会有兼容性问题。我整理了一张表,方便你对照:
| 工具 | 推荐版本 | 最低版本 | 备注 |
|---|---|---|---|
| Verilator | 4.210+ | 4.0 | 4.0以下不支持SystemVerilog部分语法 |
| GTKWave | 3.3.104+ | 3.3.0 | 旧版加载大波形会卡顿 |
| RISC-V GCC | 2021.12+ | 2020.04 | 旧版对Zicsr扩展支持不全 |
| Make | 4.3+ | 3.81 | 基本都兼容 |
2.7 快速验证:跑通第一个仿真
环境搭好了,怎么知道对不对?写一个最简单的RISC-V核——就一条加法指令。嗯,你没听错,就一条。
Verilog代码(rtl/top.v):
module top (
input clk,
input rst_n,
output [31:0] result
);
reg [31:0] a, b;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
a <= 32'd1;
b <= 32'd2;
end
end
assign result = a + b;
endmodule
C++测试台(sim/main.cpp):
#include <verilated.h>
#include "Vtop.h"
int main(int argc, char** argv) {
Verilated::commandArgs(argc, argv);
Vtop* top = new Vtop;
// 初始化波形
VerilatedFstC* tfp = new VerilatedFstC;
Verilated::traceEverOn(true);
top->trace(tfp, 99);
tfp->open("wave.fst");
// 复位
top->rst_n = 0;
top->clk = 0;
top->eval();
top->rst_n = 1;
// 跑10个时钟周期
for (int i = 0; i < 10; i++) {
top->clk = !top->clk;
top->eval();
tfp->dump(i);
}
tfp->close();
delete top;
return 0;
}
然后 make。如果一切正常,你会看到终端打印出仿真完成的信息。再用 make wave 打开波形,你应该能看到 result 信号的值是3。
嗯,环境搭好了,后面的事情就顺了。记住,工具是死的,人是活的。遇到问题别慌,先检查版本、再检查路径、最后检查Makefile。我当年调试环境时,最常犯的错误就是忘了 make clean——旧文件没清掉,新代码没编进去,折腾半天才发现。