在一些单元模块仿真时,往往需要构建一定格式的数据激励,如某个处理TCP报文的单元模块,需要构建符合TCP报文格式的激励。基于verilog的激励生成,大致有两种方法:
- txt文件法。将符合需求的数据记录于txt,仿真时调用。
- 直接合成法。利用verilog在tb中直接合成激励。
这两种方法的优点是直观,但不够灵活。其一,当激励的数据结构复杂时,构建起来比较麻烦;其二,当被测对象的输入协议改动时,往往牵一发而动全身,需要对tb做整体的检查。
利用system verilog构建单元测试可以克服上述缺点,同时还具备其它优秀特性,如带约束的随机激励生成、测试覆盖率分析。
本人根据实践,总结出基于system verilog的单元测试解决方法,希望对大家有帮助,并共同探讨。
下面以msgx_cfg模块的测试说明该方法,重点讲述如何从原来的基于verilog测试tb转为基于system verilog的测试tb。这个过渡包括四个步骤:
- 修改tb文件类型。由.v改为.sv。
- 在tb.sv中增加一个class。
- 在tb.sv中写一个数据转信号的task。
- 调用task,享受sv带来的测试快感。
1 单元仿真的激励要求
该模块结构
本文重点讲述激励产生,因此省略模块所有输出接口。该模块数据输入接口信号如下:
信号名 |
位宽(bit) |
描述 |
i_data_valid |
1 |
电平,为高表示数据有效 |
i_data |
64 |
数据 |
i_eop_p |
1 |
脉冲,数据尾标志 |
对于i_data的格式要求如下:
63:56 |
55:48 |
47:40 |
39:32 |
31:24 |
23:16 |
15:8 |
7:0 |
|
beat1 |
msg_type |
pld_len |
b |
c[47:16] |
||||
beat2 |
c[15:0] |
payload |
||||||
beat3 |
payload |
|||||||
… |
… |
其中各字段说明:
名称 |
长度(byte) |
说明 |
msg_type |
1 |
消息类型 |
pld_len |
1 |
payload的byte数,最小6 |
b |
2 |
|
c |
6 |
|
payload |
pld_len |
长度取决于pld_len |
2 修改tb
根据上述输入激励要求,利用sv快速构建单元仿真激励。
2.1 修改tb文件类型
原来msgx_cfg_tb.v的内容如下:
1 module msgx_cfg_tb; 2 3 reg clk; 4 reg rst_n; 5 reg i_data_valid; 6 reg [63:0] i_ data; 7 reg i_eop_p; 8 9 msgx_cfg DUT( 10 .clk (clk ), 11 .rst_n (rst_n ), 12 13 .i_data_valid (i_data_valid ), 14 .i_rdfifo_data (i_ data ), 15 .i_eop_p (i_eop_p ), 16 17 //其它接口,省略 18 19 ); 20 21 initial begin 22 clk = 0; 23 forever #5ns clk = ~clk; 24 end 25 26 initial begin 27 rst_n = 0; 28 #50ns; 29 rst_n = 1; 30 end 31 32 endmodule
将文件名改为msgx_cfg_tb.sv。
2.2 在tb.sv中增加一个class
在msgx_cfg_tb.sv头部,增加一个class,其作用是产生“输入激励要求”描述的数据。
1 class msg_data_class; 2 3 //声明data中的所有字段 4 rand bit[7:0] msg_type; 5 rand bit[7:0] pld_len; 6 rand bit[15:0] b; 7 rand bit[47:0] c; 8 rand bit[7:0] payload[]; 9 10 //用于存储最终数据data 11 bit[63:0] data[]; 12 13 //随机约束 14 constraint legal_payload_size_c {pld_len > 6; payload.size == pld_len;} 15 16 //自定义pack()函数,将字段打包进动态数组data 17 function void pack(); 18 data = {>>{msg_type, pld_len, b, c, payload}}; 19 endfunction 20 21 //重写class内置函数: post_randomize(),该函数在class随机化后会自动调用 22 function void post_randomize (); 23 pack(); //打包 24 endfunction : post_randomize 25 26 endclass
2.3 在tb.sv中增加一个task
现在,我们的msgx_cfg_tb.sv中有一个描述数据组成的class,一个module,接下来,需要在module内把数据转化为接口信号。我们在module内写一个task,完成这个任务。
1 module msgx_cfg_tb; 2 3 reg clk; 4 reg rst_n; 5 6 reg i_data_valid; 7 reg [63:0] i_ data; 8 reg i_eop_p; 9 10 //声明class 11 msg_data_class msg; 12 13 msgx_cfg DUT( 14 .clk (clk ), 15 .rst_n (rst_n ), 16 17 .i_data_valid (i_data_valid ), 18 .i_rdfifo_data (i_ data ), 19 .i_eop_p (i_eop_p ), 20 21 //其它接口,省略 22 ); 23 24 initial begin 25 clk = 0; 26 forever #5ns clk = ~clk; 27 end 28 29 initial begin 30 rst_n = 0; 31 #50ns; 32 rst_n = 1; 33 end 34 35 //新增task 36 task data2signal(msg_data_class msg); 37 @(posedge clk); 38 i_data_valid <= 1; 39 i_data <= 64’h0; 40 for(int i=0; i<msg.data.size; i++) begin 41 i_data <= msg.data[i]; 42 i_eop_p <= i==msg.data.size-1 ? 1:0; 43 @(posedge clk); 44 end 45 i_data_valid <= 0; 46 i_data <= 64’h0; 47 endtask 48 49 endmodule 50
当data2signal task写好后,在测试case编写过程中专注于数据层构建就可以了。
2.4调用task,享受sv带来的测试快感
让我们在module内写第一个测试case。为了便于case的可重现性,我们把每一个case封装成一个task,在主过程中调用各个task,这样做的好处是测试后期一旦有改动可以将前期case方便的再做测试。
1 //主过程 2 initial begin 3 test_case1(); 4 end 5 6 //第一个测试case 7 task test_case1(); 8 $display(“------------- start of test_case1 -------------”); 9 msg = new(); 10 wait(rst_n); 11 @(posedge clk); 12 13 //随机化 14 assert (msg.randomize with {msg_type == 8’d1;}); 15 16 //调用task: data2signal() 17 data2signal(msg); 18 $display(“------------- end of test_case1 -------------”); 19 endtask
在test_case1的task中,通过assert()语句控制随机产生msg_type字段为1的数据包,而其它一些字段(pld_len和payload的长度),由msg_data_class中的constraint约束。注意,当assert()中的约束与constraint中约束冲突时,仿真器会给出告警,此时产生的数据是不受控、没有意义的,因此仿真时需要留意告警信息,写tb时也注意做好约束规划和分配,避免产生冲突