覆盖率是衡量激励生成种类和功能点验证的量化指标。
覆盖率类型
- 覆盖率是衡量设计验证完备性的一个通用词语。
- 随着测试逐步覆盖各种合理的组合,仿真过程会慢慢勾画出你的设计情况。
- 覆盖率工具会在仿真过程中收集信息,然后进行后续处理并且得到覆盖率报告。
- 通过这个报告找出覆盖之外的盲区,然后修改现有测试或者创建新的测试来填补这些盲区。
- 这个过程可以一直迭代进行,直到你对覆盖率满意为止。
- 可以使用一个反馈回路来分析覆盖率的结果,并决定采取哪种行动来达到100%的覆盖率。
- 首要的选择是使用更多的种子来运行现有的测试程序。
- 当大量种子依然对于覆盖率增长没有帮助时,需要建立新的约束。只有在确实需要的时候才会求助于创建定向测试。
代码覆盖率
(语句<statement>覆盖率、条件<conditin>覆盖率、决策<branch>覆盖率、事件<event>覆盖率、跳转<toggle>覆盖率、状态机<finite stage machine>覆盖率)
- 不添加任何额外的HDL代码,(只收集设计代码)工具会通过分析源代码和增加隐藏代码来自动完成代码覆盖率的统计。
- 当运行完所有测试,代码覆盖率工具便会创建相应的数据库。
- 仿真器都带有代码覆盖率的工具,覆盖率数据也可被转换为可读格式。
行覆盖率:多少行代码已经被执行过。
路径覆盖率:在穿过代码和表达式的路径中有哪些已经被执行过。
翻转覆盖率:哪些单位比特变量的值为0或1。
状态机覆盖率:状态机哪些状态和状态转换已经被访问过。
- 代码覆盖率用于衡量执行了设计中的多少代码(应该关注设计代码的分析而不是测试平台)
- 未经测试的设计代码里可能隐藏硬件漏洞,也可能仅仅就是冗余的代码。
- 代码覆盖率衡量的是测试对于硬件设计描述的"实现"究竟测试得有多彻底,而非针对验证计划。
- 代码覆盖率达到了100%,并不意味着验证的工作已经完成,但代码覆盖率100%是验证工作完备性的必要条件。(存在dead code导致达不到100%,一般会设置预值)
代码覆盖率是用来衡量RTL代码是否被充分运行的指标。覆盖率工具会在仿真过程中收集信息,然后进行后续处理并且得到覆盖率报告。仿真工具会自动收集代码覆盖率,不需要额外的代码即可完成
断言覆盖率
- 用于一次性地或在一段时间对一个或多个设计信号在逻辑或者时序上的声明性代码
- 断言可以跟随设计和测试平台一起仿真,也可以被形式验证工具所证实
- 可以使用sv的程序性代码编写等效性检查,但使用SVA来表达会更容易
- 常用于查找错误,,例如两个信号是否应该相斥,或者请求与许可信号之间的时序
- 一旦检测到错误,仿真可以立即停止
- 可以使用cover property来测量这些关心的信号值或者状态是否发生
- 仿真结束时,仿真工具可以自动生成断言覆盖率数据。
- 断言覆盖率数据以及其他覆盖率数据都会被集成在同一个覆盖率数据库中,verifier可以对其展开分析
基于动态仿真或者硬件加速的断言覆盖率;基于形式验证的静态断言覆盖率
漏洞率曲线
- 在一个项目实施期间,你应该保持追踪每周有多少个漏洞被发现。
- —开始,当你创建测试程序时,通过观察可能就会发现很多漏洞。当设计逐渐稳定时,你需要利用自动化的检查方式来协助发现可能的漏洞。
- 在设计临近流片时,漏洞率会下降,甚至有望为零。即便如此,验证工作仍然不能结束。
- 每次漏洞率下降时,就应该寻找各种不同的办法去测试可能的边界情况(corner case) 。
- 漏洞率可能每周都有变化,这跟很多因素都有关。不过漏洞率如果出现意外的变化,可能预示着潜在的问题。
功能覆盖率
- 验证的目的就是确保设计在实际环境中的行为正确。
- 功能描述文档详细说明了设计应该如何运行,而验证计划则列出了相应的功能应该如何激励、验证和测量。
- 当你收集测量数据希望找出那些功能已经被覆盖时,你其实就是在计算"设计"的覆盖率。
- 功能覆盖率是和功能设计意图紧密相连的,有时也被称为"描述覆盖率”,而代码覆盖率则是衡量设计的实现情况。
- 某个功能在设计中可以被遗漏,代码覆盖率不能发现这个错误,但是功能覆盖率可以。
- 每一次仿真都会产生一个带有覆盖率信息的数据库,记录随机游走的轨迹。
- 把这些信息全部合并在一起就可以得到功能覆盖率,从而衡量整体的进展程度。
- 通过分析覆盖率数据可以决定如何修改回归测试集。
- 如果覆盖率在稳步增长,那么添加新种子或者加长测试时间即可。
- 如果覆盖率增速放缓,那么需要添加额外的约束来产生更多"有意思"的激励。
- 如果覆盖率停止增长,然而设计某些测试点没有被覆盖到,那么就需要创建新的测试了。
- 如果覆盖率为100%但依然有新的设计漏洞,那么覆盖率可能没有覆盖到设计中的某些设计功能区域。
功能覆盖策略
收集信息而非数据
- 对于MCDF,你需要关心的是合法的寄存器地址和非法的寄存器地址,可写的寄存器域和非法的寄存器域,而不是具体的寄存器地址数值。
- 一旦关注的地方着眼于感兴趣的状态,而不是具体数值,那么这对于你如何定义功能覆盖率,以及如何收集信息会减轻很大的负担。
- 设计信号如果数量范围太大,应该拆分为多个小范围再加上边界情况。
只测量需要的内容
- Verifier需要懂得,在使能覆盖率收集时,这一特性会降低很大的仿真性能。
- 由于收集功能覆盖率数据的开销很大,所以应该只测量你会用来分析并且改进测试的那部分数据。
- 同时也需要设定合理的覆盖率采样的事件,一方面提升采样效率,一方面也可以降低收集覆盖率的开销。
验证的完备性
- 完备的覆盖率测量结果和漏洞增长曲线,可以帮助确认设计是否被完整地验证过。
- 如果代码覆盖率低但功能覆盖率高,这说明验证计划不完整,测试没有执行设计的所有代码。
- 如果代码覆盖率高但功能覆盖率低,这说明即使测试平台很好地执行了设计的所有代码,但是测试还是没有把设计定位到所有感兴趣的状态上。(或者设计可能没有实现某一部分功能)
- 你的目标是同时驱动高的代码覆盖率和功能覆盖率。
覆盖组
与类相似,一次定义后便可以多次实例化
covergroup要注意采样哪些变量、数值(也可不定义,默认关心所有可能值)以及什么时候采样。(采样事件)
- covergroup可以包含一个或者多个coverpoint,且全都在同一时间采集
- covergroup可以定义在类中,也可以定义在interface或者module中
- covergroup可以采样任何可见的变量(即coverpoint),如程序变量、接口信号或者设计端口
- 一个类里可以包含多个covergroup,每个covergroup可以根据需要自行使能或禁止
- 每个covergroup可以定义单独的触发采样事件,允许从多个源头收集数据
- covergroup必须例化才可以用来收集数据
constraint、randmize、covergroup都可以enable/disable
class Transactor;
Transaction tr;
mailbox mbx_in;
covergroup CovPort; //covport为covergroup名字 //@() 采样方式B 等待特定信号采样
coverpoint tr.port; 此处是采样的变量(还可指定数值),不是coverpoint的名字 coverpoint衡量变量
endgroup
function new(mailbox mbx_in); //maibox不用例化吗
CovPort=new(); //covport此处为类型、实例,名字一样 例化方式2:covport(类型) cg1(实例)
this.mbx_in=mbx_in; //=new()方便多次例化
endfunction
task main;
forever begin
tr=mbx_in.get;
ifc.cb.port <= tr.port;
ifc.cb.data <= tr.data;
CovPort.sample(); //实例名称,采样方式A
end
endtask
endclass
covergroup的采样触发
- covergroup由采样的数据和数据被采样的事件构成,当两条件都准备好以后,测试平台会触发covergroup。该过程可以通过使用sample()函数完成,也可以在covergroup中采样阻塞表达式或者使用wait或@实现在信号或者事件上的阻塞
- 若希望显式地触发covergroup采样或者不存在采样时刻的信号或事件,又或者一个covergroup被例化为多个实例需要单独触发,那么可以使用sample()方法
- 若想借助已有事件或者信号触发covergroup,可以在covergroup声明中使用阻塞语句。相比直接调用sample(),事件的好处在于能借助已有的事件。
event trans_ready; covergroup CovPort @(trans_ready); //也有@posedge clk coverpoint ifc.cb.port; endgroup
数据采样
- 当在coverpoint指定采样一个变量或表达式时,sv会创建很多仓(bin)来记录每个数值被捕捉到的次数,bin是衡量功能覆盖率都基本单位。sv默认会为coverpoint创建bin,用户可以自己定义bin的采样域
- covergroup中可以定义多个coverpoint,coverpoint中可以自定义多个cover bin或者SV帮助自动定义多个cover bin。
- 每次covergroup采样,SV都会在一个或者多个cover bin中留下标记,用来记录采样时变量的数值和匹配的cover bin。
- 仿真之后,可以使用分析工具读取工具这些数据库来生成覆盖率报告,包含了各部分和总体的覆盖率
- 为了计算覆盖率,首先需要确定可能数值的个数,这被称为域。
- 覆盖率就是采样值的数目除以bin的数目,如3bit变量域为0:7,正常会分配8个bin,仿真过程若有7个值被采样,那么覆盖率为7/8。数据变量域范围过大又没有指定bin,默认分配64个bin。可通过covergroup的选项auto_bin_max来指定自动创建bin的最大数目。实际建议自行定义bin,或者减少auto_bin_max的数值。
- 所有coverpoint覆盖率最终构成covergroup覆盖率,所有covergroup覆盖率构成整体功能覆盖率
covergroup CoverPort;
options.auto_bin_max=8; //所有coverpoint auto_bin数量等于8
coverpoint tr.port;
{ options.auto_bin_max=2;} //特定coverpoint auto_bin数量=2 特定优先级高于默认
endgroup
covergroup CovKind;
coverpoint tr.kind{
bins zero = {0}; //注意是bins,一个仓代表kind==0;
bisn lo={[1:3],5}; //一个仓代表1:3和5
bins hi[]={[8:$]}; //8个独立的仓代表8:15
bins misc =default; //一个仓代表剩下的所有值
} //没有分号
Bins # hits at least 覆盖率10/11
hi_8 0 1
hi_9 5 1
hi_a 3 1
hi_b 4 1
hi_c 2 1
hi_d 2 1
hi_e 9 1
hi_f 4 1
lo 16 1
misc 15 1
zero 1 1
条件覆盖率
- 用关键词iff给coverpoint(也可用于covergroup)添加条件,常用于复位期间关闭覆盖以忽略不合理的条件触发
- 也可以用start和stop函数控制covergroup各个独立实例
covergroup CoverPort;
coverpoint port iff
(!bus_if.reset); //ck.sample采样时还需满足iff后面的条件
endgroup
initial begin
CovPort ck=new();
#1ns;
ck.stop(); //停止采样,任何采样不会记录到里面,即使调用sample也无效,直到start
bus_if.reset=1;
#100ns bus if.reset=0;
ck.start();
……
end
翻转覆盖率
记录变量从A值到B值的跳转情况,还可以确定任何长度的翻转次数
covergroup CovPort;
coverpoint port {
bins t1=(0=>1),(0=>2),(0=>3); //任一满足,bin就算采样到 甚至可以采样0=>1=>2
}
endgroup
wildcard覆盖率
用关键字wildcard创建多个状态或者翻转,表达式中,任何x,z或者?都会被当作0或1的通配符
bit [2:0] port;
covergroup CoverPort;
coverpoint port{
wildcard bins even ={3'b??0};
wildcard bins odd = {3'b??1}; //奇偶不关心高位
}
忽略的bin、非法的bin
- 某些coverpoint可能始终无法得到全部的阈值
- 对于那些不计算功能的域值用ignore_bis来排除,最终它们不会计入coverpoint的覆盖率
- 有些采样值不仅应该被忽略,而且如果出现还应该报错。某些情况可以在测试平台中监控,也可以使用illegal_bins对特定的bin进行标示。 采样到illegal_bind,仿真会停止。
bit [2:0] low_ports;
covergroup CovPort;
coverpoint low_ports{
ignore_bins hi={[6,7]}; //忽略数值6,7
illegal_bins ni={[3,4]}; //出现3,4会报错,采样到illegal_bins仿真会停止
}
endgroup
交叉覆盖率
- coverpoint用于记录单个变量或者表达式的观测值;若需要在某时刻记录多个变量之间值的组合情况,需要交叉(cross)覆盖率
- cross语句只允许带coverpoint或简单的变量名
class Transaction; rand bit[3:0] kind; rand bit[2:0] port; endclass Transaction tr; covergroup CovePort; kind: coverpoint tr.kind; port: coverpoint tr.point; cross kind,port; //kind、port都为coverpoint名称 endgroup
通过使用ignore_bins、binsof和intersect分别指定coverpoint和值域,这样可以清除很多不关心的cross bin
covergroup Covport;
port:coverpoint tr.port{
{bins port[] = {0:$}}; //动态数组,8个bin
}
kind:coverpoint tr.kind{
bins zero ={0}; //只有1个bin
bins lo={[1:3]}; // 只有1个bin
bins hi[]={[8:$]}; //8个bin 数组会平均分配
bins misc = default; //1个bin 总共8*11,共88个bin
}
cross kind,port{
ignore_bins hi=binsof(port) intersect {7}; //忽略port为7的组合
ignore_bins md=binsof(port) intersect {0} && binsof(kind) intersect {[9:11]} //port为0,
且kind为9-11时的组合
ignore_bins lo=binsof(kind.lo) //忽略kind为[1:3],port为[0:7]的组合忽略
}
有时候相比使用自动分配的cross bin,自己声明感兴趣的cross bin更合适
{a==0,b==0},{a==1,b==0}和{b==1}
class Transaction;
rand bit a,b;
endclass
covergroup CrossBinNames;
a:coverpoint tr.a{
bins a0={0};
bins a1={1};
option.weight=0; //不计算覆盖率
}
b:coverpoint tr.b{
bins b0={0};
bins b1={1};
option.weight=0; //不计算覆盖率
}
ab:cross a,b{
bins a0b0=binsof(a.a0) && binsof(b.b0);
bins a1b0:binsof{a.a1} && binsof(b.b0);
bins b1:binsof(b.b1);
}
//方法2
covergroup CrossBinsofIntersect;
a:coverpoint tr.a{
option.weight=0;
}
this coverpoint
b:coverpoint tr.b{
option.weight=0;
}
this coverpoint
ab:cross a,b{
bins a0b0 = binsof(a) intersect {0} && binsof(b) intersect {1};
bins a1b0 = binsof(a) intersect {1} && binsof(b) intersect {0};
bins b1 = binsof(b) intersect {1};
}
如果cp_A有4个bin,cp_B有6个bin,cross cp_A cp_B不一定会自动分配24个bin
(如果bin涵盖所有可能的值,那么上式成立)
port: coverpoint tr.port;
{bins port[] = {[0:$]};} //8个bin
kind: coverpoint tr.kind{
bins zero = {0};
bins lo = {1:3};
bins hi[] = {[8:$]}; //已有有10个bin,但由于遗漏(4,5,6,7)4个值,cross时会计算当前bin列举
//值的范围在不在,自动添加4个bin,因为cross时含有这4个数值,没有列举出来
//仿真器会自动添加这4个bin,因此建议声明感兴趣的bin
}
覆盖选项
单个实例覆盖率:如果对一个covergroup例化多次,那么默认情况下sv会将所有实例的覆盖率合并在一起。如果需要单独列出每个covergroup实例的覆盖率,需要设置覆盖选项
注释:如果有多个covergroup实例,可以通过参数对每一个实例传入单独的注释,注释最终会显示在覆盖数据的总结报告。
覆盖次数限定:默认情况下,数值采样1次就会计入有效的bin。可以通过修改at_least 修改每个bin的数值最少的采样次数,如果低于at_least则不会计入bin中。(option.at_least可以在covergroup中声明所有影响coverpoint,也可以在coverpoint中声明来只影响该coverpoint下所有的bin)
覆盖率目标:一个covergroup、coverpoint目标是100%覆盖率。不过可以将其设置为低于100%的目标。这个选项只会影响覆盖率报告。
covergroup方法:sample():采样;get_coverage()/get_inst_coverage():获取覆盖率,返回0-100的real数值。set_inst_name(string):设置covergroup名称。start()/stop():使能或者关闭覆盖率的收集。
covergroup CoverPort(int lo,hi, string comment);
option.per_instance=1; //单独实例覆盖率
option.comment=comment; //注释
option.goal=90; //覆盖率目标
coverpoint port
{bind range={[lo:hi]}; //参数
}
endgroup
CoverPort cp_lo=new(0,3,"Low port numbers");
数据分析
- 使用$get_coverage()可以获得总体的覆盖率;也可使用covergroup_inst.get_inst_coverage()来获取单个covergroup实例的覆盖率。这些测试函数最实际用处是在一个测试当中监测覆盖率的变化
- 如果覆盖率水平在一段时间之后没有提高,那么测试应该停止。重启新的随机种子或者测试可能有望提高覆盖率
- 如果测试可以基于功能覆盖率采取一些深入的行动,如重新编写随机的约束,那将非常好,但这种测试很难编写。