西洲渡

一只还没迈入IC领域的小菜鸡

SV系统验证(9)——验证量化和覆盖率

概述

“验证如果没有量化,那么就意味着没有尽头。”
伴随着复杂SoC系统的验证难度系数成倍增加,无论是定向测试还是随机测试,我们在验证的过程中终究需要回答两个问题:

  • 是否所有设计的功能在验证计划中都已经验证?
  • 代码中的某些部分是否从未执行过?

覆盖率就是用来帮助我们在仿真中回答以上问题的指标,已经被广泛采用,作为衡量验证过程中的重要数据
一旦通过覆盖率来量化验证,我们可以在更复杂的情况下捕捉一些功能特性是否被覆盖:

  • 当我们在测试X特性的时候,Y特性是否也在同一时刻被使能和测试?
  • 是否可以精简我们已有的测试来加速仿真,并且取得同样的覆盖率?
  • 覆盖率在达到一定的数值的时候,是否停滞,不再继续上升?

简单而言,覆盖率就是用来衡量验证精度完备性的数据指标,可以告诉我们在仿真时设计的哪些结构被触发,当然,它也可以告诉我们设计在仿真时的哪些结构从未被触发过
只有满足以下三个条件,才可以在仿真中实现高质量的验证:

  • 测试平台必须产生合适的激励来触发一个设计错误
  • 测试平台仍然需要产生合适的激励使得被触发的错误可以进一步传导到输出端口
  • 测试平台需要包含一个监测器(monitor)用来监测被激活的设计错误,以及在它传播的某个节点(内部或者外部)可以捕捉到它

覆盖率种类

没有任何一种单一的覆盖率可以完备地去衡量验证过程即使我们可以达到100%的代码覆盖率,但这并不意味着100%
的功能覆盖率。原因在于代码覆盖率并不是用来衡量设计内部的功能运转,或者模块之间的互动,或者功能时序的触发等。
类似地,我们即便达到了100%功能覆盖率,也可能只达到了90%的代码覆盖率。原因可能在于我们疏漏了去测试某些功能,或者一些实现的功能并没有被描述。
从上述关于代码覆盖率和功能覆盖率简单的论述就可以证明,如果想要得到全面的验证精度,我们就需要多个覆盖率种类的指标
最常见的划分覆盖率的两种方法

  • 按照覆盖率生成的方法,即隐性生成还是显性生成。
  • 按照覆盖率溯源,即它们从功能描述而来还是从设计实现而来。

    例如功能覆盖率是显性的需要人为定义的覆盖率,而代码覆盖率则是隐性覆盖率这是因为仿真工具可以自动从RTL代码来生成。

代码覆盖率

代码覆盖率的一个优势在于它可以由仿真工具自动收集,继而用来指出在测试程序中设计源代码哪些被激活触发,而哪些则一直处于非激活的状态。由于代码覆盖率自动的特性,在仿真过程中使能它变得很简单,它不需要修改设计或者验证环境。

局限

  • 代码覆盖率100%并不意味着足够的功能覆盖率
  • 研究发现,在回归测试实现了90%的代码覆盖率时,平均只有54%的代码会被监测,这意味着即便代码覆盖率的完备性满足,但依然可能在这些代码中存在漏洞
  • 上述的推断是因为不是所有被覆盖的代码都会得到足够的监测,也由于没有得到足够的监测,因此一些即便被触发的漏洞也在传播的过程中没有到达监测点上——漏洞便从眼皮底下“逃逸”了
  • 另外,代码覆盖率的数据无法直接映射到哪些设计功能被测试了所以从这一点来看,代码覆盖率和功能覆盖率之间又是相互独立的。

跳转覆盖率(toggle)

  • 用来衡量寄存器跳转的次数(0->1,1->0)
  • 一般项目会要求模块的端口实现至少1次0到1,以及1次1到0的跳转来保证模块的集成和模块之间的互动,也有的项目会要求所有的寄存器都应该同端口一样满足跳转的最少次数
  • 端口跳转覆盖率经常用来测试IP模块之间的基本连接性,例如检查一些输入端口是否没有连接,或者已经连接的两个端口的比特位数是否不匹配,又或者一些已经连接的输入是否被给定的固定值等,都可以通过跳转覆盖率来发现问题

