[原创]利用system verilog快速构建单元仿真

时间:2021-12-28 22:28:36

在一些单元模块仿真时,往往需要构建一定格式的数据激励,如某个处理TCP报文的单元模块,需要构建符合TCP报文格式的激励。基于verilog的激励生成,大致有两种方法:

  1. txt文件法。将符合需求的数据记录于txt,仿真时调用。

  2. 直接合成法。利用verilog在tb中直接合成激励。

这两种方法的优点是直观,但不够灵活。其一,当激励的数据结构复杂时,构建起来比较麻烦;其二,当被测对象的输入协议改动时,往往牵一发而动全身,需要对tb做整体的检查。

利用system verilog构建单元测试可以克服上述缺点,同时还具备其它优秀特性,如带约束的随机激励生成、测试覆盖率分析。

本人根据实践,总结出基于system verilog的单元测试解决方法,希望对大家有帮助,并共同探讨。

下面以msgx_cfg模块的测试说明该方法,重点讲述如何从原来的基于verilog测试tb转为基于system verilog的测试tb。这个过渡包括四个步骤:

  1. 修改tb文件类型。由.v改为.sv。

  2. 在tb.sv中增加一个class。

  3. 在tb.sv中写一个数据转信号的task。

  4. 调用task,享受sv带来的测试快感。

1 单元仿真的激励要求

该模块结构

[原创]利用system verilog快速构建单元仿真[原创]利用system verilog快速构建单元仿真[原创]利用system verilog快速构建单元仿真[原创]利用system verilog快速构建单元仿真[原创]利用system verilog快速构建单元仿真


本文重点讲述激励产生,因此省略模块所有输出接口。该模块数据输入接口信号如下:

信号名

位宽(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时也注意做好约束规划和分配,避免产生冲突