VCS入门教程(二)

2020-09-04 17:33:04 浏览数 (1)

本文授权转发自知乎用户 橘子汽水 链接: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中打开文件,即可查看仿真波形,方便之处在于波形可以发给他人查阅。

几点补充:

  1. 在编译时,将 -debug_all 选项 更改为 -debug_pp。打开生成 VPD 文件的功能,关掉UCLI的功能,节约编译时间。
  2. 在编译时,使用 define macro1 将宏macro1传给源代码。使用 define macro1=value macro2=value 将macro1和macro2 传给源文件中同名的宏。
  3. 在编译时,使用 vpdfile filename 可以更改生成 VPD 文件的文件名,默认为 vpdplus.vpd。
  4. 可直接使用命令:dve -vpd vcdplus.vpd & 后台打开 dve 并加载 vpd 文件,代替上面图7~图9的过程。
  5. 调用 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进行联合仿真。感兴趣的同学可以查阅相关资料进行了解。

0 人点赞