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

使用流程很简单:

  1. Verilator仿真时,在C++测试台里调用 tfp->open("wave.fst")
  2. 跑完仿真,生成 wave.fst
  3. 用 gtkwave wave.fst 打开
注意: GTKWave的界面比较“复古”,别被吓到。快捷键记几个: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 → 清理中间文件

是不是清爽多了?

避坑指南: 我曾经在Makefile里忘了加 --build 参数,结果Verilator只生成C++代码没编译,折腾了半小时才发现。所以每次改Makefile,先跑一遍 make clean 再全量编译,避免缓存问题。

2.5 整体流程:一张图看懂

下面这张图,把整个开发环境的工作流串起来了。你从写Verilog和C测试程序开始,经过Verilator编译、仿真,生成波形文件,最后用GTKWave分析。同时,RISC-V GCC负责把C测试程序编译成二进制,喂给仿真器里的软核。

RISC-V软核开发环境工作流 Verilog RTL top.v, alu.v, ... C++ 测试台 main.cpp C 测试程序 test.c RISC-V GCC 交叉编译 Verilator 编译 + 仿真 波形文件 wave.fst GTKWave 波形查看 Makefile 自动化 make → 全流程 make wave → 看波形

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 基本都兼容
重要: 如果你用的是Ubuntu 20.04,默认源里的Verilator版本是4.0,够用但不够新。我建议从源码编译4.210以上版本,否则后面调试时可能会遇到一些奇怪的仿真bug。

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。

恭喜你! 你的第一个RISC-V软核仿真环境跑通了。虽然这个核只做加法,但后面我们会一步步把它变成完整的五级流水线RISC-V处理器。

嗯,环境搭好了,后面的事情就顺了。记住,工具是死的,人是活的。遇到问题别慌,先检查版本、再检查路径、最后检查Makefile。我当年调试环境时,最常犯的错误就是忘了 make clean——旧文件没清掉,新代码没编进去,折腾半天才发现。