前言
最近在搞一生一芯,前期还算比较顺利,但是在仿真环境搭建这部分遇到了大问题。一是因为自己之前都是直接烧录到真实的FPGA等板卡上,很少链接这样的仿真环境,二是因为Verilator和NVBoard的资料很少,尤其是NVBoard。
在摸索了好几天后,大致摸索出来一点点门路,所以希望缓一下,整理一下,也帮大家少走一些弯路。
PS:本人是微电子专业,本文中的许多用词可能并不严谨,甚至有错误,但是并不影响最终的使用,还请大家见谅。
前置条件
这里默认大家已经按照一生一芯的笔记安装好Verilator和NVBoard了,这里只讲解如何使用。
PS_1:安装NVBoard所依赖的3个包(SDL2,SDL2-image和SDL2-ttf)的时候,使用Ubuntu自带的软件源可能会报错,需要更换到其他源,我更换到阿里云源可以下载。
PS_2:查看波形文件可以直接使用VSCode里的Digital IDE插件,这个插件功能很强大推荐给大家。
Verilator的简单使用
我这里主要参考的以下这篇文章,这篇文章给出了一种Verilator简单的使用方法:
http://t.csdnimg.cn/4wioKhttp://t.csdnimg.cn/4wioK接下来我主要把上面的文章总结一下,给大家一个固定的使用流程模板。我使用一个与门作为例子。
(可选)第零步:搭建工程框架
新建一个文件夹,名字随意,这里叫做my_example_andgate。然后再新建一个子文件夹名字叫做"vsrc",这里保存verilog的源代码文件。
最终框架如下:
my_example_andgate
|--vsrc
|--top.v
第一步:编写verilog文件top.v
首先我们需要使用verilog语言编写我们希望生成的电路,并且最顶层的module名称必须为top。在“vsrc”文件夹中,新建一个top.v文件,top.v文件里我编写了一个非常简单的与门:
// 使用一个与门作为示范
module top(
input a,
input b,
output f
);
assign f = a & b;
endmodule
保存top.v代码后关闭。
第二步:使用verilator将top.v文件转换为cpp文件(verilate化)
在my_example_andgate这个目录下,打开命令行终端,输入以下代码:
verilator -Wall --trace -cc ./vsrc/top.v --exe tb_top.cpp
执行完这一步,将会自动产生一个obj_dir文件夹,obj_dir里面是top.v经过verilator转化后生成的Vtop.cpp以及各种头文件。
PS:这行代码每一个参数的具体含义请参考上面那篇文章。
此时的框架如下:
my_example_andgate
|--obj_dir
|--...
|--vsrc
|--top.v
其实到这里,Verilator的工作就完成了,Verilator的本质就是将verilog文件转化为cpp以及各种头文件供我们后边调用。至于编写后续测试激励文件的事情,是一个纯cpp的开发,与verilator已经没有关系了。
第三步:编写测试激励文件tb_top.cpp
我们打开obj_dir文件夹,并在这里新建一个名为tb_top.cpp的测试激励文件。
打开tb_top.cpp后,在里面输入以下代码:
#include <verilated.h>
#include <verilated_vcd_c.h>
#include "Vtop.h"
#include "Vtop___024root.h"
#define MAX_SIM_TIME 20 // 最大仿真时钟周期
vluint64_t sim_time = 0; // 实际仿真周期
int main(int argc, char** argv, char** env) {
Vtop *dut = new Vtop; // 例化一个top模块
// 下面4行为开启跟踪固定代码
Verilated::traceEverOn(true);
VerilatedVcdC *m_trace = new VerilatedVcdC;
dut->trace(m_trace, 5);
m_trace->open("waveform.vcd");
while (sim_time < MAX_SIM_TIME) {
int a = rand() & 1; // 随机生成a
int b = rand() & 1; // 随机生成b
dut->a = a; // 将a赋值给例化模块的a
dut->b = b; // 将b赋值给例化模块的b
dut->eval(); // 更新电路状态!!!切记要在while中添加,非常重要,一定不能少
m_trace->dump(sim_time); // 将当前状态记录到trace中,后续输出波形要用
sim_time++; // 仿真周期增加
}
m_trace->close();
delete dut;
exit(EXIT_SUCCESS);
}
可以看出,实际上main函数中的while循环就是我们之前传统verilog开发时候编写的testbench,只不过因为verilator将verilog文件转化为cpp文件了,所以我们就可以直接用cpp编写testbench了。
第四步:编译测试激励文件并生成一个可执行文件
编写testbench(tp_top.cpp)后,我们再次在my_example_andgate这个目录下,打开命令行终端,输入以下代码:
make -C obj_dir -f Vtop.mk Vtop
执行完这一步,就会把第二步中转化的cpp文件按照tb_top.cpp文件中的测试激励代码进行编译,最终生成一个名为Vtop的可执行文件。
第五步:执行可执行文件并生成最终的仿真波形文件
最后,我们直接在命令行中输入以下代码:
./obj_dir/Vtop
这一步就是运行Vtop这个可执行文件,执行完之后,就会发现在命令行的当前目录下(my_example_andgate),生成了一个waveform.vcd文件,这个文件就是按照激励文件生成的仿真波形。
(可选)第六步:编写一个Makefile文件,实现多步合一
上述步骤我们也可以通过编写一个Makefile文件,来快速执行。
在my_example_andgate目录下,新建一个Makefile,输入以下代码:
sim:
verilator -Wall --trace -cc ./vsrc/top.v --exe tb_top.cpp
# 执行完这一步,将会自动产生一个obj_dir文件夹,obj_dir里面是通过verilator将top.v转化后的Vtop.cpp以及各种头文件
# 注意:执行下一步之前,需要自己手动创建一个tb_top.cpp的文件,并在这个文件中编写好测试的testbench代码
make -C obj_dir -f Vtop.mk Vtop
# 执行这一步,就会把第一步转化的cpp文件按照tb_top.cpp文件中的测试激励代码进行编译,最终生成一个名为Vtop的可执行文件
./obj_dir/Vtop
# 这一步就是运行Vtop这个可执行文件,执行完之后,就会发现在命令行的当前目录下,生成了一个waveform.vcd文件,这个文件就是按照激励文件生成的仿真波形
然后我们就可以直接在命令行终端中输入:
make sim
即可直接得到仿真波形文件了。
实验结果(仿真波形查看):
最终的波形如下,f表现为一个与门行为:只有当a和b同时为1时,f才输出1。
PS:一生一芯中要求的双控开关(其实就是异或门),只需要改一下top.v即可。
NVBoard的简单使用
先说踩坑记录:一定记住必须要加:dut->eval(); // 更新电路状态!!!切记要在while中添加,非常重要,一定不能少。因为这行代码没加,我卡了好几个小时,还是群里的一位大佬一眼给我指出来的(跪谢)。
我是直接复制的官方的Makefile,因为我这种菜鸡根本读不懂Makefile,更别说自己发挥了。。。
我这里继续使用上一节的与门模块,我会将与门的两个输入a,b分别连接到NVBoard的SW0和SW1上,输出链接到LD0上。最终的实验结果应该是:只有当SW0和SW1都拨动开时,LD0才会点亮。
第一步:搭建实验框架
新建一个文件夹,我这里名为“my_example_nvboard”,再新建3个子文件夹和Makefile:
第二步:vsrc中编写verilog文件
打开vsrc文件夹,把上一节中编写的与门top.v文件复制进来:
第三步:constr中进行引脚绑定
打开constr文件夹,新建一个名为top.nxdc的文件。
这个文件是引脚绑定的文件,打开后输入如下代码:
top=top
a SW0
b SW1
f LD0
这个输入的格式在NVBoard官方的README.MD中说明了。我这里就是把与门的两个输入a和b连接到NVBoard的SW0和SW1引脚,输出f连接到LD0引脚。
第四步:csrc中编写NVBoard仿真cpp文件
然后打开csrc文件夹,新建一个main.cpp文件,这个就是我们最终的NVBoard仿真cpp文件。打开main.cpp文件后,我们输入以下代码:
#include <nvboard.h>
#include <Vtop.h>
static TOP_NAME dut;
void nvboard_bind_all_pins(TOP_NAME* top);
int main() {
nvboard_bind_all_pins(&dut);
nvboard_init();
while(1) {
nvboard_update();
dut.eval(); // 一定要加!否则就没有更新电路状态!一定要加!否则就没有更新电路状态! 一定要加!否则就没有更新电路状态!
}
}
其实这个main.cpp类似与上一节中的tb_top.cpp文件,只不过因为我们直接使用NVBoard了,所以就不需要自己写生成激励信号的代码了。
第五步:编写Makefile
我们的Makefile直接使用官方的代码,复制以下代码到Makefile文件中:
TOPNAME = top
NXDC_FILES = constr/top.nxdc
INC_PATH ?=
VERILATOR = verilator
VERILATOR_CFLAGS += -MMD --build -cc \
-O3 --x-assign fast --x-initial fast --noassert
BUILD_DIR = ./build
OBJ_DIR = $(BUILD_DIR)/obj_dir
BIN = $(BUILD_DIR)/$(TOPNAME)
default: $(BIN)
$(shell mkdir -p $(BUILD_DIR))
# 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")
CSRCS = $(shell find $(abspath ./csrc) -name "*.c" -or -name "*.cc" -or -name "*.cpp")
CSRCS += $(SRC_AUTO_BIND)
# rules for NVBoard
include $(NVBOARD_HOME)/scripts/nvboard.mk
# rules for verilator
INCFLAGS = $(addprefix -I, $(INC_PATH))
CXXFLAGS += $(INCFLAGS) -DTOP_NAME="\"V$(TOPNAME)\""
$(BIN): $(VSRCS) $(CSRCS) $(NVBOARD_ARCHIVE)
@rm -rf $(OBJ_DIR)
$(VERILATOR) $(VERILATOR_CFLAGS) \
--top-module $(TOPNAME) $^ \
$(addprefix -CFLAGS , $(CXXFLAGS)) $(addprefix -LDFLAGS , $(LDFLAGS)) \
--Mdir $(OBJ_DIR) --exe -o $(abspath $(BIN))
all: default
run: $(BIN)
@$^
clean:
rm -rf $(BUILD_DIR)
.PHONY: default all clean run
第六步:命令行终端运行make run
最后,只需要我们在my_example_nvboard目录下的命令行终端中输入:
make run
稍等片刻,NVBoard的界面就出现了。
实验结果
看窗口的左上角,只有当SW0和SW1都拨动后,LD0才变亮,说明是与门,结果正确。
总结
Verilator和NVBoard这两个工具我在之前的学习中从来没有使用过,而且资料较少,所以踩了很多坑,也有好几次都想放弃了。回首向来萧瑟处,也无风雨也无晴,解决问题的过程是痛苦的,但是解决之后的充实感确是满满的。
希望可以帮助和当初的我一样毫无头绪的同学一些帮助。
尚存问题
为了方便起见,我这里都使用的是组合电路(与门),将来应该使用时序电路仿真,主要的区别在于testbench的编写。
参考文献
http://t.csdnimg.cn/y8q47http://t.csdnimg.cn/y8q47j额vhttp://t.csdnimg.cn/xGYMahttp://t.csdnimg.cn/xGYMahttp://t.csdnimg.cn/xGYMahttp://t.csdnimg.cn/xGYMahttp://t.csdnimg.cn/xGYMahttp://t.csdnimg.cn/xGYMahttp://t.csdnimg.cn/xGYMa