4. RTL仿真入门:仿真流程概述、编写第一个Testbench、使用Verilator进行RTL仿真、波形生成与查看(GTKWave)

说实话,很多刚接触RISC-V的朋友,一上来就盯着代码看,想一口气搞懂蜂鸟E203的流水线。我个人的建议是——先跑通仿真。你想想看,代码写得再漂亮,不跑一遍仿真,你怎么知道它能不能动?

这一章,我们就从零开始,把RTL仿真的整套流程走一遍。我会带你写第一个Testbench,用Verilator跑起来,最后用GTKWave看波形。嗯,这些都是基本功,但也是后面所有调试工作的基础。

4.1 仿真流程:从代码到波形

RTL仿真说白了就是:给设计喂激励,看它怎么反应。整个过程其实就三步:

  1. 编译:把Verilog/VHDL代码和Testbench编译成可执行文件
  2. 运行:执行仿真,产生波形文件(.vcd, .fsdb等)
  3. 查看:用波形工具打开,分析信号变化

我在项目中遇到过好几次,有人把编译和运行混为一谈,结果报错了半天找不到原因。记住,编译是生成仿真器能理解的东西,运行才是真正让电路动起来。

核心要点:仿真不是验证设计的唯一手段,但它是发现RTL级bug最直接的方法。尤其是蜂鸟E203这种多级流水线处理器,很多时序问题只有跑仿真才能暴露出来。

下面这张图,把整个仿真流程串起来了。你可以把它当作本章的地图:

RTL仿真完整流程 RTL设计代码 e203_core.v 等 Testbench tb_top.v Verilator 编译 verilator --cc --build 实例化 输入 仿真可执行文件 obj_dir/Vtb_top 生成 运行仿真 ./Vtb_top +trace 波形文件 sim.vcd GTKWave 查看 gtkwave sim.vcd 避坑提醒 • 编译和运行是两步 • 波形文件要加 +trace • 先检查编译警告 • 再跑仿真看波形

4.2 编写第一个Testbench

Testbench是什么?说白了,就是一段Verilog代码,它不综合成电路,只用来给设计提供激励。我习惯把Testbench叫做「仿真脚手架」——它本身不是建筑,但没它建筑立不起来。

写Testbench有几个要点:

  • 实例化待测模块:把蜂鸟E203的顶层模块例化进来
  • 生成时钟和复位:处理器没有时钟和复位,啥也干不了
  • 加载程序:把RISC-V指令通过总线接口灌进去
  • 检查结果:看处理器是不是跑到了预期的结果

来看一个最简单的例子。这个Testbench只做一件事:给蜂鸟E203上电,让它跑起来:

// tb_top.v - 蜂鸟E203最小Testbench
`timescale 1ns / 1ps

module tb_top;

    // 时钟和复位
    reg clk;
    reg rst_n;

    // 实例化待测设计
    e203_core_top u_e203_core_top (
        .clk    (clk),
        .rst_n  (rst_n)
        // 其他端口先不接,简化处理
    );

    // 生成时钟:10ns周期,100MHz
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // 复位时序:先拉低,再释放
    initial begin
        rst_n = 0;
        #100;
        rst_n = 1;
    end

    // 仿真结束控制
    initial begin
        #10000;  // 跑10us
        $finish;
    end

    // 波形导出
    initial begin
        $dumpfile("sim.vcd");
        $dumpvars(0, tb_top);
    end

endmodule

小技巧:`timescale 1ns / 1ps 的意思是时间单位1ns,精度1ps。精度设得比单位小,是为了防止仿真中的时间舍入误差。我刚开始写Testbench时没注意这个,结果波形里信号跳变沿对不齐,查了半天才发现是精度问题。

4.3 使用Verilator进行RTL仿真

Verilator是个好东西。它不像商业仿真器那样吃资源,而且跑得飞快。蜂鸟E203的整个核,用Verilator编译也就几十秒。

安装Verilator我就不多说了,网上教程很多。咱们直接看怎么用它跑蜂鸟E203:

# 第一步:编译RTL和Testbench
verilator --cc --build --trace \
    -I./rtl \
    -I./tb \
    --top-module tb_top \
    tb_top.v \
    e203_core_top.v \
    -o Vtb_top

# 参数说明:
# --cc      生成C++代码
# --build   自动编译生成可执行文件
# --trace   支持VCD波形导出
# -I        指定搜索路径
# --top-module 指定顶层模块

编译完成后,你会看到一个 obj_dir 目录。里面有个 Vtb_top 可执行文件。运行它:

# 第二步:运行仿真
./obj_dir/Vtb_top

# 如果一切正常,会生成 sim.vcd 文件

这里有个坑,我曾经踩过:如果编译时忘了加 --trace,运行后不会生成 .vcd 文件。你检查半天代码,结果只是少了个参数。所以我的习惯是,每次编译都加上 --trace,反正也不影响性能。

注意:Verilator对Verilog的语法要求比商业仿真器严格。比如,它不允许在always块里对同一个变量多次赋值。蜂鸟E203的代码本身是经过Verilator验证的,但如果你自己写了外围模块,编译时可能会报一些语法错误。别慌,按提示改就行。

4.4 波形生成与查看(GTKWave)

仿真跑完了,生成了 sim.vcd 文件。怎么打开?用GTKWave:

gtkwave sim.vcd

GTKWave的界面很朴素,但功能够用。打开后,你会看到左侧是信号列表,右侧是波形显示区。操作很简单:

  • 添加信号:在左侧找到信号,双击或拖到右侧
  • 缩放:鼠标滚轮,或者按 + / - 键
  • 测量时间:按住Ctrl,点击两个位置,会显示时间差
  • 搜索跳变:按 / 键,输入信号值,可以快速定位

看波形时,我一般会关注这几个信号:

信号名 关注点 说明
clk 周期是否稳定 时钟抖动会导致时序问题
rst_n 复位释放时机 复位释放后,处理器才开始工作
pc_rdata PC值是否递增 PC不动说明处理器卡住了
exu_arith_rd 运算结果是否正确 可以对照指令集手册检查

举个例子。如果你看到PC值一直停在0x1000不动,那大概率是取指阶段出了问题。可能是ICache没命中,也可能是总线接口没响应。这时候,顺着波形往前查,看 ifu_req_valid 有没有拉高,ifu_req_ready 有没有回过来。

调试心法:看波形不是从头看到尾,而是带着问题看。先确定现象(PC不动),再推测原因(取指卡住),最后用波形验证(看握手信号)。我在调试蜂鸟E203的ICache时,就是用这个方法,半小时就定位到了一个地址对齐的bug。

GTKWave还有一个很实用的功能:保存波形配置。你调好信号布局后,按Ctrl+S保存为 .gtkw 文件。下次直接 gtkwave sim.vcd -a config.gtkw 就能恢复布局。我每次调试都会保存配置,省得反复拖信号。

嗯,到这里,RTL仿真的整套流程就走通了。从写Testbench,到用Verilator编译运行,再到GTKWave看波形,每一步都是后面调试的基础。你可以在蜂鸟E203上试试,跑一个简单的RISC-V程序,看看PC是不是从复位向量开始,一步步往下走。