SV 有关覆盖率学习
1. 概述
- 覆盖率是衡量验证精度和完备性的数据指标;
- 只有满足以下三个条件,才可以在仿真中实现高质量的验证:
- 测试平台必须产生合适的激励来触发一个设计错误;
- 测试平台仍然需要产生合适的激励使得被触发的错误可以进一步传导到输出端口;
- 测试平台需要包含一个监测器(monitor)用来检测被激活的设计错误,以及在它传播的某个节点(内部或者外部)可以捕捉到她;
- 没有任何一种单一的覆盖率可以完备地去衡量验证过程;
- 即使可以达到100%的代码覆盖率,也不意味着100%地功能覆盖率。因为代码覆盖率并不是用来衡量设计内部地功能运转,或者模块之间地互动,或者功能时序的触发等;
- 类似,即使达到了100%的
功能覆盖率,也可能只达到了90%的代码覆盖率。因为我们可能疏漏了去测试某些功能或者一些实现的功能还没有被描述;
以上,如果想要得到全面的验证精度,就需要多个覆盖率种类的指标;
2. 覆盖率的种类
- 最常见的划分覆盖率的两种方法:
- 按照覆盖率生成的方法,即隐性生成还是显性生成;
- 按照覆盖率溯源,即它们从功能描述而来还是从设计实现而来;
- 例如,功能覆盖率是显性的需要认为定义的覆盖率,而代码覆盖率则是隐性覆盖率,这是因为仿真工具可以自动从RTL代码来生成;
3. 代码覆盖率
- 代码覆盖率的优势在于,它可以由仿真工具自动收集,继而用来指出在测试程序中设计源代码哪些被激活触发,而哪些则一直处于非激活的状态;
- 由于代码覆盖率自动的特性,在仿真过程中能使它变得很简单,它不需要修改设计或者验证环境;
- 代码覆盖率100%并不意味着足够的功能覆盖率;即便代码覆盖率的完备性满足,但依然可能在这些代码中存在漏洞;
- 上述推断是因为不是所有被覆盖到的代码都会得到足够的检测,也由于没有得到足够的检测,因此一些即使被触发的漏洞也在传播的过程中没有达到监测点上;
- 代码覆盖率的数据无法直接映射到哪些设计功能被测试到了,所以,代码覆盖率和功能覆盖率之间又是相互独立的;
3.1. 跳转覆盖率(toggle)
- 用来衡量寄存器跳转的次数(0->1, 1->0);
- 一般项目会要求模块的端口至少实现一次0到1,以及一次1到0的跳转来保证模块的继承和模块之间的互动;
- 也有项目会要求所有的寄存器都应该同端口一样满足跳转的最少次数;(不理解这句话)
- 端口跳转覆盖率经常用来测试IP模块之间的基本连接性,例如检测一些输入端口是否没有连接,或者已经连接的两个端口的bit位数是否不匹配,又或者一些已经连接的输入是否被给定的固定值等,都可以通过跳转覆盖率来发现问题;(这些举例没有碰到过,理解我们要实现多样化的驱动来提升验证完备性,但不是很理解为什么要去通过跳转覆盖率检测接口连接)
3.2. 行覆盖率(statement / line)
- 用来衡量源代码哪些代码行被执行过,以此来指出哪些代码行没有被执行过;
- 从每一行执行的次数,如果设置最小的执行次数,也可以用来做充足代码测试的一项指标;
- 代码覆盖率可以hi出在一些缺乏输入激励的情况下,某些赋值的代码行没有被执行的情况,它也可以指出在一些漏洞影响或者无用代码的影响下,一些代码行无法被执行的情况;
- 对于无用代码,也就是永远不会被执行的代码,在代码分析时,可以将它们从覆盖率数据中过滤掉;
3.3. 分支覆盖率(branch)
- 分支覆盖率是用来对条件语句(if / else, case,?:),指出其执行的分支轨迹;
- 例如,判断下列分支的布尔表达式为true或者false:
if(parity = = ODD || parity == EVEN) …
3.4. 条件覆盖率(condition / expression)
- 条件覆盖率用来衡量一些布尔表达式中各个条件真伪判断的执行轨迹;
- 例如,下列if条件语句中的两个条件是否各自衡量为true / false:
if(parity = = ODD || parity == EVEN) …- parity == ODD 或者 parity != ODD
- parity != ODD 或者 parity == ODD
3.5. 状态机覆盖率(FSM)
- 仿真工具可以自动识别状态机,因此在收集覆盖率时,也可以将覆盖率状态的执行情况检测到;
- 每个状态的进入次数,状态之间的跳转次数,以及多个状态的跳转顺序都可以由仿真工具记录下来;
4. 功能覆盖率
- 功能验证的目标在于确定设计有关的功能描述是否被全部实现了?
- 这一检查中可能会存在不期望的情况:
- 一些功能没有被实现
- 一些功能被错误的实现了
- 一些没有被要求的功能也被实现了;
- 要求的功能是否被实现了无法通过代码覆盖率而得知,需要显性的通过功能覆盖率与设计功能描述做映射,继而量化功能验证的进程;
- 在随机约束测试流行于验证时,由于随机测试在仿真时会自动产生上千条测试激励,但我们无法直到随机产生的激励究竟是测试了什么功能;
- 功能覆盖率是最好的可以协助在回归测试时自动检测哪些功能被激活的方法;
- 创建功能覆盖率模型需要完成以下两个步骤:
- 从功能描述文档提取拆分需要测试的功能点;
- 将功能点量化为与设计实现对应的SV功能覆盖代码;
4.1. 覆盖组(covergroup)
- 覆盖组与类相似,在一次定义以后可以多次进行例化;
- 覆盖组含有覆盖点(coverpoint)、选项(option)、形式参数(argument)和可选触发(trigger event);
- 一个覆盖组包含了一个或者多个数据点,全都在同一时间采集;
- 覆盖组可以定义在类里,也可以定义在模块或者程序(program)中,package、model、interface中;
- 覆盖组可以采集任何可见的变量,比如程序或者模块变量、接口信号或者设计中的任何信号,比如接口上的信号;
- 在类中覆盖组也可以采集类的成员变量;
- 覆盖组应该定义在适当的抽象层次上;
- 对任何事物的采样都必须等到数据被待测设计接收到以后;
- 一个类也可以包含多个覆盖组,每一个覆盖组可以根据需要将它们使能或者禁止;
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; // cross 2 variables
all: cross color, Hue, Offset; // cross 1 VARs and 2 CPs
endgroup
g2 cg_inst = new();
- covergroup … coverpoint来定义覆盖组;
- 内部可以定义多个coverpoint;
- 如果不在covergroup声明时指定采样事件,那么默认该覆盖组只能依赖于其他一个手动采样函数sample();
- 在类中声明covergroup的方式被称为嵌入式覆盖组声明,以下的方式将会声明一个匿名的覆盖组类型和一个覆盖组变量cov1:
class xyz;
bit [ 3: 0] m_x;
int m_y;
bit m_z;
covergroup cov1 @m_z; // enbedded covergroup
coverpoint m_x;
coverpoint m_y;
endgroup
function new();
cov1 = new();
endfunction
endclass
- 一个类中可以声明多个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;
coverpoint m_z;
endgroup
endclass
- 一个covergroup可以包含一个或者多个coverpoint,一个coverpoint可以用来采样数据或者数据的变化;
- 一个coverpoint可以对应多个bin(仓),这些仓可以显性指定,也可以隐性指定;
- coverpoint对数据的采样发生在covergroup采样的时候
4.2. 仓(bin)
- 关键词bins可以用来将每一个感兴趣的数据均对到一个独立的bin或者将所有值对应到一个共同的bin;
- iff语句也可以用在bin的定义,它表示条件为false,那么在收集该bin的时候,该bin的采样数据不会增长;
bit [ 9: 0] v_a;
covergroup cg @(posedge clk);
coverpoint v_a{
bins a = {[0:63], 65};
bins b[] = {[127:150], [148:191]};
bins c[] = {200, 201, 202};
bins d = {[1000:$]};
bins others[] = default;
};
endgroup
- covergroup的参数也可以被传递到bin的定义中;covergroup都是用来采样变量的,一般参数的类型都是input或者ref;input是一个快照,ref是无时无刻的检测;
covergroup cg(ref int ra, input int low, int high) @(posedge clk);
coverpoint r{
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);
}
coverpoint b{
bins func[] = b with{myfunc(item)};
}
- 除了可以覆盖数值,还可以覆盖数值的变化;
value1 => value2
value => value => 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) or (3=>3=>3=>3) or (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
bit [ 4: 1] v_a;
covergroup cg @(posedge clk);
coverpoint v_a{
bins sa = (4 => 5 => 5), ([7:9],10 => 11,12);
bins sb[] = (4 => 5 => 6), ([7:9],10 => 11,12);
bins sc = (12 => 3[->1]);
}
endgroup
-
如果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之间的值,即4’b1100、4’b1101、4’b1110、4’b1111 -
ignore_bins用来将其排除在有效统计的bin集合之外;
-
illegal_bins用来指出采样到的数值为非法值,如果illegal_bins被采样到,那么仿真将报错;
covergroup cg23;
coverpoint a{
ignore_bins ignore_vals = {7, 8};
ignore_bins ignore_trans = (1=>3=>5);
}
endgroup
covergroup cg24;
coverpoint b{
illegal_bins bad_vals = {1, 2, 3};
illegal_bins bad_trans = (4=>5=>6);
}
endgroup
4.3. 覆盖点(coverpoint)
- 在定义coverpoint时,可以不给名字或者给名字,建议给不同的coverpoint以不同的名字;
- 这些有名字的coverpoint可以用来做更进一步的处理,例如在交叉覆盖率中使用某一个coverpoint;
- 可以通过iff在一些情况下禁止coverpoin的采集:
covergroup g4;
coverpoint s0 iff(!reset);
endgroup
covergroup cg(ref int x, ref int y, input int c);
coverpoint x;
b: coverpoint y;
cx: coverpoint x;
option.weight = c;
bit [ 7: 0] d: coverpoint y[31:24];
e: coverpoint x{
option.weight = 2;
}
cross x, y{
option.weight = c;
}
endgroup
4.4. 交叉覆盖率(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