行覆盖率(statement/line)

  • 用来衡量源代码哪些代码行被执行过,以此来指出哪些代码行没有被执行过
  • 从每一行执行的次数,如果设置最小的执行次数,也可以用来做充足代码测试的一项指标
  • 代码覆盖率可以指出在一些缺乏输入激励的情况下,某些赋值的代码行没有被执行的情况,它也可以指出在一些漏洞影响或者无用代码的影响下,一些代码行也无法被执行的情况对于那些无用的代码,也就是永远不会被执行的代码,在代码分析时,我们可以将它们从覆盖率数据中过滤掉

分支覆盖率(branch)

  • 分支覆盖率是用来对条件语句(if/else,case,?:)指出其执行的分支轨迹
  • 例如判断下列分支的布尔表达式为true或者false

if(parity==ODD || parity==EVEN)begin

条件覆盖率(condition/expression)

  • 条件覆盖率是用来衡量一些布尔表达式中各个条件真伪判断的执行轨迹。
  • 例如下列if条件语句中的两个条件是否各自衡量为true/false
    1
    2
    3
    if(parity==ODD || parity==EVEN)begin
    parity==ODD 或者 parity !=ODD
    paraity==EVEN 或者parity !=EVEN

状态机覆盖率(FSM)

  • 仿真工具由于可以自动识别状态机,因此在收集覆盖率时,也可以将覆盖率状态的执行情况监测到。
  • 每个状态的进入次数,状态之间的跳转次数,以及多个状态的跳转顺序都可以由仿真工具记录下来。

功能覆盖率

概述

这一检查中可能会存在一些不期望的情况

  • 一些功能没有被实现
  • 一些功能被错误地实现了
  • 一些没有被要求的功能也被实现了

我们无法通过代码覆盖率得知要求的功能是否被实现了,而需要显性地通过功能覆盖率与设计功能描述做映射,继而量化功能验证的进程。
在约束随机测试流行于验证时,由于随机测试在仿真时会自动产生上千条测试激励,但我们却无法知道随机产生的激励究竟测试了什么功能。
功能覆盖率是最好地可以协助在回归测试时自动监测哪些功能被激活的方法。

创建功能覆盖率模型需要完成以下两个步骤

  • 从功能描述文档提取拆分需要测试的功能点
  • 将功能点量化为与设计实现对应的SV功能覆盖代码

覆盖组(covergroup)

  • 覆盖组与类相似,在一次定义以后便可以多次进行例化。
  • 覆盖组含有覆盖点(coverpoint)、选项(option)、形式参数(argument)和可选触发(trigger event)。
  • 一个覆盖组包含了一个或者多个数据点
  • 覆盖组可以定义在类里,也可以定义在模块或者程序(program)中。
  • 覆盖组可以采集任何可见的变量,比如程序或模块变量、接口信号或者设计中的任何信号
  • 在类中的覆盖组也可以采集类的成员变量
  • 覆盖组应该定义在适当的抽象层次
  • 对任何事务的采样都必须等到数据被待测设计接收到以后
  • 一个类也可以包含多个覆盖组,每个覆盖组可以根据需要将它们使能或者禁止
    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum { red, green, blue}color; 
    bit [3:0] pixel_adr, pixel_offset, pixel_hue;
    covergroup g2 @(posedge clk); //每个时钟上升沿都采样
    Hue:coverpoint pixel_hue; //采样 Hue为采样任务的名称
    Offset:coverpoint pixel_offset;
    AxC:cross color,pixel_adr;// cross 2 variables 3*16种全部
    all:cross color,Hue,Offset;// cross 1 VARs and 2 CPs
    endgroup
    g2 cg_inst=new();

仓(bin)

  • 关键词bins可以用来将每个感兴趣的数值均对一个独立的bin上或者将所有值对应到一个共同的bin

  • iff语句也可以用在bin的定义,它表示条件为false,那么右集该bin的时候,该bin的采样数自不会增长

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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; //除了上面定义的合法值都属于default
    }
    endgroup

    //covergroup的参数也可以被传递到bin的定义中,做数值覆盖
    covergroup cg (ref int ra, input int low, int high)
    @(posedge clk);
  • 除了可以覆盖数值,还可以覆盖数值的变化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    valuel => value2
    valuel => value3 => value4 => value5
    range_list1 => range_list21,5=>67
    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
    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,也就是通配符的意思

    1
    2
    wildcard bins g12_15={4b'11??};
    //g12_15可以用来表示12到15之间的值,即1100,1101,1110,1111
  • ignore_bins用来将其排除在有效统计的bin集合之外

    1
    2
    3
    4
    5
    covergroup cg23; 
    coverpoint a{
    ignore_bins ignore_vals={7,8};
    ignore_bins ignore_trans=(1=>3=>5);
    endgroup
  • illegal_bins用来指出采样到的数值为非法制,如果illegal_bins被采样到,那么仿真将报错

    1
    2
    3
    4
    5
    covergroup cg3; 
    coverpoint b{
    illegal_bins bad_vals={1,2,3};
    illegal_bins bad_trans=(4=>5=>6); //illegal_bins被采样到,那么仿真将报错
    endgroup

覆盖点(coverpoint)

  • 在定义coverpoint时可以不给名字或者给名字,我们建议给不同的coverpoint以不同的名字
  • 这些有名字的coverpoint可以用来做更进一步的处理,例如在交叉覆盖率中使用某个coverpoint
  • 可以通过iff在一些情况下禁止coverpoint的采集
    1
    2
    3
    covergroup g4; 
    coverpoint s0 iff(!reset);
    endgroup

交叉覆盖率(cross)

  • covergroup可以在两个或者更多的coverpoint或者变量之间定义交叉覆盖率(cross coverage)
  • 在对a和b产生交叉覆盖率之前,系统会先为它们隐性产生对应的coverpoint和bin,每个coverpoint都有16个自动产生的bin,两个coverpoint交叉以后将生成256个交叉的bin
  • 除了系统会自动为交叉覆盖率生成bin以外,用户还可以自己定义交叉覆盖率的
  • bin binsof()的参数可以是coverpoint或者变量,表示对应的bin总和,可以利用binsof()对其结果做进一步的过滤。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int i,j; 
    covergroup ct;
    coverpoint i { bins i[]={[0:1]};}
    coverpoint j { bins j[]={[0:1]};}
    x1: cross i,j; //因为两个变量都是32bits,所以交叉覆盖计算32*32位
    x2: cross i,j{
    bins i_zero=binsof(i).intersect {0};
    }
    endgroup

指定覆盖率选项

  • 覆盖率选项按照在covergroup、coverpoint和cross不同的域中是否具备,下表做了归纳:

image.png

预定义覆盖率方法

image.png

从功能描述到覆盖率

  • 功能覆盖率应该基于对DUT端口或者内部信号的监测
  • 功能覆盖率的有效性应该首先建立在测试通过的情况下,这还需要测试平台有自动检查(scoreboard/reference model)来保证对应的功能得到检验
  • 在创建覆盖率模型时,同创建property(assertion)一样,不要贪大求全,而应该对每一个covergroup、coverpoint、bin和cross都做到简明扼要,尽量将逻辑和时序都拆分开,便于后期的覆盖率分析
  • 选取一个适当的范围进行覆盖率收集,例如一个32位的整形变量,在收集数值时可以结合其与设计功能的特点,找到边界值、分段范围,再在合适的抽象层次上进行收集
  • 采用统一的覆盖率编码风格,例如covergroup的名字,采样的方式,是否填写comment,覆盖率模型置干何处等。
  • 要实现功能覆盖率的收敛,就需要按照以下步骤考虑:
    • 哪些功能需要测试
    • 明白在什么条件下需要测试对应的功能
    • 为了测试这些功能,需要提供什么样的测试平台组件以便提供激励和监测
    • 测试平台如何检查这些功能正常工作
  • 由于功能覆盖率不是自动的过程,因此它需要将功能描述同设计实现对应起来