1. 覆盖率类型
概述
- 覆盖率是衡量设计完备性的一个通用词语
- 随着测试逐步覆盖各种合理的组合,仿真过程会慢慢勾画出你的设计情况
- 覆盖率共居会在仿真过程中收集信息,然后进行后续处理并且得到覆盖率报告
- 通过这个报告找出覆盖之外的盲区,然后修改现有测试或者创建行动测试来填补这些盲区
- 这个过程可以一直迭代进行,直到你对覆盖率满意为止
覆盖率反馈回路
note:定向测试和随机约束并没有严格界线,随机约束很窄的时候也可以看作定向测试
- 可以使用一个反馈回路来分析覆盖率的结果,并决定采取哪种行动来达到100%覆盖率
- 首要的选择是使用更多的种子来运行现有的测试程序
- 当大量种子依然对于覆盖率增长没有帮助时,需要建立新的约束
- 只有在确实需要的时候才会求助于创建定向测试
代码覆盖率
- 不添加任何额外的HDL code,共居会通过分析源代码和增加隐藏代码来自动完成代码覆盖率的统计(只收集设计代码的覆盖率)
- 当运行完所有测试,代码覆盖率工具变回创建相应的数据库
- 放着呢起都带有代码覆盖率的工具,覆盖率数据也可以被转换为可读格式
- 行覆盖率:多少行代码已经被执行过
- 路径覆盖率:在穿过代码和表达式的路径中有哪些已经被执行过
- 翻转覆盖率:哪些单位比特变量的值为0或1
- 状态机覆盖率:状态机哪些状态和状态转换已经被访问过
- 代码覆盖率最终的结果用于衡量你执行了设计中的多少代码
- 关注点应该放在设计代码的分析上,而不是测试平台
- 未经测试的设计代码里可能隐藏硬件漏洞,也可能仅仅就是冗余的代码
- 代码覆盖率衡量的是测试对于硬件设计描述的“实现”究竟测试得有多彻底,而非针对验证计划
- 代码覆盖率达到了100%,并不意味着验证工作已经完整,但代码覆盖率100%是验证工作完备性的必要条件
note:工具只会自动收集code coverage
断言覆盖率
- 断言是用于一次性或在一段时间对一个或者多个设计信号在逻辑或者时序上的声明性代码
- 断言可以跟随设计和测试平台一起仿真,也可以被形式验证工具所证实
- 也可以使用SVA来表达
- 断言最常用于查找错误,如两个信号是否应该互斥,或者请求和许可信号之间的时序
- 一段检测到问题,仿真应该立即停止
- 有些断言可以用于查找感兴趣的信号值或者设计状态
- 可以使用cover property来测量这些关心的信号值或者状态是否发生
- 在方针结束时,仿真工具可以自动生成断言覆盖率数据
- 断言覆盖率数据以及其他覆盖率数据都会被集成在同一个覆盖率数据库中,verifier可对其展开分析
漏洞率曲线
- 在一个项目实施期间,应该保持追踪每周有多少个漏洞被发现
- 一开始,创建测试程序时,通过观察可能就会发现很多漏洞
- 当设计逐渐稳定,需要利用自动化的检查方式来协助发现可能的漏洞
- 在设计临近流片时,漏洞率会下降,甚至有望为零,即便如此,验证工作仍然不能结束
- 每次漏洞率下降时,就应该寻找各种不同的办法去测试可能的边界情况(corner case)
- 漏洞率可能每周都有变化,这跟很多因素都有关,不过漏洞率如果出现意外的变化可能预设着潜在的问题
功能覆盖率
- 验证的目的就是确保世界在实际环境中的行为正确
- spec详细说明了设计应该如何运行,而验证计划则列出了相应的功能应该如何激励,验证和测量
- 当手机测量数据希望好处那些功能已经被覆盖时,其实就是在计算设计的覆盖率
- 功能覆盖率和功能谁意图紧密相连的,有时也被称为描述覆盖率,而code coverage则是衡量设计的实现情况
- 某个功能在设计中可以被遗漏,code coverage不能发现这个错误,但是function coverage可以
note:覆盖率不只是功能仿真才可以做,形式验证也可以
function coverage收集的前提是testcase pass
- 每一次仿真都会产生一个带有覆盖率信息的数据库,记录随机游走的轨迹
- 把这些信息全部合并在一起就可以得到功能覆盖率,从而衡量整体的进展程度
- 通过分析覆盖率数据可以决定如何修改回归测试集
- 如果覆盖率在稳步增长,那么添加心中或者加长测试实际即可
- 如果覆盖率增速放缓,那么需要添加额外的约束来产生更多“有意思”的激励
- 如果覆盖率停止增长,然而设计某些测试点没有被覆盖到,那么就需要创建新的测试
- 如果覆盖率100%但依然有行动设计漏洞,那么覆盖率可能没有覆盖到设计中的某些设计功能区域
2. 功能覆盖策略
收集信息而非数据
- 对于MCDF,需要关心的是合法的寄存器地址和非法的寄存器地址,可写的寄存器域和非法的寄存器域,而不是具体的寄存器地址数值
- 一旦关注的地方着眼于感兴趣的状态,而不是具体数值,那么这对于如何定义功能覆盖率,以及如何收集信息会减轻很大的负担
- 设计信号如果数量范围太大,就应该拆分为多个小范围再加上边界情况
只测需要的内容
- verifier需要懂得,在是能覆盖率收集时,会降低很大的仿真性能
- 由于收集功能覆盖率数据的开销很大,所以只测量会用来分析并且改进测试的那部分数据
- 同时需要设定合理的覆盖率采样事件,一方面提升采样效率,一方面也可以降低收集覆盖率的开销
验证的完备性
- 完备的覆盖率测量结果和漏洞增长曲线,可以帮助确认设计是否被完整的验证过
- 如果代码覆盖率低但功能覆盖率高,这说明验证计划不完整,测试没有执行设计的所有代码
- 如果代码覆盖率高但功能覆盖率低,这说明即使测试平台很好的执行了设计的所有代码,但测试还是没有把设计定位到所有感兴趣的状态上
- 目标是同时驱动高的代码覆盖率和功能覆盖率
note:一般而言,code coverage和function coverage需要达到95%
3.覆盖组
概述
- covergroup与类类似,一次定义后便可以多次实例化
- covergroup可以包含一个或者多个coverpoint,且全都在同一时间采集(coverpoint在特定时刻用来监测某些特定的值)
- covergroup可以定义在类中,也可以定义在interface或者module中
- covergroup可以采样任何可见的变量,例如程序变量,接口信号或者设计端口
- 一个类里可以包含多个covergroup
- 当你拥有多个独立的covergroup时,每个covergroup可以根据需要自行使能或者禁止
- 每个covergroup可以定义单独的触发采样事件,允许从多个源头收集数据
- covergroup必须被例化才可以用来收集数据
note:covergroup,random,contraint均可以做使能
在类中定义covergroup
class Transactor; Transaction tr; mailbox mbx_in; covergroup CovPort; coverpoint tr.port; endgroup function new(mailbox mbx_in); CovPort = new( ); this.mbx_in = mbx_in; endfunction task main; forever begin tr = mbx_in.get; ifc.cb.port <= tr.port; ifc.cb.data <= tr.data; CovPort.sample( ); end endtask endclass
coverpoint可以采样sw,hw的变量,数值,当不定义具体数值时,采样所有可能数值
上述代码中,类型的名字和实例的名字一致。这种情况只能例化一次
也可以这样例化: CovPort cg1 = new(),可例化多次
covergroup的采样出发
- covergroup由采样的数据和数据被采样的事件构成
- 当这两个条件都准备好以后,测试平台变会出发covergroup
- 这个过程可以直接使用sample()函数完成,也可以在covergroup中采样阻塞表达式或者使用wait或@实现在信号或事件上的阻塞
- 如果你希望在code中显式地触发covergroup采样,或者不存在采样时刻的信号或事件,又或者一个covergroup被例化为多个实例需要单独触发,那么可以使用sample()方法
- 如果想借助已有的事件或者信号出发covergroup,可以再covergroup声明中使用阻塞语句
- 与直接调用sample( )相比,使用事件触发的好处在于能够借助已有的事件
event trans_ready; covergroup CovPort @(trans_ready); coverpoint ifc.cb.port; //也可以使用posedge进行采样 endgroup
4.数据采样
概述
- 当你在coverpoint指定采样一个变量或表达式时,SV会创建很多“仓(bin)”来记录每个数值被捕捉到的次数
- 这些bin是衡量功能覆盖率的基本单位
- covergroup中可以定义多个coverpoint,coverpoint中可以自定义多个cover bin或者SV帮助自动定义多个cover bin
- 每次covergroup采样,SV都会在一个或者多个cover bin中留下标记,用来记录采样时变量的数值和匹配的cover bin
- 在仿真后,可以使用分析工具读取这些数据库来生成覆盖率报告,包含了各部分和总体的覆盖率
- 所有的coverpoint的覆盖率最终构成一个covergroup的覆盖率
- 所有的covergroup的覆盖率构成了整体的功能覆盖率
bin的创建和应用
- SV会默认为某个coverpoint创建bin,用户也可以自己定义bin的采样域
- 如果采样变量的域范围过大而没有制定bin,那么系统会默认分配64个bin,将值域范围平均分配给这64个bin
- 用户可以通过covergroup的选项auto_bin_max来制定自动创建bin的最大数目
- 实际操作中,自动创建bin的方法不实用,建议用户自行定义bin,或者减小auto_bin_max的数值
covergroup CovPort; option.auto_bin_max = 8; //所有coverpoint auto_bin数量=8 coverpoint tr.port { option.auto_bin_max = 2;} //特定coverpoint auto_bin数量=2 endgroup
命名coverpoint和bin
note:coverpoint定义使用{}而不是begin...end,大括号结尾没有分号,和end一样
条件覆盖率
- 可以使用关键词iff给coverpoint添加条件
- 这种做法常用于在复位期间关闭覆盖以忽略不合理的条件触发
- 也可以使用start和stop函数来控制covergroup各个独立实例
covergroup CoverPort; coverpoint port iff(!bus_if.reset); endgroup initial begin CoverPort.ck =new(); #1ns; ck.stop(); bus_if.reset = 1; #100ns bus_if.reset = 0; ck.start(); ... ck.sample(); end
note : iff可以添加到covergroup,也可以添加到coverpoint
翻转覆盖率
- coverpoint也可以用来记录变量从A值到B值的跳转情况
- 还可以确定任何长度的翻转次数
covergroup CoverPort; coverpoint port{ bins t1 = (0=>1), (0=>2), (0=>3); } endgroup
wildcard覆盖率
- 可以使用关键字wildcard来创建多个状态或者翻转
- 在表达式中,任何x,z或者?都会被当成0或1的通配符
忽略的bin
- 在某些coverpoint可能始终无法得到全部的域值
- 对于哪些不计算功能的域值可以使用ignore_bins来排除,最终它们不会记入coverpoint的覆盖率
非法的bin
- 有些采样值不仅不该被忽略,而且如果出现还应该报错
- 这种情况可以使用illegal_bins对特定的bin进行表示
交叉覆盖率
- coverpoint记录的是单个变量或者表达式的观测值
- 如果像记录在某一时刻,多个变量之间值的组合情况,需要使用交叉(cross)覆盖率
- cross语句只允许带coverpoint或者简单的变量名
class Transaction; rand bit [3:0] kind; rand bit [2:0] port; enclass Transaction tr; covergroup CovPort; kind: coverpoint tr.kind; port: coverpoint tr.port; cross kind, port; endgroup
排除部分cross bin
- 通过使用ignore_bins,binsof和intersect分别指定coverpoint和值域,这样可以清楚很多不关心的cross bin
covergroup CovPort; port: coverpoint tr.port {bins port[] = {[0:$]}; } kind: coverpoint tr.kind { bins zero = {0}; bins lo = {[1:3]}; bins hi[] = {[8:$]}; bins misc = default; } cross kind, port{ ignore_bins hi = binsof(port) intersect {7}; ignore_bins md = binsof(port) intersect {0} && binsof(kind) intersect {[9:11]}; ignore_bins lo = binsof(kind.lo) } endgroup
精细的交叉覆盖率指定
- 随着cross覆盖率越来越精细,可能需要花费不少时间来指定哪些bin应该被使用或者被忽略
- 更合适的方式是自己声明
class Transction; rand bit a,b; endclass covergroup CrossBinNames; a: coverpoint tr.a { bins a0 = {0}; bins a1 = {1}; option.weight = 0;} //不计算覆盖率 b: coverpoint tr.b { bins b0 = {0}; bins b1 = {1}; option.weight = 0;} //不计算覆盖率 ab: cross a,b { bins a0b0 = binsof(a.a0) && binsof (b.b0); bins a1b0 = binsof(a.a1) && binsof (b.b0); bins b1 = binsof(b.b1); } endgroup
class Transction; rand bit a,b; endclass covergroup CrossBinNames; a: coverpoint tr.a { option.weight = 0;} //不计算覆盖率 b: coverpoint tr.b { option.weight = 0;} //不计算覆盖率 ab: cross a,b { bins a0b0 = binsof(a) intersect {0} && binsof (b) intersect {0}; bins a1b0 = binsof(a) intersect {1} && binsof (b) intersect {0}; bins b1 = binsof(b) intersect {1}; } endgroup
5.覆盖选项
单个实例的覆盖率
如果对一个covergroup例化多次,那么默认情况下,SV会将所有实例的覆盖率合并到一起,如果需要单独列出每个covergroup实例的覆盖率,需要设置覆盖选项
covergroup CoverLength; coverpoint tr.length; option.per_instance = 1; endgroup
注释
- 如果有多个covergroup实例,可以通过参数来对每一个实例传入单独的注释,这些注释最终会显示到覆盖率数据的总结报告中
covergroup Coverport(int lo,hi, string comment); option.comment = comment; option.per_instance = 1; coverpoint port {bins range = {[lo:hi]} } endgroup ... CoverPort cp_lo = new(0, 3, "Low port numbers"); CoverPort cp_hi = new(4, 7, "High port numbers");
覆盖次数限定
- 默认情况下,数值采样了1次就可以记入有效的bin,可以通过修改at_least来修改每个bin的数值最少的采样次数,如果低于at_least数值,则不会被计入bin
- option.at_least可以再covergroup中声明来影响所有的coverpoint,也可以在coverpoint中声明来只影响该coverpoint下所有的bin
覆盖率目标
一个covergroup或者一个coverpoint目标是100%覆盖率
也可以设置为低于100%目标,option.goal = 90%
covergroup方法
- sample():采样
- get_coverage()/get_inst_coverage():获取覆盖率,返回0-100的real数值
- set_inst_name(string):设置covergroup的名称
- start()/stop():使能或者关闭覆盖率的收集
6.数据分析
概述
- 使用$get_coverage()可以得到总体的覆盖率
- 也可以使用covergroup_inst.get_inst_coverage()来获取单个covergroup实例的覆盖V领
- 这些函数最实际的用处是在一个测试当中监测覆盖率的变化
- 如果覆盖率水平在一段时间后没有提高,那么这个测试就应该停止
- 重启新的随机种子或者测试可能有望提高覆盖率
- 如果测试可以基于功能覆盖率采取一些深入的行动,例如重新限定随机约束