一生一芯——搭建Verilator仿真环境
前言
终于写完nemu和npc了,写这篇文章只是为了记录一下自己的学习过程,以及为之后的”萌新“朋友们提供一点参考。一生一芯和南大PA一直强调自主解决问题的能力,希望能好好遵守。不过我也深知自己一个人探索的无力,常常因为一个简单的问题STFW/STFM一整天。希望这篇学习记录能帮到需要的人。
如果发现文章有问题,可以发邮件给我(1072821751@qq.com),或者评论区直接骂我 😃。
安装Verilator
首先贴一个官方的安装教程Verilator安装。十分建议你参考官方手册进行安装,因为这是一生一芯一直强调的能力。
- 首先安装依赖(Prerequisites)
sudo apt-get install git help2man perl python3 make autoconf g++ flex bison ccache
sudo apt-get install libgoogle-perftools-dev numactl perl-doc
sudo apt-get install libfl2 # Ubuntu only (ignore if gives error)
sudo apt-get install libfl-dev # Ubuntu only (ignore if gives error)
sudo apt-get install zlibc zlib1g zlib1g-dev # Ubuntu only (ignore if gives error)
# 建议安装 最好都安装上
sudo apt-get install ccache # If present at build, needed for run
sudo apt-get install mold # If present at build, needed for run
sudo apt-get install libgoogle-perftools-dev numactl
sudo apt-get install perl-doc
- 获取Verilator
Verilator官方GitHub仓库:Verilator
cd ~
git clone https://github.com/verilator/verilator
unset VERILATOR_ROOT
cd verilator
git pull
git tag # 查看版本号
git checkout v5.008 # ysyx要求安装5.008版本
make -j `nproc` # 强烈建议使用 不然速度很慢 需要等待大概5-10分钟
sudo make install
Verilator --verision
# 如果显示以下信息则安装成功
Verilator 5.008 2023-03-04 rev v5.008
可能遇到的问题: fetch-pack: unexpected disconnect while reading sideband packet
,这是由于clone大项目出现超时。
可以尝试利用ssh方式获取(我用这种方法解决的),其他方法STFW。
git clone git@github.com:verilator/verilator.git
- 运行c++示例
cd verilator/examples/make_hello_c/
make
出现下列信息运行成功,打印出Hello World!
字符。这只是一个简单的示例,makefile文件和cpp文件的说明放到下面的流水灯实现中。
-- RUN ---------------------
obj_dir/Vtop
Hello World!
- top.v:12: Verilog $finish
-- DONE --------------------
安装gtkwave
sudo apt-get install gtkwave
gtkwave --version
务必进行version查看,因为gtkwave可能有些依赖包你没有安装,导致没有波形。
可能遇到的问题:Failed to load module "canberra-gtk-module"
,安装即可。
sudo apt-get install libcanberra-gtk-module
接入NVBoard
cd ysyx-workbench
bash init.sh nvboard
git clone https://github.com/NJU-ProjectN/nvboard.git
apt-get install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev
最好查一下NVBOARD_HOME是否设置正确,正确路径应该为/home/user/ysyx-workbench/nvboard
echo $NVBOARD_HOME
# 如果输出不正确
export NVBOARD_HOME=/home/user/ysyx-workbench/nvboard # 注意等号两边不要有空格
cd nvboard/example
make run
# 运行成功则安装成功
流水灯实现
Makefile
Makefile文件可以参考NVBoard的example里面的Makefile,只用修改一点地方。
TOPNAME = top
NXDC_FILES = constr/top.nxdc
INC_PATH ?=
VERILATOR = verilator
VERILATOR_CFLAGS += -MMD --build --trace -cc \ # 加--trace以启动波形记录
-O3 --x-assign fast --x-initial fast --noassert # Verilaotor编译规则
BUILD_DIR = ./build
OBJ_DIR = $(BUILD_DIR)/obj_dir
BIN = $(BUILD_DIR)/$(TOPNAME) # BIN为最后的可执行文件,路径为./build/top
default: $(BIN)
$(shell mkdir -p $(BUILD_DIR)) # 创建build文件夹
# constraint file
SRC_AUTO_BIND = $(abspath $(BUILD_DIR)/auto_bind.cpp) # 引脚绑定文件
$(SRC_AUTO_BIND): $(NXDC_FILES)
python3 $(NVBOARD_HOME)/scripts/auto_pin_bind.py $^ $@ # 生成引脚绑定文件
# project source
VSRCS = $(shell find $(abspath ./vsrc) -name "*.v") # 所有.v文件 即你编写的verilog文件
CSRCS = $(shell find $(abspath ./csrc) -name "*.c" -or -name "*.cc" -or -name "*.cpp")
# 所有c++文件,其实就是sim_main.cpp和auto_bind.cpp
CSRCS += $(SRC_AUTO_BIND)
# rules for NVBoard
include $(NVBOARD_HOME)/scripts/nvboard.mk # 把NVBoard编译规则包含进来
# rules for verilator
INCFLAGS = $(addprefix -I, $(INC_PATH))
CXXFLAGS += $(INCFLAGS) -DTOP_NAME="\"V$(TOPNAME)\""
LDFLAGS += -lSDL2 -lSDL2_image
# 这条是最主要的命令,即仿真指令。
$(BIN): $(VSRCS) $(CSRCS) $(NVBOARD_ARCHIVE)
@rm -rf $(OBJ_DIR) wave.vcd # 删除上一次make生成的obj文件夹和vcd波形文件,防止冲突
$(VERILATOR) $(VERILATOR_CFLAGS) \
--top-module $(TOPNAME) $^ \ # 指示顶层模块名,所以顶层模块最好都命名为top
$(addprefix -CFLAGS , $(CXXFLAGS)) $(addprefix -LDFLAGS , $(LDFLAGS)) \ # 链接库 NVBoard需求
--Mdir $(OBJ_DIR) --exe -o $(abspath $(BIN)) # 写入obj文件夹 --exe生成可执行文件 -o设置生成路径
all: default
run: $(BIN)
./build/top +trace # 运行top可执行文件以记录波形
clean:
rm -rf $(BUILD_DIR) wave.vcd
.PHONY: default all clean run
verilog
路径:npc/vsrc/top.v
,文件名字最好使用top.v,方便后续文件编写。
module top( // 这里名字要与Makefile中的TOPNAME和cpp文件中的#include "Vtop.h"相同
input clk,
input rst,
output reg [15:0] led
);
reg [31:0] count;
always @(posedge clk) begin
if (rst) begin led <= 1; count <= 0; end
else begin
if (count == 0) led <= {led[14:0], led[15]}; // 后四位:0001->0010->0100->1000 为1的位亮灯
count <= (count >= 5000000 ? 32'b0 : count + 1); // 控制亮灯间隔
end
end
endmodule
sim_main.cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<nvboard.h>
#include"Vtop.h" // 从top.v生成
#include"verilated.h"
#include"verilated_vcd_c.h"
void nvboard_bind_all_pins(Vtop* top);
int main(int argc, char** argv) {
VerilatedContext* contextp = new VerilatedContext;
contextp->traceEverOn(true); // 开启波形
contextp->commandArgs(argc, argv);
Vtop* top = new Vtop{contextp};
nvboard_bind_all_pins(top); // 引脚绑定
nvboard_init();
VerilatedVcdC* m_trace = new VerilatedVcdC; // trace_object
top->trace(m_trace, 99); // 99表示记录最详细的信号信息
m_trace->open("wave.vcd"); // 波形文件
top->clk = 0; // clk初始化
// ----原reset函数----start
int n = 10;
top->rst = 1;
while (n-- > 0){top->clk = !top->clk;top->eval();}
top->rst = 0;
// ----原reset函数----end
while (!contextp->gotFinish()) {
nvboard_update();
contextp->timeInc(1); // 推进仿真时间
top->clk = !top->clk; top->eval(); // eval()模型更新 可以理解为执行一次.v文件
m_trace->dump(contextp->time()); // 记录波形
}
m_trace->close();
delete top;
delete contextp;
return 0;
}
理解sim_main.cpp文件对Verilator仿真与波形生成有重大意义,首先
- 模型更新后立即记录波形
in your main loop, right after eval() call trace_object->dump(contextp->time()) every time step
上面这句话摘自Verilator官方文档
需要注意的是dump只记录下此刻模型中的信号信息。
void single_cycle(Vtop* top) {
top->clk = 0; top->eval();
top->clk = 1; top->eval();
}
while (!contextp->gotFinish()) {
contextp->timeInc(1);
single_cycle(top);
m_trace->dump(contextp->time());
}
如果这样写,得到的波形如下:clk永远为1。因为每次single_cycle执行结束后,clk都为1,此刻再调用dump记录信号信息,就只会记录到clk=1。(这里led和count的初始值不正确,你应该知道怎么回事了!)
- 为什么需要reset函数
reset函数是对应.v文件中if (rst) begin led <= 1; count <= 0; end
,信号需要初始化。reset函数执行时,始终满足top->rst=1
,此时只执行这行初始化语句,led<=1 count<=0
。 - 为什么n的值为10,可以为其他值吗
我们首先记录一下波形看一下(波形是很重要的调试工具!)
top->clk = 0;
int n = 10;
top->rst = 1;
m_trace->dump(contextp->time());
while (n -- > 0){
contextp->timeInc(1);
top->clk = !top->clk;
top->eval();
m_trace->dump(contextp->time());
}
top->rst = 0;
实际只要给rst=1
时一个上升沿,让.v文件成功完成信号初始化即可。即在信号初始化后立即退出while循环结束初始化,此时的n为最小。
通过这两个例子相信你对sim_main.cpp文件的编写以及波形查看已经有一些感觉了。
top.nxdc
NVBoard的引脚绑定文件
top=top
led (LD15, LD14, LD13, LD12, LD11, LD10, LD9, LD8, LD7, LD6, LD5, LD4, LD3, LD2, LD1, LD0)
一切准备就绪
make run
如果一切顺利,你会看到弹出NVBoard界面,在LED处从左到右依次亮起