在一些单元模块仿真时,往往需要构建一定格式的数据激励,如某个处理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时也注意做好约束规划和分配,避免产生冲突