VCS入门教程(二) debug
一、前言
本文主要介绍VCS进行verilog代码debug的基本方法。
二、三种方法(第三种最常用)
1. 使用系统函数
首先我们在编写verilog模块的testbench时,可以在里面使用一些verilog的系统函数,在运行simv文件跑仿真时,进行一些控制。例如:
$time 代表当前的仿真时间。
$display 类似C语言的printf函数,仿真时在终端上打印一些信息,比如一些变量的值。
$monitor 和$display类似,不同的是$display在被调用的时候打印一些信息,$monitor可以自动监测变量,当变量值发生变化时,便打印出信息。
$stop 调用时使仿真产生一次中断。
$finish 调用时使仿真结束。
$readmemb 用于存储器建模时的初始化,将一个文本文件里的数据,写入存储器。
$readmemh $readmemb 以二进制数的形式写入,$readmemh以十六进制数写入。
下面来看下VCS Labs 里lab1/parta 下addertb.v 的内容。
module addertb; reg [7:0] a_test, b_test; wire [7:0] sum_test; reg cin_test; wire cout_test; reg [17:0] test; add8 u1(a_test, b_test, cin_test, sum_test, cout_test); initial begin $monitor("time = %d, a = %h, b = %h, sum = %h; cin = %h, cout = %h", $time, a_test, b_test, sum_test, cin_test, cout_test); end initial begin for (test = 0; test <= 18'h1ffff; test = test +1) begin cin_test = test[16]; a_test = test[15:8]; b_test = test[7:0]; #50; if ({cout_test, sum_test} !== (a_test + b_test + cin_test)) begin $display("***ERROR at time = %0d ***", $time); $display("a = %h, b = %h, sum = %h; cin = %h, cout = %h", a_test, b_test, sum_test, cin_test, cout_test); $finish; end #50; end $display("*** Testbench Successfully completed! ***"); $finish; end endmodule
上面是一个二输入加法器的testbench,
a_test和b_test 为输入,
sum_test为和,
cin_test为来自低位的进位输入,
cout_test为向高位的进位输出。
本人在里面新增了$monitor 的部分。
运行仿真时,每延迟100个时间单位,test的值变化一次,使输入发生一次变化。$monitor 监测四个变量,当任何一个发生变化时,打印出输入输出和当前仿真时间的值。当输入输出不满足加法关系时,调用$display函数打印错误信息。并使用$finish 结束仿真。若所有输入输出均满足加法关系,打印完成信息,并结束仿真。
下面看一个使用 $readmemb 的例子
`timescale1ns/10psmodulemyrom(read_data,addr,read_en_);inputread_en_;input[3:0]addr;output[3:0]read_data;reg[3:0]read_data;reg[3:0]mem[0:15];initial$readmemb("my_rom_data",mem);always@(addrorread_en_)if(!read_en_)read_data=mem[addr];endmodule
my_rom_data 文件里的内容:
0000010111000011110100100011111110001001100000011101101000011101
在上述ROM建模中,使用 $readmemb 将文本数据写入ROM来进行初始化。利用系统函数进行debug的方式大概介绍到这里。
2. 使用UCLI (用户命令行接口)
使用 lab2/partb 里面的源码,addertb.v 与上面的代码一致,就是将$monitor部分去掉了。在判断发生error的地方,将$finish 更改为 $stop。相当于每发生一次错误,终断一次仿真。
图 1
图 2
在编译指令中加入 -ucli 使用UCLI。
图 3
在仿真时会打开UCLI,并使仿真停止在 0 时刻。
图4
使用 run 继续运行仿真,当出错时,$stop被调用而使仿真停下。使用 scope 查看当前 module 名。使用 show 查看信号列表。使用 get sum_test -radix hex 查看当前某个信号的值。
使用 UCLI 进行Debug其实是非常低效的,使仿真在错误的地方停止,用命令打开一个一个“黑盒子”(module) 并查看内部信号与预期是否一致。在实际使用VCS的时候基本不用,在此简单介绍,不做过多赘述。
3. 使用DVE
在前面我们已经使用命令 ./simv -gui 。以图形化界面的方式运行仿真。以下介绍一种更为常用的方式。
还是使用lab1/parta 下的例子,修改 addertb.v 的内容。
moduleaddertb;reg[7:0]a_test,b_test;wire[7:0]sum_test;regcin_test;wirecout_test;reg[17:0]test;add8u1(a_test,b_test,cin_test,sum_test,cout_test);initialbegin`ifdefDUMP_VPD$vcdpluson();`endifendinitialbeginfor(test=0;test<=18'h1ffff;test=test+1)begincin_test=test[16];a_test=test[15:8];b_test=test[7:0];#50;if({cout_test,sum_test}!==(a_test+b_test+cin_test))begin$display("***ERROR at time = %0d ***",$time);$display("a = %h, b = %h, sum = %h; cin = %h, cout = %h",a_test,b_test,sum_test,cin_test,cout_test);$finish;end#50;end$display("*** Testbench Successfully completed! ***");$finish;endendmodule
在 addertb.v 中新增了一个 initial 块。表示如果在编译时,定义了 DUMP_VPD 这个宏,那么在仿真时,打开 $vcdpluson() 这个开关选项。
图 5
使用上图命令编译源码后仿真,+define+DUMP_VPD表示在编译时定义 DUMP_VPD 这个宏,即在仿真时,打开了$vcdpluson() 这个开关选项。
图 6
我们可以看到,在仿真完成后,生成了 vcdplus.vpd 这个文件。这个文件记录了仿真过程中所有信号的波形,可以使用 dve 打开。
图 7
通过 dve & 命令打开 dve, "&"的用途是后台打开dve,以免终端被占用。我们可以看到 dve 打开后界面为空白。
图 8
File -> Open Database
图 9
选择 vpd 文件并打开
图 10
在Hierarchy 部分,可以查看顶层模块里面的子模块,右键 -> Add to Waves 查看对应模块的波形图。
在上述方法中,在编译时通过定义一个宏,打开 testbench 中 $vcdpluson() 这个开关选项,在运行 simv 进行仿真时,VCS便把所有的波形记录下来,生成一个 .vpd 文件 (波形文件)。在dve中打开文件,即可查看仿真波形,方便之处在于波形可以发给他人查阅。
几点补充:
- 在编译时,将 -debug_all 选项 更改为 -debug_pp。打开生成 VPD 文件的功能,关掉UCLI的功能,节约编译时间。
- 在编译时,使用 +define+macro1 将宏macro1传给源代码。使用+define+macro1=value+macro2=value 将macro1和macro2 传给源文件中同名的宏。
- 在编译时,使用 +vpdfile+filename 可以更改生成 VPD 文件的文件名,默认为 vpdplus.vpd。
- 可直接使用命令: dve -vpd vcdplus.vpd & 后台打开 dve 并加载 vpd 文件,代替上面图7~图9的过程。
- 调用 $vcdpluson() 时可以加入一些参数,如果什么都不加,则默认记录顶层模块下所有子模块的信号波形。参数格式:$vcdpluson(level_number, module_instance, ... , ... )。比较重要的参数为前两个。数字设计里面的 module 是层次化/结构化的,类似于一层一层的黑盒子不断包含下去。module_instance 表示从哪一个module开始记录波形,level_number表示查看 module_instance 下子模块多少层的波形。下面使用一些例子来说明。
图 11
$vcdpluson() 或者 $vcdpluson(0, addertb) 记录 addertb 及其所有子模块的波形。
$vcdpluson(1, addertb) 只记录 addertb 层的波形。
$vcdpluson(2, addertb) 记录 addertb 层和 u1(add8) 层的波形。
$vcdpluson(3, addertb) 记录 addertb , u1(add8) ,u1(add4),low_add, high_add 层的波形。
三、对makefile的补充
在VCS入门教程(一)中,我们已经写过一个 makefile,现针对上述使用dve debug 的方法,对其做一些补充。仍使用上面 lab1/parta 内的代码。修改makefile如下:
.PHONY:comsimdebugcleanOUTPUT= adder_topALL_DEFINE= +define+DUMP_VPDVPD_NAME= +vpdfile+${OUTPUT}.vpdVCS= vcs -sverilog +v2k -timescale=1ns/1ns \ -debug_pp \ -o ${OUTPUT}\ -l compile.log \${VPD_NAME}\${ALL_DEFINE}SIM= ./${OUTPUT}${VPD_NAME} -l ${OUTPUT}.logcom:${VCS} -f verilog_file.fsim:${SIM}debug: dve -vpd ${OUTPUT}.vpd &clean: rm -rf ./csrc *.daidir *.log simv* *.key *.vpd ./DVEfiles
在终端上分别使用 make com ,make sim ,make debug ,make clean 来编译,仿真,查看波形和清理生成的文件和目录。
四、结束语
本文介绍了VCS 进行 debug 的三种方式,其中第三种是最常使用最有效的。在实际工程中,通常使用VCS生成 fsdb 格式的波形文件,将其导入另一个软件 Verdi 查看波形,代替DVE进行联合仿真。感兴趣的同学可以查阅相关资料进行了解。