提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
主要内容:
实验5主要是认识如何定义覆盖率,如何从验证计划到测试用例的实现,最后再到覆盖率的量化。主要掌握两种验证量化数据即代码覆盖率和功能覆盖率。从这两种数据,我们可以掌握何时可以结束验证,确认验证的完备性。
覆盖率模型已经给出:主要定义了相应测试的覆盖组和采样函数,需要注意的是
- 单个覆盖点声明type_option.weight = 0;是必须的,否则cross会将覆盖点的默认仓也算上,这样cross覆盖率就不是我们想要的了
- 采样任务中规定的采样事件要尽可能的避免在每个时钟上升沿采样(也就是说如果采样有其他条件一定要加上),频繁采样会消耗很多的时间,使仿真变慢。
- 采样的不一定是硬件信号,也可能是自定义的软件变量,如delay_req_to_grant
实验通过要求:
代码覆盖率大于90%,功能覆盖率大于90%
class mcdf_coverage;
local virtual chnl_intf chnl_vifs[3];
local virtual arb_intf arb_vif;
local virtual mcdf_intf mcdf_vif;
local virtual reg_intf reg_vif;
local virtual fmt_intf fmt_vif;
local string name;
local int delay_req_to_grant;
covergroup cg_mcdf_reg_write_read;
addr: coverpoint reg_vif.mon_ck.cmd_addr {
type_option.weight = 0;
bins slv0_rw_addr = {`SLV0_RW_ADDR};
bins slv1_rw_addr = {`SLV1_RW_ADDR};
bins slv2_rw_addr = {`SLV2_RW_ADDR};
bins slv0_r_addr = {`SLV0_R_ADDR };
bins slv1_r_addr = {`SLV1_R_ADDR };
bins slv2_r_addr = {`SLV2_R_ADDR };
}
cmd: coverpoint reg_vif.mon_ck.cmd {
type_option.weight = 0;
bins write = {`WRITE};
bins read = {`READ};
bins idle = {`IDLE};
}
cmdXaddr: cross cmd, addr {
bins slv0_rw_addr = binsof(addr.slv0_rw_addr);
bins slv1_rw_addr = binsof(addr.slv1_rw_addr);
bins slv2_rw_addr = binsof(addr.slv2_rw_addr);
bins slv0_r_addr = binsof(addr.slv0_r_addr );
bins slv1_r_addr = binsof(addr.slv1_r_addr );
bins slv2_r_addr = binsof(addr.slv2_r_addr );
bins write = binsof(cmd.write);
bins read = binsof(cmd.read );
bins idle = binsof(cmd.idle );
bins write_slv0_rw_addr = binsof(cmd.write) && binsof(addr.slv0_rw_addr);
bins write_slv1_rw_addr = binsof(cmd.write) && binsof(addr.slv1_rw_addr);
bins write_slv2_rw_addr = binsof(cmd.write) && binsof(addr.slv2_rw_addr);
bins read_slv0_rw_addr = binsof(cmd.read) && binsof(addr.slv0_rw_addr);
bins read_slv1_rw_addr = binsof(cmd.read) && binsof(addr.slv1_rw_addr);
bins read_slv2_rw_addr = binsof(cmd.read) && binsof(addr.slv2_rw_addr);
bins read_slv0_r_addr = binsof(cmd.read) && binsof(addr.slv0_r_addr);
bins read_slv1_r_addr = binsof(cmd.read) && binsof(addr.slv1_r_addr);
bins read_slv2_r_addr = binsof(cmd.read) && binsof(addr.slv2_r_addr);
}
endgroup
covergroup cg_mcdf_reg_illegal_access;
addr: coverpoint reg_vif.mon_ck.cmd_addr {
type_option.weight = 0;
bins legal_rw = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};
bins legal_r = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR};
bins illegal = {[8'h20:$], 8'hC, 8'h1C};
}
cmd: coverpoint reg_vif.mon_ck.cmd {
type_option.weight = 0;
bins write = {`WRITE};
bins read = {`READ};
}
wdata: coverpoint reg_vif.mon_ck.cmd_data_m2s {
type_option.weight = 0;
bins legal = {[0:'h3F]};
bins illegal = {['h40:$]};
}
rdata: coverpoint reg_vif.mon_ck.cmd_data_s2m {
type_option.weight = 0;
bins legal = {[0:'hFF]};
illegal_bins illegal = default;
}
cmdXaddrXdata: cross cmd, addr, wdata, rdata {
bins addr_legal_rw = binsof(addr.legal_rw);
bins addr_legal_r = binsof(addr.legal_r);
bins addr_illegal = binsof(addr.illegal);
bins cmd_write = binsof(cmd.write);
bins cmd_read = binsof(cmd.read);
bins wdata_legal = binsof(wdata.legal);
bins wdata_illegal = binsof(wdata.illegal);
bins rdata_legal = binsof(rdata.legal);
bins write_illegal_addr = binsof(cmd.write) && binsof(addr.illegal);
bins read_illegal_addr = binsof(cmd.read) && binsof(addr.illegal);
bins write_illegal_rw_data = binsof(cmd.write) && binsof(addr.legal_rw) && binsof(wdata.illegal);
bins write_illegal_r_data = binsof(cmd.write) && binsof(addr.legal_r) && binsof(wdata.illegal);
}
endgroup
covergroup cg_channel_disable;
ch0_en: coverpoint mcdf_vif.mon_ck.chnl_en[0] {
type_option.weight = 0;
wildcard bins en = {1'b1};
wildcard bins dis = {1'b0};
}
ch1_en: coverpoint mcdf_vif.mon_ck.chnl_en[1] {
type_option.weight = 0;
wildcard bins en = {1'b1};
wildcard bins dis = {1'b0};
}
ch2_en: coverpoint mcdf_vif.mon_ck.chnl_en[2] {
type_option.weight = 0;
wildcard bins en = {1'b1};
wildcard bins dis = {1'b0};
}
ch0_vld: coverpoint chnl_vifs[0].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
ch1_vld: coverpoint chnl_vifs[1].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
ch2_vld: coverpoint chnl_vifs[2].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
chenXchvld: cross ch0_en, ch1_en, ch2_en, ch0_vld, ch1_vld, ch2_vld {
bins ch0_en = binsof(ch0_en.en);
bins ch0_dis = binsof(ch0_en.dis);
bins ch1_en = binsof(ch1_en.en);
bins ch1_dis = binsof(ch1_en.dis);
bins ch2_en = binsof(ch2_en.en);
bins ch2_dis = binsof(ch2_en.dis);
bins ch0_hi = binsof(ch0_vld.hi);
bins ch0_lo = binsof(ch0_vld.lo);
bins ch1_hi = binsof(ch1_vld.hi);
bins ch1_lo = binsof(ch1_vld.lo);
bins ch2_hi = binsof(ch2_vld.hi);
bins ch2_lo = binsof(ch2_vld.lo);
bins ch0_en_vld = binsof(ch0_en.en) && binsof(ch0_vld.hi);
bins ch0_dis_vld = binsof(ch0_en.dis) && binsof(ch0_vld.hi);
bins ch1_en_vld = binsof(ch1_en.en) && binsof(ch1_vld.hi);
bins ch1_dis_vld = binsof(ch1_en.dis) && binsof(ch1_vld.hi);
bins ch2_en_vld = binsof(ch2_en.en) && binsof(ch2_vld.hi);
bins ch2_dis_vld = binsof(ch2_en.dis) && binsof(ch2_vld.hi);
}
endgroup
covergroup cg_arbiter_priority;
ch0_prio: coverpoint arb_vif.mon_ck.slv_prios[0] {
bins ch_prio0 = {0};
bins ch_prio1 = {1};
bins ch_prio2 = {2};
bins ch_prio3 = {3};
}
ch1_prio: coverpoint arb_vif.mon_ck.slv_prios[1] {
bins ch_prio0 = {0};
bins ch_prio1 = {1};
bins ch_prio2 = {2};
bins ch_prio3 = {3};
}
ch2_prio: coverpoint arb_vif.mon_ck.slv_prios[2] {
bins ch_prio0 = {0};
bins ch_prio1 = {1};
bins ch_prio2 = {2};
bins ch_prio3 = {3};
}
endgroup
covergroup cg_formatter_length;
id: coverpoint fmt_vif.mon_ck.fmt_chid {
bins ch0 = {0};
bins ch1 = {1};
bins ch2 = {2};
illegal_bins illegal = default;
}
length: coverpoint fmt_vif.mon_ck.fmt_length {
bins len4 = {4};
bins len8 = {8};
bins len16 = {16};
bins len32 = {32};
illegal_bins illegal = default;
}
endgroup
covergroup cg_formatter_grant();
delay_req_to_grant: coverpoint this.delay_req_to_grant {
bins delay1 = {1};
bins delay2 = {2};
bins delay3_or_more = {[3:10]};
illegal_bins illegal = {0};
}
endgroup
function new(string name="mcdf_coverage");
this.name = name;
this.cg_mcdf_reg_write_read = new();
this.cg_mcdf_reg_illegal_access = new();
this.cg_channel_disable = new();
this.cg_arbiter_priority = new();
this.cg_formatter_length = new();
this.cg_formatter_grant = new();
endfunction
task run();
fork
this.do_reg_sample();
this.do_channel_sample();
this.do_arbiter_sample();
this.do_formater_sample();
join
endtask
task do_reg_sample();
forever begin
@(posedge reg_vif.clk iff reg_vif.rstn);
this.cg_mcdf_reg_write_read.sample();
this.cg_mcdf_reg_illegal_access.sample();
end
endtask
task do_channel_sample();
forever begin
@(posedge mcdf_vif.clk iff mcdf_vif.rstn);
if(chnl_vifs[0].mon_ck.ch_valid===1
|| chnl_vifs[1].mon_ck.ch_valid===1
|| chnl_vifs[2].mon_ck.ch_valid===1)
this.cg_channel_disable.sample();
end
endtask
task do_arbiter_sample();
forever begin
@(posedge arb_vif.clk iff arb_vif.rstn);
if(arb_vif.slv_reqs[0]!==0 || arb_vif.slv_reqs[1]!==0 || arb_vif.slv_reqs[2]!==0)
this.cg_arbiter_priority.sample();
end
endtask
task do_formater_sample();
fork
forever begin
@(posedge fmt_vif.clk iff fmt_vif.rstn);
if(fmt_vif.mon_ck.fmt_req === 1)
this.cg_formatter_length.sample();
end
forever begin
@(posedge fmt_vif.mon_ck.fmt_req);
this.delay_req_to_grant = 0;
forever begin
if(fmt_vif.fmt_grant === 1) begin
this.cg_formatter_grant.sample();
break;
end
else begin
@(posedge fmt_vif.clk);
this.delay_req_to_grant++;
end
end
end
join
endtask
function void do_report();
string s;
s = "\n---------------------------------------------------------------\n";
s = {s, "COVERAGE SUMMARY \n"};
s = {s, $sformatf("total coverage: %.1f \n", $get_coverage())};
s = {s, $sformatf(" cg_mcdf_reg_write_read coverage: %.1f \n", this.cg_mcdf_reg_write_read.get_coverage())};
s = {s, $sformatf(" cg_mcdf_reg_illegal_access coverage: %.1f \n", this.cg_mcdf_reg_illegal_access.get_coverage())};
s = {s, $sformatf(" cg_channel_disable_test coverage: %.1f \n", this.cg_channel_disable.get_coverage())};
s = {s, $sformatf(" cg_arbiter_priority_test coverage: %.1f \n", this.cg_arbiter_priority.get_coverage())};
s = {s, $sformatf(" cg_formatter_length_test coverage: %.1f \n", this.cg_formatter_length.get_coverage())};
s = {s, $sformatf(" cg_formatter_grant_test coverage: %.1f \n", this.cg_formatter_grant.get_coverage())};
s = {s, "---------------------------------------------------------------\n"};
rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
endfunction
virtual function void set_interface(virtual chnl_intf ch_vifs[3]
,virtual reg_intf reg_vif
,virtual arb_intf arb_vif
,virtual fmt_intf fmt_vif
,virtual mcdf_intf mcdf_vif
);
this.chnl_vifs = ch_vifs;
this.arb_vif = arb_vif;
this.reg_vif = reg_vif;
this.fmt_vif = fmt_vif;
this.mcdf_vif = mcdf_vif;
if(chnl_vifs[0] == null || chnl_vifs[1] == null || chnl_vifs[2] == null)
$error("chnl interface handle is NULL, please check if target interface has been intantiated");
if(arb_vif == null)
$error("arb interface handle is NULL, please check if target interface has been intantiated");
if(reg_vif == null)
$error("reg interface handle is NULL, please check if target interface has been intantiated");
if(fmt_vif == null)
$error("fmt interface handle is NULL, please check if target interface has been intantiated");
if(mcdf_vif == null)
$error("mcdf interface handle is NULL, please check if target interface has been intantiated");
endfunction
endclass
- $get_coverage:用来获取当前测试平台总体覆盖率,其值由所有的covergroup类型的覆盖率决定;
- get_coverage:用来获取当前实例所对应的covergroup的覆盖率;
- get_inst_coverage:用来获取当前covergroup实例的覆盖率
硅芯思见:你知道get_coverage和get_inst_coverage得到的都是什么覆盖率吗
为了方便测试,采用全新的测试用例mcdf_full_random_test,它包含lab4中编写的6种测试。代码如下:
class mcdf_full_random_test extends mcdf_base_test;
function new(string name = "mcdf_full_random_test");
super.new(name);
endfunction
task do_reg();
bit[7:0] addr;
bit[31:0] wr_val, rd_val;
// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
this.write_reg(`SLV0_RW_ADDR, wr_val);
this.read_reg(`SLV0_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));
// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,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"));
// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,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"));
// write or read illegal address
addr = $urandom_range(8'h20, 8'hFF);
this.write_reg(addr, 32'hDEAD_BEEF);//非法地址写入非法值
this.read_reg(addr, rd_val);//非法地址的读取
// send IDLE command
this.idle_reg();
endtask
task do_formatter();
void'(fmt_gen.randomize() with {fifo inside {SHORT_FIFO, ULTRA_FIFO}; bandwidth inside {LOW_WIDTH, ULTRA_WIDTH};});
fmt_gen.start();
endtask
task do_data();
void'(chnl_gens[0].randomize() with {ntrans inside {[400:600]}; ch_id==0; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};});
void'(chnl_gens[1].randomize() with {ntrans inside {[400:600]}; ch_id==1; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};});
void'(chnl_gens[2].randomize() with {ntrans inside {[400:600]}; ch_id==2; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,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
endclass
提示:以下是本篇文章正文内容,下面案例可供参考
一、编译
对设计文件设置额外的覆盖率编译选项
选择compile >> compile properties,勾选以下选项。
然后完成所有文件的编译,这将会在编译DUT时生成代码覆盖率的模型,测试文件不需要添加代码覆盖率选项。代码覆盖率指的是设计文件的代码!
二、仿真
仿真命令如下:
vsim -i -classdebug -solvefaildebug -coverage -coverstore <自定义存储路径> -testname mcdf_full_random_test -sv_seed random +TESTNAME=mcdf_full_random_test -l mcdf_full_random_test.log work.tb
-coverage 仿真时生成代码覆盖率数据。PS:功能覆盖率是默认生成的
-coverstore <自定义存储路径> 仿真结束时,生成覆盖率数据并保存到自定义的路径
-testname TESTNAME 添加仿真的测试用例名称,这样在仿真结束时会在自定义的路径下产生一个覆盖率文件{TESTNAME}_{SV_SEED}.data.由于仿真时的种子数都是随机的,所以每次仿真都会生成这样的一个文件。
最后在仿真finish以后,一定要选择菜单栏Simulate >> end simulate或者输入quit -sim命令来结束仿真,这样才会得到覆盖率文件。
查看覆盖率
只有在仿真过程中或停止仿真时才能查看,一旦结束仿真quit -sim就无法查看
功能覆盖率:View >> Coverage >> Code Coverage Analysis、Covergroups
代码覆盖率:Analysis中查看dut实例的代码覆盖率
2.合并覆盖率
在进行多次随机测试后,每次测试都会生成相应的数据库,接下来可以将覆盖率数据合并。
vcover merge -out merged_coverage.ucdb <自定义路径>
然后打开文件,需要注意的是如果某个测试在仿真时报错,那么它产生的数据库则无法合并,合并时会跳过该测试的数据库。
可以看到覆盖率已达到81.9%,而有两个功能已经达到了100%,这意味着我们下一次测试时可以不用检查这两个功能了,就比如channel的使能端我们可以打开,而不需要在0和1之间随机。
再次随机测试了几次,覆盖率已经达到了90.3%,想要继续提高覆盖率可以针对未被覆盖到的交叉覆盖点bins值进行定向测试。
3.分析覆盖率
为了更直观的查看覆盖率,可以生成HTML报告。
Tools >>Coverage Report >> HTML
HTML报告更加详细,覆盖率实际上是90.27%而非Questa sim/Covergroups显示的90.3%
4.提高覆盖率的定向测试
提高功能覆盖率
缺少对状态寄存器合法地址的读操作
缺少对控制寄存器合法地址的非法数据写入,对状态寄存器的写入操作。
有没有很熟悉?没错这就是实验4中我们编写的mcdf_reg_illegal_access_test,所以只需要跑一次该测试就可以了。
从HTML报告可以看到最终我们的功能覆盖率达到了100%,代码覆盖率达到了90.95%
提高代码覆盖率
检查代码覆盖率发现大部分未执行的代码都发生在arbiter中,是不是也很熟悉?没错这就是在实验4中我们编写的mcdf_arbiter_priority_test,在这个测试中我们添加了优先级相同时arbiter的轮询机制,因此我们只需要多测试几次优先级相同的测试即可提高代码覆盖率
代码覆盖率只提高了一点,后续测试无法再提高代码覆盖率。 如何继续提高代码覆盖率?
- 实际上有些代码没有被覆盖是设计所允许的(如寄存器保留域的翻转覆盖),这部分代码可以将它排除,直接在Analysis中右键选中代码Exclude With Comment 选择第二个选项,然后备注原因。
, - 当我们重新打开ucdb文件后,就会看到排除的代码失效了,排除的部分如何重新加载,我们需要将排除的代码保存下来,然后键入命令
do ./exclusion.do
疑惑
在进行full_random测试时,偶尔会出现仿真一直进行无法停止的情况,尚不清楚是什么原因。本来以为是某个通道关闭后造成的情况(实验4在通道关闭测试时出现了相同的问题),但是在将通道全部打开后还是会出现这样的情况。
8.18补充:针对这种情况可以设置watchdog与do_data并行执行,避免仿真持续等待
local int timeout = 10; // 10 * ms
// timeout watchdog to avoid simulation pending
virtual task do_watchdog();
`uvm_info(get_type_name(), "=====================WATCHDOG GUARDING=====================", UVM_LOW)
#(this.timeout * 1ms);
`uvm_info(get_type_name(), "=====================WATCHDOG BARKING=====================", UVM_LOW)
endtask