4. 蜂鸟E203源码结构与模块划分
说实话,很多刚接触RISC-V的朋友,一上来就盯着代码看,想一口气搞懂蜂鸟E203的流水线。我个人的建议是——先跑通仿真。你想想看,代码写得再漂亮,不跑一遍仿真,你怎么知道它能不能动?
这一章,我们就从零开始,把RTL仿真的整套流程走一遍。我会带你写第一个Testbench,用Verilator跑起来,最后用GTKWave看波形。嗯,这些都是基本功,但也是后面所有调试工作的基础。
4.1 仿真流程:从代码到波形
RTL仿真说白了就是:给设计喂激励,看它怎么反应。整个过程其实就三步:
- 编译:把Verilog/VHDL代码和Testbench编译成可执行文件
- 运行:执行仿真,产生波形文件(.vcd, .fsdb等)
- 查看:用波形工具打开,分析信号变化
我在项目中遇到过好几次,有人把编译和运行混为一谈,结果报错了半天找不到原因。记住,编译是生成仿真器能理解的东西,运行才是真正让电路动起来。
核心要点:仿真不是验证设计的唯一手段,但它是发现RTL级bug最直接的方法。尤其是蜂鸟E203这种多级流水线处理器,很多时序问题只有跑仿真才能暴露出来。
下面这张图,把整个仿真流程串起来了。你可以把它当作本章的地图:
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是不是从复位向量开始,一步步往下走。