----------------感谢移知的SV课程----------------
覆盖率概述
随着设计变的越来越复杂,覆盖率驱动的验证(CDV, coverage driven verification)成为不可或缺的有效的验证手段,需要覆盖率来保证验证的完备性,以及覆盖的全面性。
覆盖率是呈现的一组信息,这组信息可以告诉我们,关于设计的代码、功能等的覆盖信息,是否覆盖全面,有没有漏掉的覆盖点等信息。所以,覆盖率收集的是信息。
使用随机激励进行的每次仿真,触发覆盖组,都会在一个或者多个仓里留下标记。当仿真结束,所有被标记的仓将被汇集到一个数据库里,然后生成覆盖率报告。功能覆盖点可以验证设计完成的程度。
覆盖率分类:
代码覆盖率
功能覆盖率
断言覆盖率
代码覆盖率
为仿真具有分析出来的代码执行情况的统计,哪句代码执行到了,哪句代码没有执行到等信息,断言覆盖率是针对断言覆盖的统计,本课程主要针对功能覆盖率。
功能覆盖率
功能覆盖率是衡量设计特征是否已经被验证的标准,设计被定义为验证过程中不同的覆盖点。所以在进行覆盖率测试之前,先总结归纳覆盖点,建立功能覆盖率模型。然后通过覆盖率采样、分析得出功能覆盖的信息。覆盖率模型是明确验证计划中的哪些功能点需要进行测试来确保最终设计满足规范(即设计的实现),所以功能覆盖率测试也称为规范性测试。功能覆盖率又分三个概念:覆盖组(covergroup),覆盖点(coverpoint),交叉覆盖点(cross)。
在验证设计代码之前,需要首先明确设计关键特性,边界情形,编写验证计划以及需要覆盖的功能点
从功能覆盖的角度出发,明确我们需要验证什么
覆盖率更加注重收集到的信息,即哪些功能点,哪些数值已经验证到了,哪些还没验证到。
例子
program automatic test(busifc.TB ifc);
class packet;
rand bit [31:0] data;
rand bit [2:0] port;//对这个信号进行采样
endclass
covergroup cov;
coverpoint tr.port;//自动建仓
endgroup
initial begin
packet tr;
cov ck;
ck = new();//创建一个功能覆盖率的实例
tr = new();
repeat (32) begin//重复一些周期
assert(tr.randomsize);
ifc.cb.port <= tr.port;
ifc.cb.data <= tr.data;
ck.sample();//对数据采样
@ifc.cb();//等待一个时钟周期
end
end
endprogram
最后仿真的覆盖率报告
覆盖组
覆盖组是覆盖模型中的最大的概念,覆盖组中包含了覆盖点和交叉覆盖点。一个覆盖组中可以包含一个或者多个覆盖点,一个或者多个交叉覆盖点。覆盖组与类相似,一次定义以后便可以进行多次实例化。覆盖组的触发可以分为:回调函数,事件触发,时钟触发。
覆盖组与类相似,一次定义之后便可以进行多次实例化。它含有覆盖点,选项,形式参数和可选的触发器,一个覆盖组包含了一个或者多个数据点,全都在同一时间采样。
covergroup cg;
...
endgroup
覆盖组可以定义在类里,也可以定义在程序或者模块中,可以采样任何可见的变量,例如程序或模块变量,接口信号或者设计中的任何信号。
一个类里可以包含多个覆盖组,每个覆盖组都是各自独立的,每个组可以根据需要自行使能和禁止。
每个组可以有单独的触发条件,允许用户从多个源头收集数据。
一个覆盖组必须被实例化后才可以用来收集数据。与类不同的是,如果没有实例化覆盖组,在运行时也不会打印错误信息,但是在生成的覆盖率报告中却没有此覆盖组的任何信息。
例子:创建一个覆盖组需要1.定义覆盖组,2.实例化覆盖组,3.采样(sample函数)
class packet;
packet ppp;
mailbox mbx_in;
covergroup cov;//定义覆盖组
coverpoint tr.point;
endgroup
function new(mailbox mbx_in);
cov = new();//实例化一个覆盖组
this.mbx_in = mbx_in;
endfunction
task main;
forever begin
ppp = mbx_in.get;
ifc.cb.port <= ppp.port;
ifc.cb.data <= ppp.data;
cov.sample();
end
endtask
endclass
覆盖组的触发
关于覆盖率我们最关心的为两个方面:采样的数据、数据采样的时刻。
采样的数据
一般为测试平台打出约束后的随机激励,该激励使得DUT和整个测试平台运转起来,采样任何我们需要采样的变量,-般不需要人为干预(定向case除外)。
数据采样的时刻
数据采样时刻取决于验证人员何时主动去采样,或者覆盖组何时被触发,分为以下几类:
回调函数
wait/@/event触发覆盖组
验证人员主动调用sample()函数采样
覆盖组的触发
覆盖组cov在测试平台触发ppp事件时进行采样,一个事件可以多次触发。
event ppp;
covergroup cov @(ppp);
coverpoint ifc.cb.port;
endgroup
在时钟上升沿采样
covergroup cov @(posedge clk);
coverpoint i {
bins zero = {0};
bins tin = {[1:100]};
bins a[3] = {1,2,3,4,5,6,7,8};
bins hug = {[1000:$]};
ignore_bins ignore = {[88:99]};
illegal bins hi = {[88,89]};//非法仓
bins others[] = default;
}
endgroup
通用的覆盖组
SV允许使用传递参数的形式,创建一个通用的覆盖组,如下例。(注意,SV不允许把覆盖组的触发条件传递给实例,作为变通的方法,可以把覆盖组放到一个类里,然后把触发参数传递给类的构造函数)
覆盖组不仅可以传递普通参数,还可以通过ref来传递引用。
覆盖点
覆盖点可以是变量(数据),表达式,翻转率。覆盖点可以是变量(数据),表达式,翻转率。覆盖点可以是变量(数据),表达式,翻转率。当覆盖点指定一个变量或者表达式时,systemverilog会创建很多的 “仓(bins)” 来记录每个数据被捕捉的次数。
覆盖点
覆盖点使用coverpoint来定义,每个覆盖点可以起一个名字。
覆盖点指出了本覆盖组中,需要对哪个变量进行采样。如上左例中,需要针对addr和addr2采样,覆盖点的名字为ADDRESS以及ADDRESS2。
覆盖点还可以对表达式进行采样,对条件覆盖进行采样
翻转覆盖率
通过确定覆盖点状态转移的次数,不仅可以知道哪些数值出现过,还可以知道这些值的变化过程。下例可以查询到port有没有从0变为1或者2。
covergroup cov;
coverpoint port{
bins t1 = ((0 => 1), (0 => 2));
}
endgroup
比如,表达式(1,2=>3,4)创建了4个翻转过程,分别为(1=>3)、 (1=>4)、 (2=>3)、 (2=>4)。
还可以确定任意长度的翻转次数,必须对转换工程中的每个状态都有以次采样。例如(0=>1=>2)、(0=>1=>1=>2)、 (0=>1=>1=>=>2).
可以使用缩略新式(0=> 1[*3]=>2),如果1的次数需要重复3~5次,那么使用1[*3:5]。
交叉覆盖点
交叉覆盖点观测两个或两个以上的覆盖点或者表达式,成为交叉覆盖点。在一个组内可以先建立覆盖点,然后cross几个相关的覆盖点,组成交叉覆盖点。例如: cross a,b即为将覆盖点a 和b 交叉起来的情况。如果a 可能的情况有8种,b可能的情况有16种,那么交叉起来总共有168=128。
覆盖点记录的是单个变量或者表达式的观测值,有时用户希望观测两个变量交叉起来的覆盖情况,此时可以使用交叉覆盖率。如果有两个变量,其中一个有N种取值, 另一个有M种取值,交叉覆盖则有NM种取值。
class packet;
rand bit [3:0] port;
rand bit [2:0] kind;
endclass
packrt ppp;
covergroup cov;
kind:coverpoint ppp.kind;
port:coverpoint ppp.port;
cross kind,port;//cross关键字 一共128种
endgroup
为了增加代码可读性,可以对各个覆盖点的仓进行单独的标号
covergroup cov;
kind:coverpoint ppp.kind{
bins zero = {0};
bins lo = {[1:3]};
bins mi=default;
}
port:coverpoint ppp.port{
bins port[]={[0,$]};
}
cross kind,port;//cross关键字 一共128种
endgroup
功能覆盖率之“仓”
功能覆盖率首先是覆盖组,然后是覆盖点和交叉覆盖点,但是最终都是以“各种仓"作为基本元素(落脚点)。“仓” 即代表了这个数据或者这个表达式出现了该数据。仓可以分为以下几种情况:
自动建仓
用户建仓
可忽略的仓
非法仓
清除一些不需要的仓
自动建仓
通过auto_bin_max设置bin的数量(bins就是仓),数据有多少种情况,就会为其建立多少个仓。建仓的过程是systemverilog自动完成的,仓的最大个数一般为64。
假如元素的个数超过了64,则会把几个数字归为一类。 例如,一个16bit的变量有65536个可能值,所以64个仓中的每个仓都覆盖了1024个值。(第一个仓的值为0-1023以此类推)。
covergroup cov;
coverpoint tr.port// port为bins[2:0]一共八个仓
{options.auto_bin_max = 2}//分为两个后就是0-3一个4-7一个
endgroup
用户建仓
如果是自动建仓那就是0-7一共八个仓,并给出了仿真结果
module tb;
bit [2:0] mode;
covergroup cg;//这是一个覆盖组
converpoint mode{//对上面mode信号进行采样,mode一共0-7
bins a = {1};a的值是1,这就是仓
bins e = {5};
}
endgroup
initial begin
cg cg_inst=new();//实例化才能使用
for(int i = 0; i <5;i++)begin
#10 mode = $random;
$display("%0t mode = 0x%0h", $time,mode);
cg_inst.sample();//这是端口函数,对mode进行采样
end
$finish();
end
endmodule
在vcs中编译后会产生一个*.vdb文件,这个vdb属于一个覆盖率的数据库然后敲以下命令
之后会产生一个urgreport文件夹,然后用火狐浏览器打开其中的
其中绿色说明完美覆盖,右边的bins 至少击中一次,a为两次,e为一次。
可忽略的仓
仓与可能的产生的数据一一对应。但是有些数据就不能出现,此时就需要忽略这些仓以保证这些不可能的数据不会影响最终的覆盖率。
非法仓
非法仓
有些数据不该出现,此时就需要为这些数据设置非法仓以监测这些状态不能出现,如果出现非法仓便会报错。
批量的消除不需要的仓
某些情况下,数据都是合理有效的,但是- -旦和其他情况交叉起来后,有些情况是不必须的,此时需要消除这些仓。
在验证的过程中,可能会需要忽略大量的仓。此时就需要使用binsof(关注覆盖点),intersect (关注数据)和ignore_ bins 的组合来忽略大量的仓。
为枚举类型建仓
对于枚举类型,SV会为每个可能的值创建一个仓。如果用户想把多个数值放到单个仓里,那就需要自定义仓。auto_ bin_ max在收集枚举类型的覆盖率时不起作用。
typedef enum {INIT,DECODE,IDLE} state;
state c_state,n_state;
covergroup cov;
coverpoint c_state;
endgroup