提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
本文内容:
回顾:
第一:验证环境按照隔离的观念,应该分为硬件DUT,软件验证环境package,和处于信号媒介的借口interface
第二:对于软件验证环境,它需要经历建立阶段(build),连接阶段(connect),产生激励阶段(generator)和发送激励阶段(transfer)
当所有的激励发送完毕且比较完全后,才可以结束测试。
在MCDT基础上:
- 与MCDT相比较,MCDF添加了寄存器控制和状态显示功能,同时也添加了一个重要的数据整形功能。
- 实验4的文件增多是为了让整个验证环境的各组件相互独立,不同的package之间功能是相互独立的,同一个package中的个验证组件的功能也是独立的,而他们之间的同步和通信是依靠event和mailbox来实现。
提示:以下是本篇文章正文内容,请在了解mcdf设计文件及相关验证组件后阅读,内容仅供参考
一、编译-仿真
- vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_data_consistence_basic_test -l mcdf_data_consistence_basic_test_sim.log work.tb
-classdebug,提供SV类调试功能
-solvefaildebug,SV随机化失败时提示信息
-sv_seed 0,固定种子值为0
+TESTNAME=mcdf_data_consistence_basic_test,指定仿真选择的测试
-l mcdf_data_consistence_basic_test_sim.log,让仿真记录保存在特定的测试文件名称中
信箱mailbox没有快速清空的delete()方法
仿真波形:
可以看到三个通道在某段时间是并行发送的,过了一段时间后变成了顺序发送,返回代码可以看到测试代码使用的是并行fork…join块,为何还会出现这样的情况? - 原因在于mcdf_data_consistence_basic_test类中在做寄存器配置do_reg时对通道的优先级作了规定,channel0>channel1>channel2
二、测试功能
1.寄存器读写测试
测试内容:所有控制寄存器的读写测试,所有状态寄存器的读写测试
测试类名:mcdf_reg_write_read_test
vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_reg_write_read_test -l mcdf_reg_write_read_test_sim.log work.tb
测试代码
//寄存器读写测试,即检查读写值是否一致
class mcdf_reg_write_read_test extends mcdf_base_test;
function new(string name = "mcdf_reg_write_read_test");
super.new(name);
endfunction
task do_reg();
//两个动态数组,里面是3个读写寄存器和3个只读寄存器对应的地址
bit[7:0] chnl_rw_addrs[] = '{`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};
bit[7:0] chnl_ro_addrs[] = '{`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR};
//寄存器配置的位数pkg_length_width+prio_width+en_width = 6
int pwidth = `PAC_LEN_WIDTH +`PRIO_WIDTH +1;
//指定配置,这里给出了3个,就是之后要检查的
bit[31:0] check_pattern[] = '{((1<<pwidth)-1), 0, ((1<<pwidth)-1)};//(1<<pwidth)-1 = (32'b0100_0000)-1 即(64-1)=32'b0011_1111 (63)
//表示寄存器读、写配置的变量
bit[31:0] wr_val, rd_val;
//RW register write and read
foreach(chnl_rw_addrs[i])begin
foreach(check_pattern[i])begin
wr_val = check_pattern[i];
this.write_reg(chnl_rw_addrs[i], wr_val);
this.read_reg(chnl_rw_addrs[i], rd_val);
void'(this.diff_value(wr_val, rd_val));
end
end
//RO register read
foreach(chnl_ro_addrs[i]) begin
this.read_reg(chnl_ro_addrs[i], rd_val);
end
//send IDLE command
this.idle_reg();
endtask
仿真波形
可以看到,对于读写寄存器的写测试,重复三次【如果想要输入三次不同的配置,第二个foreach的循环变量应该区别于i】
写入寄存器给ch0~ch2的配置命令为32'h0000_003f,32'h0000_0000,32'h0000_003f
,cmd_data_i
波形正确;
读测试,cmd_data_o
波形正确
最后是只读寄存器的读测试,不能写入,从cmd_data_o
的信号波形可以看出只读寄存器配置随机化后为32'h0000_0020
👻为什么选用((1<<pwidth)-1)作为测试的写入数据?
原因在于控制寄存器的的高26位是保留位,无法写入,在reg.v文件里,高26位是置零的,所以读出来的一定是低六位的数据,所以在做读写测试时写入的一定是正常值,读出来的才会符合期望,比较才不会出错。
测试通过!
2.寄存器稳定性测试
测试内容:所有控制,状态寄存器保留位的读写测试
测试类名:mcdf_reg_illegal_access_test
vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_reg_illegal_access_test -l mcdf_reg_illegal_access_test_sim.log work.tb
class mcdf_reg_illegal_access_test extends mcdf_base_test;
function new(string name = "mcdf_reg_write_read_test");
super.new(name);
endfunction
task do_reg();
bit[7:0] chnl_rw_addrs[] = '{`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};
bit[7:0] chnl_ro_addrs[] = '{`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR};
int pwidth = `PAC_LEN_WIDTH +`PRIO_WIDTH +1;
bit[31:0] check_pattern[] = '{32'h0000_FF00, 32'hFFFF_0000};
bit[31:0] wr_val, rd_val;
//控制寄存器保留域读写
foreach(chnl_rw_addrs[i]) begin
foreach(check_pattern[j])begin
wr_val = check_pattern[j];
this.write_reg(chnl_rw_addrs[i], wr_val);
this.read_reg(chnl_rw_addrs[i], rd_val);
void'(this.diff_value(wr_val & ((1<<pwidth)-1), rd_val));
end
end
//状态寄存器保留域读写
foreach(chnl_rw_addrs[i]) begin
wr_val = 32'hFFFF_FF00;
this.write_reg(chnl_ro_addrs[i], wr_val);
this.read_reg(chnl_ro_addrs[i], rd_val);
void'(this.diff_value(0, rd_val & 32‘hFFFF_FF00));
end
//send IDLE command
this.idle_reg();
endtask
endclass
👻对读写寄存器的非法写入测试,为什么比较的是wr_val & ((1<<pwidth)-1)和rd_val?
读写寄存器正常工作下读取到的数据是非法数值的低6位,wr_val & ((1<<pwidth)-1)按位相与后就剩下低六位了。这样比较成功就表示寄存器即使输入了非法数值,也不会影响其对低六位有效值的读取,即寄存器的功能不会紊乱。
👻对只读寄存器的非法写入测试,为什么是0和rd_val & 32‘hFFFF_FF00比较?
状态寄存器的低8位是fifo余量,没写data所以是32,而高24位是我们需要检查是否读取到了,因此只取ra_val的高24位,正常情况下,我们读回来的高24位一定是0,因此期望值设置为0。这样比较就可以知道状态寄存器是否正常工作。
仿真波形
可以看到控制寄存器和状态寄存器的非法写入值cmd_data_i波形显示正常,并且虽然输入了非法值,但是寄存器命令的期望值cmd_data_o并没有发生紊乱,对于控制寄存器它的输出是0,因为低6位是0,符合期望,状态寄存器输出指令是32’h0000_0020 = 32 即fifo的余量,也符合期望。
寄存器对于非法地址的读写,寄存器正常工作,没有输出紊乱值。
测试通过!
3.数据通道开关测试
测试内容:对每个通道的en配置为0,测试写入值能否通过
测试类名:mcdf_channel_disable_test
vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_channel_disable_test -l mcdf_channel_disable_test_sim.log work.tb
在checker中添加任务用于检查通道关闭时,slave的时序是否正确
//检查通道关闭是否实现
task do_channel_disable_check(int id);
//after run to check it forever
forever begin
//toggle event: clk posedge and (rstn && chnl_en[id]===0) ==1
//clk,rstn rasied and chnnel disabled
@(posedge this.mcdf_vif.clk iff (this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id]===0));
//the chnl[id]'s valid is high and ready is high, this is not allowed
//when channel disabled, valid should be low
if(this.chnl_vifs[id].mon_ck.ch_valid===1 && this.chnl_vifs[id].ch_ready===1)
rpt_pkg::rpt_msg("[CHECKER]",
$sformatf("ERROR! %0t when channel disabled, ready signal rasied when valid high",$time),
rpt_pkg::ERROR,
rpt_pkg::TOP,
);
end
endtask
添加好该任务后,在checker.run中调用,以检查三个通道
task run();
fork
this.do_channel_disable_check(0);
this.do_channel_disable_check(1);
this.do_channel_disable_check(2);
this.do_compare();
this.refmod.run();
join
endtask
补全mcdf_intf接口
interface mcdf_intf(input clk, input rstn);
logic chnl_en[3];//寄存器的使能端硬件信号
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input chnl_en;
endclocking
endinterface
连接接口的另一端,设计文件中的寄存器实例的对应端口
//mcdf interface monitoring MCDF ports and signals
assign mcdf_if.chnl_en[0] = tb.dut.ctrl_regs_inst.slv0_en_o;
assign mcdf_if.chnl_en[1] = tb.dut.ctrl_regs_inst.slv1_en_o;
assign mcdf_if.chnl_en[2] = tb.dut.ctrl_regs_inst.slv2_en_o;
- mcdf_channel_disable_test采用mcdf_data_consistence_basic_test代码,只需修改en的值,这里打开0通道,关闭1,2
仿真波形如下:
从波形来看只有0通道数据输出,符合预期,但是仿真一直运行,没有report,没有$finish
有知道为什么的吗?试着跑了一下lab5的参考代码mcdf_full_random_test,也是会出现这样的情况,波形如下:
测试结果符合预期但是仍不明白仿真不结束的原因。有知道的可以在评论区交流,开始下一个测试。
4.优先级测试
测试内容:在通道打开的情况下,设置相同或不同的优先级,查看结果。
测试类名:mcdf_aribiter_priority_test
在checker中添加任务用于检查当某一通道优先级最高时且slavx与formatter都发送了请求,仲裁器是否允许数据从端发送数据,详解看代码注释。
//仲裁器优先级检查
task do_arbiter_priority_check();
int id;
//after run to check it
forever begin
//toggle event : clk posedge and (rstn && f2s_id_req===1) == 1
//clk,rstn rasied and formatter send request to arbiter
@(posedge this.arb_vif.clk iff (this.arb_vif.rstn && this.arb_vif.mon_ck.f2a_id_req===1));
//get channel id
id = this.get_slave_id_with_prio();
//after got preferential channel id, check whether the priority takes effect
if(id >= 0) begin
@(posedge this.arb_vif.clk);
//formatter request to receive chnl_x data and slvx request send it but arbiter not granted slvx send data
//this is not allowed, when slvx.request and fmt.request ==1 ,the arbiter should allow slvx to send data
if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)
rpt_pkg::rpt_msg("[CHECKER]",
$sformatf("ERROR! %0t arbiter received f2a_id_req===1 and channel[%0d] raising request with high priorty, but is not granted by arbiter",$time,id),
rpt_pkg::ERROR,
rpt_pkg::TOP,
);
end
end
endtask
//获取优先级最高的通道id
function int get_slave_id_with_prio();
int id = -1;
int prio = 999;
//use foeach loop,compare three channel priority,and only the highest priority channel id can be assigned to value id
foreach(this.arb_vif.mon_ck.slv_prios[i]) begin
//when highest priority appear && slvx.request == 1, return id
if(this.arb_vif.mon_ck.slv_prios[i] < prio && this.arb_vif.mon_ck.slv_reqs[i]===1) begin
id = i;
prio = this.arb_vif.mon_ck.slv_prios[i];
end
end
return id;
endfunction
添加好该任务后,在checker.run中调用,以检查仲裁器
task run();
fork
this.do_channel_disable_check(0);
this.do_channel_disable_check(1);
this.do_channel_disable_check(2);
this.do_arbiter_priority_check();
this.do_compare();
this.refmod.run();
join
endtask
arb_intf接口
interface arb_intf(input clk, input rstn);
logic [1:0] slv_prios[3];//register port
logic slv_reqs[3];//slave port
logic a2s_acks[3];//slave port
logic f2a_id_req;//formater port
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input slv_prios, slv_reqs, a2s_acks, f2a_id_req;
endclocking
endinterface
连接接口的另一端,设计文件中的仲裁器实例的对应端口
// arbiter interface monitoring arbiter ports
assign arb_if.slv_prios[0] = tb.dut.arbiter_inst.slv0_prio_i;
assign arb_if.slv_prios[1] = tb.dut.arbiter_inst.slv1_prio_i;
assign arb_if.slv_prios[2] = tb.dut.arbiter_inst.slv2_prio_i;
assign arb_if.slv_reqs[0] = tb.dut.arbiter_inst.slv0_req_i;
assign arb_if.slv_reqs[1] = tb.dut.arbiter_inst.slv1_req_i;
assign arb_if.slv_reqs[2] = tb.dut.arbiter_inst.slv2_req_i;
assign arb_if.a2s_acks[0] = tb.dut.arbiter_inst.a2s0_ack_o;
assign arb_if.a2s_acks[1] = tb.dut.arbiter_inst.a2s1_ack_o;
assign arb_if.a2s_acks[2] = tb.dut.arbiter_inst.a2s2_ack_o;
assign arb_if.f2a_id_req = tb.dut.arbiter_inst.f2a_id_req_i;
优先级不同时(优先级:00,10,01)的仿真波形:
优先级相同时(优先级:10,10,10)的仿真波形:
同样的从arbiter设计文件中可以看到,1和2优先级相同时取的是1通道,2和0相同时取的0通道。所以当三个都相同时0>1>2,。
按照测试要求:如果优先级相同时,arbiter应该采取轮询机制从各个通道接收数据。
轮询机制的实现:
就是在优先级相同的情况下,对存有通道id的变量id_sel_r进行判断,先让其等于任意通道id,这个是模拟其初始状态的可能值(每个可能的初始值都要有,用case实现),然后再询问出这个通道外其他两个通道的request信号slvx_req_i,如果该通道有发送请求(slvx_req_i = 1),那么通道id(id_sel_r)和数据包长度变量(a2f_pkglen_sel_r)都要更新成该通道的变量值。
👻使用case判断id时,如果初始id不是0,1,2(在这之前有判断复位信号所以id不可能是3);那么case语句默认(default)执行id为0的情况。
👻 这样在优先级相同的情况下,轮流询问了slave的request信号,当然可能会出现request都为0的情况因此整个轮询机制是在三个通道的req至少有1个是高电平,且优先级相同的情况下进行的。
👻当formatter或slvx没有发送请求时,则保持上一id和pkg_length状态
👻仿真后出现了信息报错,原因在于优先级相同时,执行了轮询机制,它是根据数据从端的req信号选择id_sel_r,与仲裁器的a2s_ack信号无关,如下图,id_sel_r在优先级不同时取决于prio;但在优先级相同时取决于slvx_req_i。
比如假设三个通道的优先级都是1,在获得最优id时,遍历过程是这样的(this.arb_vif.mon_ck.slv_prios[i] < prio),slv_prios[2]<999,所以id=2,prio=1;在下一次比较时,slv_prios[1]<1不成立,所以直接返回id=2,这时候该判断a2s_acks[2]是否不等于1了,那么如下图id_sel_r决定a2s_acks[x],这时候你返回的id就不一定等于id_sel_r,假设根据轮询机制id_sel_r=2’b00,那么a2s_acks[1] = 1, 满足if条件,就会报错。
因此在判断a2s_acks[id]时先加上三个通道优先级不相等的条件
if(this.arb_vif.mon_ck.slv_prios[0] !== this.arb_vif.mon_ck.slv_prios[1] && this.arb_vif.mon_ck.slv_prios[1] !== this.arb_vif.mon_ck.slv_prios[2])
轮询机制代码:
always @ (posedge clk_i or negedge rstn_i)
begin : CHANEL_SELECT
if (!rstn_i) id_sel_r = 2'b11;
else if (f2a_id_req_i)
begin
if({slv2_req_i,slv1_req_i,slv0_req_i} != 3'b000)
begin
if(slv0_prio_i == slv1_prio_i && slv1_prio_i == slv2_prio_i)
case(id_sel_i)
2'b00: if(slv1_req_i == 1)
begin
id_sel_r <= 2'b01;
a2f_pkglen_sel_r = slv1_pkglen_i;
end
else if(slv2_req_i == 1)
begin
id_sel_r <= 2'b10;
a2f_pkglen_sel_r = slv2_pkglen_i;
end
2'b01: if(slv2_req_i == 1)
begin
id_sel_r <= 2'b10;
a2f_pkglen_sel_r = slv2_pkglen_i;
end
else if(slv0_req_i == 1)
begin
id_sel_r <= 2'b00;
a2f_pkglen_sel_r = slv0_pkglen_i;
end
2'b10: if(slv0_req_i == 1)
begin
id_sel_r <= 2'b00;
a2f_pkglen_sel_r = slv0_pkglen_i;
end
else if(slv1_req_i == 1)
begin
id_sel_r <= 2'b01;
a2f_pkglen_sel_r = slv1_pkglen_i;
end
default: begin
id_sel_r <= 2'b00;
a2f_pkglen_sel_r = slv0_pkglen_i;
end
endcase
else
case ({slv2_req_i,slv1_req_i,slv0_req_i})
/原来优先级相同的id选择机制
endcase
end
else//这里要再添一个else语句与if({slv2_req_i,slv1_req_i,slv0_req_i} != 3'b000)对应
begin//when no request signal,keep old status
id_sel_r <= id_sel_r;
a2f_pkglen_sel_r <= a2f_pkglen_sel_r;
end
end
else //与if (f2a_id_req_i)对应
begin//when no request signal,keep old status
id_sel_r <= id_sel_r;
a2f_pkglen_sel_r <= a2f_pkglen_sel_r;
end
end
仿真部分波形及打印信息:
测试通过!
5.发包长度测试
测试内容:在通道打开的情况下,随机配置个通道的数据包长度,查看结果。
测试类名:mcdf_formatter_length_test
测试代码同mcdf_data_consistence_basic_test一样,只需要修改data_size和wr_val[2:1]为随机(使用$urandom_range
随机),检查波形do_reg()里wr_val[2:1]配置的数据包长度和do_data()内随机的data_size是否保持一致。
class mcdf_formatter_length_test extends mcdf_base_test;
function new(string name = "mcdf_formatter_length_test");
super.new(name);
endfunction
task do_reg();
bit[31:0] wr_val, rd_val;
// slv0 with len={4,8,16,32}, prio=0, en=0
wr_val = ($urandom_range(0,3)<<3)+(0<<1)+1;//32'b 001_00_1, 1左移三位+0左移1位+1
this.write_reg(`SLV0_RW_ADDR, wr_val);//write a value
this.read_reg(`SLV0_RW_ADDR, rd_val);//read the value after write
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));//compare them
// slv1 with len={4,8,16,32}, prio=1, en=0
wr_val = ($urandom_range(0,3)<<3)+(1<<1)+1;//32'b 010_10_1
this.write_reg(`SLV1_RW_ADDR, wr_val);
this.read_reg(`SLV1_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
// slv2 with len={4,8,16,32}, prio=1, en=0
wr_val = ($urandom_range(0,3)<<3)+(2<<1)+1;//32'b 011_10_1
this.write_reg(`SLV2_RW_ADDR, wr_val);
this.read_reg(`SLV2_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
// send IDLE command
this.idle_reg();
endtask
task do_formatter();
void'(fmt_gen.randomize() with {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
fmt_gen.start();
endtask
task do_data();
void'(chnl_gens[0].randomize() with {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size inside{8, 16, 32};});
void'(chnl_gens[1].randomize() with {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size inside{8, 16, 32};});
void'(chnl_gens[2].randomize() with {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size inside{8, 16, 32};});
fork
chnl_gens[0].start();
chnl_gens[1].start();
chnl_gens[2].start();
join
#10us; // wait until all data haven been transfered through MCDF
endtask
这里不需要在checker中添加检查数据包长度的任务了,原因在于checker.do_compare()里会进行这一步,如下图:
事务mont是数据从端传递来的,事务expt是寄存器参考模型out_mb[x]同步过来的事务,前者包含数据包设置的长度data_size,后者包含寄存器配置wr_val[2:1]的数据包长度,二者比较即可完成数据包长度的检查。
仿真波形:
与打印信息一致
- checker的报告信息为什么计数不是三个100?
- 为什么总结报告中计数是706,而不是707(参考代码是707)?
6.下行从端低带宽测试
测试内容:将下行fifo设置为小储存,低带宽类型,使gran
拉高延迟;检查formatter的req信号
拉高后,grant
在至少2个clk_cycles后拉高,达到模拟下行从端数据余量不足的情况,并在发生10次这样的时序后停止测试。
测试类名:mcdf_formatter_grant_test
测试代码依旧是使用mcdf_data_consistence_basic_test,需要修改的是,fifo类型以及带宽,还有就是增加数据包的数量,减少data和data,packet和packet的空闲周期,以达到庞大的数据压力,消耗fifo数据余量,这样req信号就会频繁拉高,然后再检查grant时序是否符合期望。
class mcdf_formatter_grant_test extends mcdf_base_test;
function new(string name = "mcdf_data_consistence_basic_test");
super.new(name);
endfunction
//配置了三个通道读写寄存器的写操作和读操作
task do_reg();
bit[31:0] wr_val, rd_val;
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;//32'b 001_00_1, 1左移三位+0左移1位+1
this.write_reg(`SLV0_RW_ADDR, wr_val);//write a value
this.read_reg(`SLV0_RW_ADDR, rd_val);//read the value after write
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));//compare them
// slv1 with len=16, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;//32'b 010_01_1
this.write_reg(`SLV1_RW_ADDR, wr_val);
this.read_reg(`SLV1_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
// slv2 with len=32, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;//32'b 011_10_1
this.write_reg(`SLV2_RW_ADDR, wr_val);
this.read_reg(`SLV2_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
// send IDLE command
this.idle_reg();
endtask
//对formatter下行buffer进行配置
task do_formatter();
void'(fmt_gen.randomize() with {fifo == SHORT_FIFO; bandwidth == LOW_WIDTH_WIDTH;});
fmt_gen.start();
endtask
task do_data();//add packet numbers,reduce idles of data and packet, frequent data transition, for rasiing req signal constantly
void'(chnl_gens[0].randomize() with {ntrans==300; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==8; });
void'(chnl_gens[1].randomize() with {ntrans==300; ch_id==1; data_nidles==0; pkt_nidles==1; data_size==16;});
void'(chnl_gens[2].randomize() with {ntrans==300; ch_id==2; data_nidles==0; pkt_nidles==1; data_size==32;});
fork
chnl_gens[0].start();
chnl_gens[1].start();
chnl_gens[2].start();
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass
检查时序的代码
task check_req_grant(virtual fmt_intf fmt_vif);
this.fmt_vif = fmt_vif;
forever begin
@(posedge fmt_vif.clk iff (fmt_vif.rstn && fmt_vif.mon_ck.fmt_req === 1 ));
this.delay_req_to_grant = 0;
forever begin
@(posedge fmt_vif.clk);
if(this.fmt_vif.mon_ck.fmt_grant == 1)
break;
else begin
@(posedge fmt_vif.clk);
this.delay_req_to_grant++;
end
end
if(this.delay_req_to_grant >= 2)
this.delay_success_count++;
else
$error("[ERROR] CHECKER delay req to grant :%0t formatter req===1 , but delay req to grant not beyond two ", $time);
if(this.delay_success_count == 10) begin
$display("check_delay_req_to_grant SUCCED 10 times");
$finish;
end
else continue;
end
endtask
然后在tb中调用,并传递fmt_intf例化的接口fmt_if,这样可以不用在base_test中添加额外代码。(👻也可以在base_test内声明接口,然后在set_interface里同步接口,但是不要忘记在base_test中写一个内容为空的同名的虚方法)
fork
tests[name].run();
if(name=="mcdf_formatter_grant_test") tests[name].check_req_grant(fmt_if);
join_any
仿真波形:
可以看到在前15次的req拉高以后(这里我的代码忘记添加error的计数了,如果加上的话最后计数应该会是15),紧接着下一个时钟周期grant就拉高了。在第16次以后才会出现下行从端fifo容量不足,grant延迟拉高的情况,然后在出现10次这种情况后就结束了测试。接下来可以试着修改fifo的类型和带宽为MED_FIFO和MED_WIDTH,然后查看仿真波形。
仿真跑的时间更长了。大胆猜测一下如果让仿真一直跑下去,低容量,低带宽的fifo类型是不是会花费更多的仿真时间才能跑完整个测试。
完整测试:
{SHORT_FIFO,LOW_WIDTH}
{MED_FIFO,MED_WIDTH}
{LONG_FIFO,HIGH_WIDTH}
可以看到,大容量,大带宽的fifo确实可以减少仿真时间,但对于ntrans=300,且datasize=8,16,32的,LONG_FIFO,HIGH_FIFO已经是最快的了。
测试通过!
补充:不改变tb,检查延迟并实现10次后停止仿真
- 首先是在测试类中完成check任务的编写,区别于之前在检查完10次后不直接调用系统函数$finish,使用break跳出循环结束任务。
- 然后需要将任务用到的fmt_intf接口信号与tb层连接,我们可以直接将fmt_vif的声明放在base_test父类中,然后在set_interface里同步接口即可完成接口的连接。
- 最后是调用check任务,这个任务不能调用需要和do_data()任务并行调用,且两个线程只要完成一个就可以进行do_report(),$finish(),若在do_data之前调用不会有数据发送,陷入死循环,也不能在其后调用,否则数据发送完毕依旧会陷入循环。
仿真结果: