目录
功能覆盖率是用来衡量哪些设计特征已经被测试程序测试过的一个指标。从设计规范着手,创建一个验证计划,详细列出要测试什么以及如何进行测试。验证计划是引导你开展工作的指南。
覆盖率的类型
代码覆盖率
衡量验证进展的最简易的方式是使用代码覆盖率。这种方式衡量的是多少行代码已经被执行过(行覆盖率),在穿过代码和表达式的路径中有哪些已经被执行过(路径覆盖率),哪些单比特变量的值为0或1(翻转覆盖率),以及状态机中哪些状态和状态转换已经被访问过(有限状态机覆盖率)。不用添加任何额外的 HDL代码,工具会通过分析源代码和增加隐藏代码来自动帮你完成代码覆益率的统计。当运行完所有测试,代码覆盖率工具便会创建相应的数据库。
代码覆盖率衡量的是测试对于设计规范的“实现"究竞测试得多彻底,而非针对验证计划。原因很简单.你的测试达到了100%的覆盖率,并不意味着你的工.作已经完成。
功能覆盖率
功能覆盖率是和设计意图紧密相连的,有时也被称为"规范覆盖率”,而代码覆盖率则是衡量设计的实现情况。设想某个代码块在设计中被漏掉的情况。代码覆盖率不能发现这个错误,但功能覆盖率可以。
漏洞率
衡量覆盖率的一个间接的方式是查看新漏洞出现的比率。
断言覆盖率
断言是用于一次性地或在一段时间内核对两个设计信号之间关系的声明性代码。它可以跟随设计和测试平台一起仿真,也可以被形式检查工具所证实。虽然在有些情况下你可以使用SystemVerilog 的程序性代码编写等效性检查,但是使用SystemVerilog 断言(sVA)来表达会更容易。
功能覆盖策略
收集信息而非数据
对于一个定容量的FIFO,FIFO的边界空满,如果将FIFO从空到满再由满到空,及覆盖了所有情形。
只测量将会用到的内容
测量的完备性
在开始项目时,代码覆盖率和功能覆盖率都很低。接着你开始测试,并且使用不同的随机种子反复进行到功能覆盖率不再增加。这时,创建额外的约束和测试去开发新的区域。保存那些给出高覆盖率的测试和种子组合,以备回归测试之用。
如果功能覆盖率很高但代码覆盖率很低,怎么办?这说明可能因为验证计划不完整,你的测试没有执行设计的所有代码。这时你应该回到硬件的设计规范并且更新验证计划。然后你需要增加更多针对未测试功能的功能覆盖点。
更麻烦的情形是,代码覆盖率很高但功能覆盖率很低。即使测试平台很好地执行了设计的所有代码.你还是没有把它定位到所有感兴趣的状态上。首先,查看设计是否实现了所有指定的功能。如果功能有了,但测试不到,可能需要一个形式验证工具来提取设计状态并创建适当的激励。
你的目标是同时取得高的代码和功能覆盖率。即使达到这个目标了,也先别急着安排休假。漏洞率的趋势如何?是否还在不断发现大的漏洞?更严峻的是,这些漏洞是你特意检查的,还是因为测试平台碰巧撞到了那些以前没有预见到的状态组合?另一方面,低的漏洞率可能意味着现有的策略已经到头了,你应该尝试不同的方法,例如设计块和错误产生的新组合。
覆盖组
覆盖组与类相似——一次定义后便可以进行多次实例化。它含有覆盖点、选项,形式参数和可选触发(trigger)。一个覆盖组包含了一个或多个数据点,全都在同一时间采集。
覆盖组应该带有明白无误的名字,用以表明要测量的对象,并且尽可能与验证计划关联。
覆盖组可以定义在类里,也可以定义在程序或模块层次上。它可以采样任何可见的变量,比如程序或模块变量、接口信号或者设计中的任何信号(使用层次化引用方式)。在类里的覆盖组可以采样类里的变量,以及嵌入类里的数值。
一个类里可以包含多个覆盖组。这种方法让你拥有了多个各自独立的组,每个组可以根据需要自行使能或禁止。此外,每个组可以有单独的触发﹐允许你从多个源头收集数据。
覆盖组可以在程序模块或类里定义。在所有的情况下,覆盖组都要进行明确的实例化后才可以开始采样。如果覆盖组定义在类里,实例化时使用最初的名字即可,不用另起名字。
覆盖组触发
覆盖组可以在程序模块或类里定义。在所有的情况下,覆盖组都要进行明确的实例化后才可以开始采样。如果覆盖组定义在类里,实例化时使用最初的名字即可,不用另起名字。
如果你希望在程序性代码中显式地触发覆盖组,或者不存在可以标识采样时刻的信号或事件,又或者在一个覆盖组里有多个实例需要独立触发,可以使用sample方法。
如果你想借助已有的事件或信号来触发覆盖组,可以在covergroup声明中使用阻塞语句。
使用回调函数进行采样
使用事件触发覆盖组
使用断言触发
数据采样
覆盖率信息是如何收集的?当你在覆盖点上指定一个变量或表达式时,SystemVerilog便会创建很多的“仓( bin)"来记录每个数值被捕捉到的次数。这些仓是衡量功能覆盖率的基本单位。如果你采样一个单比特变量,最多会有两个仓被创建。可以想见﹐每次覆盖组被触发,SystemVerilog 都会在一个或多个仓里留下标记。在每次仿真的末尾,所有带标记的仓会被汇聚到个新创建的数据库中。之后使用分析工具读取这些数据库就可以生成覆盖率报告,包含设计各部分和总体的覆盖率。
个体仓和总体覆盖率
为了计算出一个点上的覆盖率,首先必须确定所有可能数值的个数,这也被称为域。一个仓中可能有一个或多个值。覆盖率就是采样值的数目除以域中仓的数目。
自动创建仓
SystemVerilog会自动为覆盖点创建仓。它通过被采样的表达式的域来确定可能值的范围。对于一个位宽为N的表达式,有2的N次方个可能的值。对于3比特的port变量.存在着八个可能的值。枚举类型的域就是署名值的个数。你也可以显式地定义仓。
限制自动创建仓的数目
覆盖组选项auto_bin_max指明了白动创建仓的最大数目,缺省值是64。如果覆盖点变量或表达式的值域超过指定的最大值。SystemVerilog 会把值域范围平均分配给auto_bin_max个仓。例如,一个15比特变量有65 536个可能值,所以64个bin中的每一个都覆盖了1024个值。
对表达式进行采样
你可以对表达式进行采样,但始终都要核对覆盖率报告以确保能够得到预期的值,你可能不得不调整表达式计算出来的位宽。例如,对一个3比特头长度(0:7)加4比特负载长度(0:15)的加法表达式进行采样,只能得到2'即16个仓,如果你的数据实际上可以达到0:23个字节的话,仓数可能不够。
有一个覆盖组对事务的总长度进行采样。每个覆盖点都有标识,可以增加覆盖率报告的可读性。此外﹐带有额外常量哑元的表达式可以计算达到5比特精度,从而把自动生成的最大仓数扩大到32。
进行200次简单的仿真,可以得到len16有100%的覆盖率,但这只相对应于16个仓。覆盖点len32有68%的覆盖率,相对应于32个仓。这两个覆盖点得到的数据都不准确,因为域值的最大长度实际上是0:22((0+0):(7+15))。由于最大长度不是2的幂,所以自动生成的仓并不适用。
自动生成的仓适用于匿名数值,如计数值、地址值或2的幂值。而对于其他数值,你.应该明确对仓命名,以增加准确度并有利于对覆盖率报告的分析。SystemVerilog 会自动为枚举类型的仓命名,但对于其他变量,你需要为感兴趣的状态命名。命名仓的最简单的方式是使用[]。
命名覆盖点的仓
注意coverpoint是使用大括号{}围起来的。这是因为对仓的命名是声明语句而非程序性语句,后者才用begin. ..end围起来。最后,大括号的末尾并没有带分号,这和end一样。
那些不在指定仓涵盖范围内的数值会被忽略掉。
条件覆盖率
可以使用关健字iff给覆盖点添加条件。这种做法最常用于在复位期间关闭覆盖以忽略掉一些杂散的触发。
为枚举类型创建仓
如果想把多个数值放到单个仓里,那就必须自己定义仓。所有在枚举数值之外的仓都会被忽略掉,除非使用default标识符定义一个仓。auto _bin_max在收集枚举类型的覆盖率时不起作用。
翻转覆盖率
可以先确定覆盖点状态转移的次数。这样,你不仅可以知道有哪些感兴趣的值出现过,还可以知道这些值的变化过程。
使用范围表达式可以快速地确定多个转换过程。表达式(1,2=> 3,4)创建了四个翻转过程,分别是(1=>3)、(1=>4)、(2=>3)和(2=>4)。
还可以确定任何长度的翻转次数。注意必须对转换过程中的每个状态都进行一次采样。所以(0=>1=>2)不同于(0=>1=>1=>2)和(0=>1=>1=>1=>2)。如果你需要像最后一个式子那样重复数值,可以使用缩略形式: form: (0=>1[*3]=>2)。如果需要对数值1进行3次.4次或5次重复,那么使用1[*3:5]。
在状态和翻转中使用通配符
可以使用关键字wildcard来创建多个状态或翻转。在表达式中.任何x、z或?都会被当成0或1的通配符。下例创建了一个带有两个仓的覆盖点,一个仓代表偶数值、另一个代表奇数值。
忽略数值
可以让 SysternVerilog自动创建仓,然后使用ignore_bins排除掉那些不用来计算功能覆盖率的数值。
三比特变量low_ports_0_5最初的范用是0:7。ignore_bins排除掉最后的两个仓,从而把范围缩小到0:5。所以这个组的总体覆盖率是采样到的仓数除以总仓数,这里总仓数是6。
不合法的仓
有些采样值不仅应该被忽略,而且如果出现还应该报错。这种情况最好在测试平台中使用代码进行监测,但也可以使用illegal_bins对仓进行标识。使用illegal_bins可以捕捉到那些被错误检查程序遗漏掉的状态。这同样也可以对你创建仓的准确性进行双重检查:如果在覆盖组中发现了不合法的数值,那就是你的测试程序或者bin定义出了问题。
状态机的覆盖率
你应该已经注意到了.如果在状态机上使用了覆益组,那么就可以使用仓来列出特定的状态和它们的翻转轨迹。但这并不意味着你使用SystemVerilog 的功能覆盖率就能得到状态机的覆益率,必须手工提取状态和翻转轨迹。即使你第一次做对了,以后随着设计代码的改变还是可能会出错。相反地,使用代码覆盖率.工具来自动提取状态寄存器、状态以及翻转轨迹,可以使你免于出错。
然而,自动工具会忠实地提取出代码里的信息,包括错误以及所有内容。在有些情况下,你可能还是希望通过手工方式使用功能覆盖率来监控小部分关键的状态机。
交叉覆盖率
覆盖点记录的是单个变量或表达式的观测值。交叉覆益率,它可以同时测量两个或两个以上覆盖点的值。注意当你想测量两个变量的交叉覆盖率,其中一个有N种取值,而另一个有M种取值时,SystemVerilog需要NXM个交叉仓来存储所有的组合。
SystemVerilog中的 cross结构可以用来记录一个组里两个或两个以上覆盖点的组合值。cross语句只允许带覆盖点或者简单的变量名。如果你想使用表达式、层次化的名字或者对象中的变量,例如handle、variable,你必须首先对coverpoint里的表达式使用标号,然后把这个标号用在cross语句里。
对交叉覆盖仓进行标号
如果定义了包含多个数值的仓,那么覆盖率的统计结果将会有所变化。在下面的报告里,仓数从128降到88。这是因为kind里有11个仓:zero、lo、hi_8、hi_9、hi_avhi_b、hi_c,hi_d、hi_e、hi_f和 misc,覆盖率的百分比从87.5%跃升到90.91%。因为只要1o仓里有一个值出现,比如2,那这个仓就会被认为是覆盖过的;即使其他数值,比如1或3,没有出现也一样。
排除掉部分交叉覆盖仓
使用ignore_bins 可以减少仓的数目。在交叉覆盖中,你可以使用binsof 和intersect分别指定覆盖点和数值集,这样可以使单个的ignore_bins结构清除掉很多个体仓。
第一个ignore_bins 只排除掉了所有代表port为7和任意kind值组合的仓。因为kind是一个4比特数值.这个语句排除掉了16个仓。第二个ignore _bins更具有选择性·忽略掉的是 port为0和kind为9、10、1l的组合总共3个仓。
在ignore_bins 中可以使用已经在各个覆盖点中定义的仓。ignore_bins lo就使用了仓名来排除掉kind.lo,也就是1,2或3。这个仓名必须是在编译之前就已经定义好的,比如 zero和 lo。像hi_8、hi_9、hi_a……hi_f,以及自动生成的仓在编译之前都是没有名宇的;它们的名字是在编译或生成报告时才被创建的。
注意binsof使用的是小括号(),而 intersect指定的是一个范围,所以使用大括号{}.
从总体覆盖率中排除掉部分覆盖点
一个组的总体覆盖率是基于所有简单覆盖点和交叉覆盖率的。如果你只希望对一个coverpoint上的空址或表达式进行采样,面这个coverpoint将会被用到cross 语句中,那么你应该把它的权重设置成0这样它就不会对总体的覆盖率造成影响。
交叉覆盖的替代方法
随着交叉覆益的定义越来越精细,可能需要花费可观的时间来指定哪些仓应该使用或者被忽略掉。假设有两个随机比特变量a和 b,它们带着三种感兴趣的状态,{a==0,b=0}. (a== 1 , b==0}和{b==1}。
使用仓名的交叉覆盖率
使用binsof的交叉覆盖率
使用串联值替代交叉覆盖
通用的覆盖组
SystemVerilog 允许你创建一个通用的覆盖组,这样在对它进行实例化时就可以指定一些独特的细节。SystemVerilog 不允许把覆盖组的触发参数传递给实例。作为变通的办法.你可以把覆盖组放到一个类里;然后把触发参数传递给构造函数。
通过数值传递覆盖组参数
通过引用传递覆盖组参数
如果你不仅想在调用构造哟数时使用数值.还希望覆盖组在整个仿真过程中可以对数值进行采样,那么可以通过引用的方式来指定需要进行采样的变量。
与任务和函数相似.覆盖组的参数在方向上遵循就近缺省的原则。
覆盖组选项
可以使用SystemVerilog 提供的选项为覆盖组指定额外的信息。选项分两种类型:一种是实例选项,用于特定的覆盖组实例;一种是类型选项,用于所有的覆盖组实例,类似于类中的静态数据成员。选项可以放在覆盖组中并对组里的所有覆盖点有效,也可以放在单个覆盖点中以便实现更加精细的控制。前面已经介绍过auto_bin_max和 weight选项,下面还有其他一些选项。
单个实例的覆盖率
如果你的测试平台对一个覆盖组进行了多次实例化,那么缺省情况下 SystcmVerilog,公把所有实例的覆盖率数据汇集到一起。然而,如果你有几个发生器,每个发生器所产生的事务数据流都不相同,你需要查看单独的报告。例如,一个发生器创建的是长数据而另一个则创建的是短数据。
选项per-instance只能放在覆盖组里,不能用于覆盖点或交叉点。
覆盖组的注释
覆盖阈值
如果你相信一个仓被命中8次以后,其所对应的所有组合就都能被测试到,那么可以把option.at_least设置为8或更高。option.at_least如果定义在覆盖组里,那么它会作用于所有的覆盖点。如果定义在一个点上那它就只对该点有效。
打印空仓
缺省情况下,覆盖率报告只会给出带有采样值的仓。你的工作是检查所有列在验证计划上的情况是否都被覆盖了,所以你实际上对那些没有采样值的仓会更感兴趣。使用cross_num_print_missing 选项可以让仿真和报告工具给出所有的仓,尤其是那些没有被命中的仓。把它的值设置得大一些,但不要超出你愿意阅读的范围。
覆盖率目标
一个覆盖组或覆盖点的目标是达到该组或该点被认为已经完全覆盖的水平。缺省情况是100%的覆盖率。如果你把它设置为低于100%,这样的要求会比完全覆盖低,可能并不是你真正想要的。这个选项只影响覆盖率报告。
仿真过程中统计覆盖率
在全局层面上,使用$get_coverage可以得到所有覆盖组的总覆盖率。$get_coverage返回一个介于0~100的实数。该系统任务可以查询到所有的覆盖组。
你可以使用get_coverage()和get_inst_coverage()函数来缩小测量范围。其中第一个函数可以带覆盖组名和实例,用于给出一个覆盖组所有实例的覆盖率,其用法如CoverGroup::get_coverage ()或cgInst.get_coverage ()。第二个函数返回一个特定覆盖组实例的覆盖率,用法如cgInst.get inst coverage()。如果你希望得到单个实例的覆盖率,那么需要指定option.per_instance=1。