覆盖率
概述
- 验证没有量化,那么就意味着没有尽头
- 伴随着复杂的SoC系统的验证难度系数成倍增加,无论是定向测试还是随机测试,我们在验证的过程中终究要回答两个问题
- 是否所有设计的功能在验证计划中都已经验证?
- 代码中的某些部分是否从未执行过?
- 覆盖率就是用来帮助我们在仿真中回答以上问题的指标
- 覆盖率已经被广泛采用,作为衡量验证过程中的重要数据
- 一旦通过覆盖率来量化验证,我们可以在更复杂的情况下捕捉一些功能特性是否被覆盖
- 当我们在测试X特性时,Y特性是否也在同一时刻被使能和测试
- 是否可以精简我们已有的测试来加速仿真,并且取得同样的覆盖率
- 覆盖率在达到一定的数值时,是否停滞,不再继续上升
- 简单而言,覆盖率就是用来衡量验证精度和完备性的数据指标
- 覆盖率可以告诉我们在仿真时设计的哪些结构被触发,当然,它也可以高数我们设计在仿真时哪些结构从未被触发过
- 只有满足以下三个条件,才可以在仿真中实现高质量的验证
- 测试平台必须产生合适的激励来触发一个设计错误
- 测试平台仍然需要产生合适的激励使得被触发的错误可以进一步传导到输出端口
- 测试平台需要包含一个检测器(monitor)用来监测被激活的设计错误,以及在它传播的某个节点(内部或者外部)可以捕捉到它
覆盖率的种类
-
没有任何一种单一的覆盖率可以完备的去衡量验证过程
-
即使我们达到100%的代码覆盖率,并不意味着100%的功能覆盖率。原因在于代码覆盖率并不是用来衡量设计内部的功能运转,或者模块间的互动,或者功能时序的触发等
-
类似地,我们既便达到了100%功能覆盖率,也可能只达到了90%的代码覆盖率。原因可能在于我们疏漏了去测试某些功能或者一些实现的功能并没有被描述
-
从上述关于代码覆盖率和功能覆盖率的论述可以证明,如果想要得到全面的验证精度,我们就需要多个覆盖率种类的指标
-
最常见的划分覆盖率的两种方法
-
按照覆盖率生成的方法,即隐形生成还是显性生成
-
按炸藕改了溯源,即它们从功能描述而来还是从设计实现而来
-
例如功能覆盖率时显性的需要认为定义的覆盖率,而代码覆盖率则是隐形覆盖率,因为仿真工具可以自动从RTL代码来生成
-
-
如果将上述两个分类的方式进行组合,那么可以将代码覆盖率、断言覆盖率、功能覆盖率分别置入不同的象限
-
但是需要注意,目前有一个象限仍然处于研究阶段,没有隐形的可以从功能描述生成某种覆盖率的方法,这也是为什么功能覆盖率依然需要人为定义的原因
-
接下来我们将主要认识两种覆盖率
- 代码覆盖率(隐形覆盖率)
- 功能覆盖率(显性覆盖率)
代码覆盖率
- 代码覆盖率并不是SV独有的,在软件测试领域很早之前就有了这一项测试指标
- 代码覆盖率的一个优势在于它可以由仿真工具自动收集,继而用来指出在测试程序中设计源代码哪些被激活触发,而哪些则一直处于非激活的状态
- 由于代码覆盖率自动的特性,在仿真过程中使能它变得很简单,不需要修改设计或者验证环境
局限
- 代码覆盖率100%并不意味着足够的功能覆盖率
- 研究发现,在回归测试实现了90%的代码覆盖率时,平均只有54%的代码会被监测,这意味着即便代码覆盖率的完备性满足,但依然可能在这些代码中存在漏洞
- 上述的推断是因为并不是多有被覆盖的代码都会得到足够的监测,也由于没有足够的监测,因此一些即使被触发的漏洞也在传播的过程中没有到达监测点上(漏洞便从眼皮底下“逃逸”了)
- 另外,代码覆盖率的数据无法直接映射到哪些设计功能被测试了,所以从这一点来看,代码覆盖率和功能覆盖率之间又是相互独立的
跳转覆盖率(toggle)
- 用来衡量寄存器跳转的次数(0->1, 1->0)
- 一般项目会要求模块的端口实现至少一次0到1,以及一次1到0的跳转来保证模块的集成和模块之间的互动
- 有的项目会要求所有的寄存器都应该同端口一样满足跳转的最少次数
- 端口跳转覆盖率经常用来测试IP模块之间的基本连接性,例如检查一些输入端口是否没有连接,或者已经连接的两个端口的比特位数是否不匹配,又或者一些已经连接的输入是否被给定的固定值等,都可以通过跳转覆盖率来发现问题
行覆盖率(statement/line)
- 用来衡量源代码哪些代码行被执行过,以此来指出哪些代码行没有被执行过
- 从每一行执行的次数,如果设置最小的执行次数,也可以用来做充足代码测试的一项指标
- 代码覆盖率可以指出在一些缺乏输入激励的情况下,某些赋值的代码没有被执行的情况,它也可以指出在一些漏洞影响或者无用代码的影响下,一些代码行也无法被执行的情况
- 对于那些无用代码,也就是永远不会被执行的代码,在代码分析时,我们可以将他们从覆盖率数据中过滤掉
分支覆盖率(branch)
-
分支覆盖率是用来对条件语句(if/else, case, ?😃,治初期执行的分支轨迹
-
例如判断下列分支的布尔表达式为true或者false
if (parity == ODD || parity == EVEN) begin…
条件覆盖率(condition/expression)
-
条件覆盖率是用来衡量一些布尔表达式中各个条件真伪判断的执行规级
-
例如下列if条件语句中的两个条件各自衡量为true/false
if (parity == ODD || parity == EVEN) begin…
parity == ODD 或者 parity != ODD
parity == EVEN 或者 parity != EVEN
状态机覆盖率(FSM)
- 仿真工具由于可以自动识别状态机,因此在收集覆盖率时,也可以将覆盖率状态的执行情况检测到
- 每个状态的进入次数,状态之间的跳转次数,以及多个状态的跳转顺序都可以由仿真工具记录下来
功能覆盖率
- 功能验证的目标在于确定设计有关的功能描述是否被全部实现了
- 这一检查中可能会存在一些不期望的情况
- 一些功能没有被实现
- 一些功能被错误的实现了
- 一些没有被要求的功能也被实现了
- 我们无法通过代码覆盖率得知要求的功能是否被实现了,而需要显性的通过功能覆盖率与设计功能描述
- 在约束随机测试流行于验证时,由于随机测试在仿真时会自动产生上千条测试激励,但我们却无法知道随机产生的激励究竟测试了什么功能
- 功能覆盖率是最好的可以协助在回归测试时自动监测哪些功能被激活的方法
- 创建功能覆盖率模型需要完成以下两个步骤
- 从功能描述文档提去拆分需要测试的功能点
- 将功能点量化为与设计实现对应的SV功能覆盖率代码
覆盖组(covergroup)
- 覆盖组与类类似,在一次定义后便可以多次进行例化
- 覆盖组含有覆盖点(coverpoint)、选项(option)、形式参数(argument)、可选触发(trigger event)
- 一个覆盖组包含一个或者多个数据点,全都在同一时间采集
- 覆盖组可以定义在类里,也可以定义在模块或者程序(program)中
- 覆盖组可以采集任何可见的变量,比如程序或模块变量、接口信号或者设计中的任何信号
- 在类中的覆盖组也可以采集任何类的成员变量
- 覆盖组应该定义在适当的抽象层次上
- 对任何事务的采样都必须等到数据被待测设计接收以后
- 一个类可以包含多个覆盖组,每一个覆盖组可以根据需要将它们使能或者禁止
enum {red, green, blue} color;
bit[3:0] pixel_adr, pixel_offset, pixel_hue;
covergroup g2 @(posedge clk); // 在时钟上升沿采样
Hue: coverpoint pixel_hue;
Offset: coverpoint pixel_offset;
AxC: cross color, pixel_adr; // 采样color和pixel_offset的组合
all: cross color, Hue, Offset;
endgroup
g2 cg_inst = new();
// covergroup...endgroup 定义覆盖组
// 内部可以有多个coverpoint
// 如果不在covergroup声明时指定采样事件,那么默认该覆盖组只能依赖于另一个手动采集函数sample()
-
一个类中声明多个covergroup
class MC; logic[3:0] m_x; local logic m_z; bit m_e; covergroup cv1 @(posedge clk); coverpoint m_x; // 没有定义覆盖点的名字,会自动分配 endgroup covergroup cv2 @m_e; // 采样事件为m_e coverpoint m_z; endgroup function new(); cv1 = new(); // 在析构函数中例化覆盖组,这种例化方式是只例化一次 endfunction endclass
-
一个covergroup可以包含一个或者多个coverpoint,一个coverpoint可以用来采样数据或者数据的变化
-
一个coverpoint可以对应多个bin(仓)
-
这些仓可以是显性指定,也可以隐形指定
-
coverpoint对数据的采样发生在covergroup采样的时候
仓(bin)
-
关键词bins可以用来将感兴趣的数值均对一个独立的bin,或者将所有值对应到一个共同的bin
bit[9:0] v_a; covergroup cg @(posedge clk); coverpoint v_a { bins a = {[0:63], 65}; // 一个bin bins b[] = {[127:150], [148:191]}; bins c[] = {200, 201, 202}; // 三个bin bins d = {[1000:$]}; bins others[] = default; } endgroup
-
iff语句也可以用在bin的定义,它表示条件为false,那么在采集该bin的时候,该bin的采样数目不会增长
covergroup g4; coverpoint s0 iff(!reset); // iff,禁止采样 endgroup
-
covergroup的参数也可以被传递到bin的定义中
covergroup cg (ref int ra, input int low, int high) @(posedge clk); coverpoint ra { bins good = {[low: high]}; bins bad[] = default; } endgroup ... int va, vb; cg c1 = new(va, 0, 50); cg c2 = new(vb, 120, 600);
-
在定义bin时,可以使用with来进一步限定其关心的数值,with可以用表达式或者函数来衡量
a: coverpoint x { bins mod3[] = {[0:255]} with (item %3 == 0); // with限定0~255之间能被3整除的数 } covergroup b { bins func[] = b with (myfunc(item)); }
-
除了可以覆盖数值,还可以覆盖数值的变化
value1 => value2 value1 => value3 => value4 => value5 range_list1 => range_list2 // 1,5 => 6,7 trans_item [* repeat_range] 3 [* 5] // 表示3=>3=>3=>3=>3 3 [* 3:5] // 表示(3=>3=>3)、(3=>3=>3=>3)或(3=>3=>3=>3=>3)
-
除了使用=>来表示相邻采样点的变化,也可以使用->来表示非相邻采样点的数值变化,在=>序列后的下一个时序必须紧跟=>序列的最后一个事件
3 [->3] // 表示 ...=>3...=>3...=>3,有三次变为了3,可以不相邻 1 =>3 [->3] =>5 // 表示 1...=>3...=>3...=>3 =>5
-
与[-> repeat_range]类似的有[= repeat_range],表示非连续的循环,只是与->有区别的在于,跟随->序列的下次值变化可以发生在->结束后的任何时刻
3 [=2] // 表示 ...=>3...=>3 1 => 3[=2] =>6 // 表示 1...=>3...=>3...=>6
-
-
如果coverpoint没有指定任何bin,那么SV将会为其自动生成bin,遵循的原则:
- 如果变量是枚举类型,那么bin的数量是枚举类型的基数(所有枚举数值的和)
- 如果变量是整形(M位宽),那么bin的类型将是2^M和auto_bin_max选项的较小值
-
默认情况下,数值的变化可以针对四值类型变量进行覆盖,例如bin如果包含x或者z,则表示只有该变量对应位为x或者z的时候,bin可以采集到
-
wildcard修饰符可以使得bin中包含x,z和?的数值都将用来表示0或1,也就是通配符的意思
wildcard bins g12_15 = {4'b11??}; // g12_15可以用来表示12到15之间的值,即:1100、1101、1110、1111
-
ignore_bins用来将其排除在有效统计的bin集合之外
covergroup cg23; coverpoint a { ignore_bins ignore_vals = {7, 8}; ignore_bins ignore_trans = (1=>3=>5); } endgroup
-
illegal_bins用来指出采集到的数值为非法制,如果illegal_bins被采集到,那么仿真将会报错
covergroup cg3; coverpoint b { illegal_bins bad_vals = {1,2,3}; illegal_bins bad_trans = (4=>5=>6); } endgroup
交叉覆盖率(cross)
-
covergroup可以在两个或者更多的coverpoint或者变量之间定义交叉覆盖率(cross coverage)
-
在对a和b产生交叉覆盖之前,系统会先为它们隐形产生对应的coverpoint和bin,每个coverpoint都有16个自动产生的bin
-
两个coverpoint交叉以后将生成256个交叉的bin
bit[3:0] a, b; covergroup cov @(posedge clk); aXb: cross a, b; endgroup
-
除了系统会自动为交叉覆盖率生成bin以外,用户还可以自己定义交叉覆盖率的bin
-
binsof()的参数可以是coverpoint或者变量,表示对应的bin总和,可以利用binsof()对其结果做进一步的过滤
int i, j; covergroup ct; coverpoint i { bins i[] = {[0:1]}; } coverpoint j { bins j[] = {[0:1]}; } x1: cross i, j; x2: cross i, j { bins i_zero = binsof(i) intersect {0}; } endgroup
指定覆盖率选项
-
覆盖率选项按照在covergroup、coverpoint和cross不同的域中是否具备,下表做了归纳
option name covergroup coverpoint cross name yes no no weight yes yes yes goal yes yes yes comment yes yes yes at_last yes(default for coverpoint & crosses) yes yes detect_overlap yes(default for coverpoints) yes no auto_bin_max yes(default for coverpoints) yes no cross_num_print_missing yes(default for coverpoints) no yes per_instance yes no no [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CkX7JkAm-1692324458007)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230712150259486.png)]