目录
覆盖率是衡量设计完备性的一个指标。
覆盖率工具会在仿真过程中收集信息,然后进行后续处理并且得到覆盖率报告,然后通过报告找出覆盖之外的区域,接着修改现有测试或者创建新的测试来填补这些盲区。每一次的随机测试我们都会得到对应的测试结果,最后我们再把这些测试结果合并,就能得到最终的覆盖率报告了。
覆盖率类型
代码覆盖率
1.行覆盖率:多少行代码已经被执行过。
2.路径覆盖率:在穿过代码和表达式的路径中哪些已经被执行过。
3.翻转覆盖率:哪些单位比特变量的值为0或1.
4.状态机覆盖率:状态机哪些状态和状态转换已经被访问过。
代码覆盖率最终的结果用于衡量你执行了设计中的多少代码。未经测试的设计代码里可能隐藏硬件漏洞,也可能仅仅就是冗余的代码。
代码覆盖率到达100%,并不意味着验证的工作已经完成,但是代码覆盖率100%是验证工作完备性的必要条件。
断言覆盖率
断言是用于一次性地或在一段时间对一个或者多个设计信号在逻辑或者时序上的声明性代码。断言最常用于查找错误,例如两个信号是否应该互斥,或者请求与许可信号之间的时序等,一旦检查到问题,仿真就可以立即停止。
功能覆盖率
验证的目的就是确保设计在实际环境中的行为正确。功能描述文档详细说明了设计应该如何运行,而验证计划则列出了相应的功能应该如何激励、验证和测量。某个功能在设计时可能被遗漏,代码覆盖率不能发现这个错误,但是功能覆盖率可以。
每一次仿真都会产生一个带有覆盖率信息的数据库,记录随机游走的轨迹,把这些信息全部合并在一起就可以得到功能覆盖率,从而衡量整体的进展程度。
如果覆盖率在稳步增长,那么添加新的种子或者加长测试时间即可;如果覆盖率增速缓慢,那么需要添加额外的约束来产生更多“有意思”的激励;如果覆盖率停止增长,然而设计某些测试点没有被覆盖到,那么需要创建新的测试;如果覆盖率为100%但依然有新的设计漏洞,那么覆盖率可能没有覆盖到设计中的某些设计功能区域。
完备的覆盖率测量结果和漏洞增长曲线,可以帮助确认设计是否被完整地验证过。A.如果代码覆盖率低,但功能覆盖率高,这说明验证计划不完整可能有部分功能点没被列入验证计划。B.如果代码覆盖率高,但功能覆盖率低,这说明即使测试平台很好地执行了设计的所有代码,但测试没有把设计定位到所有感兴趣的状态上。
一般在公司里都会要求代码覆盖率和功能覆盖率都要达到95%以上。
覆盖组
覆盖组(covergroup)与类(class)相似,一次定义后便可以多次实例化。它含有覆盖点、选项、形式参数和可选触发(trigger)。一个覆盖组包含了一个或多个数据点,全都在同一时间采集。covergroup可以定义在类中,也可以定义在 interface 或者 module 中,它可以采样任何可见的变量,例如程序变量、接口信号或者设计端口。
一个类里可以包含多个covergroup,当你拥有多个独立的 covergroup 时,每个covergroup 可以根据需要自行使能或者禁止。covergroup 必须要被例化才可以用来收集数据。
如果覆盖组定义在类里,实例化时可以使用最初的名字,例如上面这个例子中展现的,申明了CovPort为covergroup后,就直接对它进行了new(),实际上我们知道这只是声明该变量为covergroup类型并不是声明对象。除此种特殊的例化方式,通常使用的是下面这种:
因为covergroup是可以例化多次的,如果采用直接把CovPort作为实例名例化,那就不能例化第二次了,而采用下面这种声明一个别的对象为CovPort类型(也即covergroup类型)显然可以例化多次,改个声明对象名字即可。
上面的代码,在采样的时候,采用的是CovPort.sample()(调用sample()函数)的方式进行采样,这是第一种采样方式,还有第二种就是采取阻塞的方式wait或者@实现事件上的阻塞,当遇到什么事件的时候再进行采样。
数据采样
当你在 coverpoint 指定采样一个变量或表达式时,SV会创建很多的仓(bin)来记录每个数值被捕捉到的次数。这些bin是衡量功能覆盖率的基本单位。
covergroup 中可以定义多个coverpoint,coverpoint中可以自定义多个cover bin或者SV帮助自动定义多个cover bin。
为了计算一个coverpoint上的覆盖率,首先需要确定可能数值的个数,这也被称为域。
※ 覆盖率就是采样值的数目除以bin的数目。例如一个3bit变量的域是0:7,正常情况下会自动分配8个bin。如果仿真过程中有7个值被采样到,那么最终该coverpoint的覆盖率为7/8。
※ 所有的coverpoint的覆盖率最终构成一个covergroup的覆盖率。所有的covergroup的覆盖率构成了整体的覆盖率。
如果采样变量的域范围过大而又没有指定bin,那么系统会默认分配64个bin,将值域范围平均分配个这64个bin,例如一个16bit变量有65536个可能值,所以64个bin中的每一个都覆盖了1024个值。用户可以通过covergroup的选项auto_bin_max来指定自动创建bin的最大数目。实际操作中自动创建bin的方法并不实用,建议用户自行定义bin:
注意coverpoint定义使用{}而不是begin-end。大括号的结尾没有分号,和end一样。
对于hi,会分配8个bin。lo是一个bin,misc也是一个bin。左边是bin的名称,中间是bin里的数据被采样了多少次,右边意思是这个至少要收集多少次。上面这个结果来看,总共有11个bin,只有一个bin没有被收集到,那么它最终的覆盖率就是10/11。
条件覆盖率
可以使用关键词 iff 给 coverpoint 添加条件。
如果在采样(sample函数)的时候,使用了iff,那么收集覆盖率的时候,既要满足sample()进行采样,也要满足iff后面的条件,才会对其进行采样。还可以使用start和stop函数来控制covergroup各个独立实例的开启和关闭,如果关闭了,哪怕设了采样sample函数,也不会真的采样。
翻转覆盖率
coverpoint 也可以用来记录变量从A值到B值的跳转情况。还可以确定任何长度的翻转次数。
记录t1值从0翻转到1或者2或者3的次数,但凡这三个事件有一个发生,bin里面记录的次数就会加1。
忽略的bin
三比特变量low_ports_0_5最初的范围是0:7。ignore_bins 排除掉最后两个仓,即不考虑6和7的采样值,从而把采样范围缩小到0:5.所以这个组的总体覆盖率是采样到的仓数除以总仓数,这里总仓数时是6。
非法的bin
有些采样值不仅应该被忽略而且如果出现还应该报错。
这种情况可以在测试平台中监测,也可以使用illegal_bins对特定的bin进行标示。
如果出现6,7不单不能出现,而且如果出现了就会报错。
交叉覆盖率
coverpoint 是记录单个变量或者表达式的观测值。如果想记录某一时刻,多个变量之间值的组合情况,需要使用交叉(cross) 覆盖率。
排除部分cross bin
比如上面的例子,假设kind有16个bin,port有8个bin,那么cross之后,它们之间就会产生8*16=128个bin,数量很多,为了减少bin的数量,我们可以采用ignore的方式来清除那些我们不关心的cross bin。
如上面的例子,port共有8个bin,kind共有11个bin。在声明两个变量的交叉覆盖率的时候,忽略了很多的交叉情况,比如当port 里值为7时,不考虑和 kind 的 bin 交叉的情况,以及 port 里值为0且 kind 里值为9-11的时候,也不考虑这种情况的交叉 bin 等。
但是随着cross覆盖率越来越精细,更合适的方式是不使用自动分配的cross bin,而是自己声明自己感兴趣的cross bin。
假如有两个随机变量a,b。它们带着三种感兴趣的状态{a==0,b==0}\{a==1,b==0}和{b==1}。
选择自己感兴趣的点做交叉覆盖。
覆盖选项
如果对一个covergroup 例化多次,那么默认情况下SV会将所有实例的覆盖率加权合并到一起,如果需要单独列出每个covergroup实例的覆盖率,需要设置覆盖选项。
这样设置option后,每个实例的覆盖率都会单独计算。
如果有多个covergroup实例,可以通过参数来对每一个实例传入单独的注释。这些注释最终会显示在覆盖率数据的总结报告中。
covergroup 方法:sample()采样、get_coverage()/get_inst_coverage():获取覆盖率,返回0-100的real数值。set_inst_name(string):设置coverage的名称、start()/stop():使能或者关闭覆盖率的收集。
覆盖率分析
使用 $get_coverage() 可以得到总体的覆盖率。
也可以使用 covergroup_inst.get_inst_coverage( ) 来获取单个 covergroup 实例的覆盖率。
如果覆盖率水平在一段时间之后没有提高,那么这个测试就应该停止。重启新的随机种子或者测试可能有望提高覆盖率,重新限定随机的约束也能对提高覆盖率有所帮助。