本文授权转发自知乎用户 橘子汽水 链接:https://www.zhihu.com/people/xing-qi-55-65/posts
一、前言
本文主要介绍VCS进行verilog代码debug的基本方法。
二、三种方法
1. 使用系统函数
首先我们在编写verilog模块的testbench时,可以在里面使用一些verilog的系统函数,在运行simv文件跑仿真时,进行一些控制。例如:
$time 代表当前的仿真时间。
$display 类似C语言的printf函数,仿真时在终端上打印一些信息,比如一些变量的值。
monitor 和display类似,不同的是display在被调用的时候打印一些信息,monitor可以自动监测变量,当变量值发生变化时,便打印出信息。
$stop 调用时使仿真产生一次中断。
$finish 调用时使仿真结束。
$readmemb 用于存储器建模时的初始化,将一个文本文件里的数据,写入存储器。
readmemh readmemb 以二进制数的形式写入,
下面来看下VCS Labs 里lab1/parta 下addertb.v 的内容。
代码语言:javascript复制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 =
***", $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函数打印错误信息。并使用
下面看一个使用 $readmemb 的例子
代码语言:javascript复制`timescale 1ns/10ps
module myrom (read_data, addr, read_en_);
input read_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 @( addr or read_en_)
if (! read_en_)
read_data = mem[addr];
endmodule
my_rom_data 文件里的内容:
代码语言:javascript复制0000
0101
1100
0011
1101
0010
0011
1111
1000
1001
1000
0001
1101
1010
0001
1101
在上述ROM建模中,使用 $readmemb 将文本数据写入ROM来进行初始化。利用系统函数进行debug的方式大概介绍到这里。
2. 使用UCLI (用户命令行接口)
使用 lab2/partb 里面的源码,addertb.v 与上面的代码一致,就是将monitor部分去掉了。在判断发生error的地方,将finish 更改为
图 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 的内容。
代码语言:javascript复制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
`ifdef DUMP_VPD
$vcdpluson();
`endif
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 =
***", $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
在 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如下:
代码语言:javascript复制.PHONY:com sim debug clean
OUTPUT = adder_top
ALL_DEFINE = define DUMP_VPD
VPD_NAME = vpdfile ${OUTPUT}.vpd
VCS = vcs -sverilog v2k -timescale=1ns/1ns
-debug_pp
-o ${OUTPUT}
-l compile.log
${VPD_NAME}
${ALL_DEFINE}
SIM = ./${OUTPUT} ${VPD_NAME} -l ${OUTPUT}.log
com:
${VCS} -f verilog_file.f
sim:
${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进行联合仿真。感兴趣的同学可以查阅相关资料进行了解。