在一些单元模块仿真时,往往需要构建一定格式的数据激励,如某个处理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带来的测试快感。
该模块结构
本文重点讲述激励产生,因此省略模块所有输出接口。该模块数据输入接口信号如下:
信号名
位宽(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
根据上述输入激励要求,利用sv快速构建单元仿真激励。
原来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。
在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
现在,我们的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编写过程中专注于数据层构建就可以了。
让我们在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时也注意做好约束规划和分配,避免产生冲突
转载于:https://www.cnblogs.com/shanelan/p/4014441.html
相关资源:各显卡算力对照表!