一生一芯——搭建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的初始值不正确,你应该知道怎么回事了!)
wave

  • 为什么需要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;

wave
实际只要给rst=1时一个上升沿,让.v文件成功完成信号初始化即可。即在信号初始化后立即退出while循环结束初始化,此时的n为最小。

wave
通过这两个例子相信你对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处从左到右依次亮起

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值