SystemVerilog之覆盖率详解

1.0 覆盖率前言

覆盖率是用来衡量设计验证完备性。随着测试逐步覆盖各种合理的组合,覆盖率用来衡量测试进行的程度。覆盖率工具会在仿真过程中收集信息,然后进行后续处理并且得到覆盖率报告。通过报告找出覆盖盲区,然后修改现有test或者创建新的test来填补这些盲区。

覆盖率反馈环路:

在这里插入图片描述

带约束的随机测试与定向测试这两种环路一直迭代,直到达到要求

收集覆盖率:最简单的办法就是改变随机种子seed,就可以反复运行同一个随机测试平台来产生新的激励。每一次仿真都会产生一个带有覆盖率信息的数据库,将这些信息全部合并在一起就可以得到功能覆盖率。

在这里插入图片描述

1.1 覆盖率类型

  • 代码覆盖率:直接去检查RTL Code

    代码覆盖率衡量的是测试对于硬件设计描述的“实现”究竟测试得有多彻底,而非针对验证计划。

    • line coverage:所有行是否都运行到
    • paths coverage:所有路径是否以cover到
    • toggle coverage:所有变量都有过0,1翻转
    • FSM coverage:状态机中所有状态都已运行到

    很多的仿真工具都带有代码覆盖率工具,而得到的最终结果就是检测对设计代码运行程度的衡量。代码覆盖率最终的结果用于衡量所有testcase执行了设计中的多少代码。 关注点在设计代码RTL的分析上,而不是测试平台。

    未经测试的设计代码里可能隐藏硬件漏洞,也可能是冗余代码。

    代码覆盖率达到了100%,并不意味着验证的工作已经完成,但代码覆盖率100%是验证工作完备性的必要条件

  • 功能覆盖率:

    验证的目的: 确保设计在实际环境中的行为正确。功能描述文档详细说明了设计应该如何运行,而验证计划则列出了相应的功能应该如何激 励、验证和测量。功能覆盖率是和功能设计紧密相连的,而代码覆盖率则是衡量设计的实现情况。若某个功能在设计中被遗漏,代码覆盖率不能发现这个错误,但是功能覆盖率可以。

  • 断言覆盖率

    断言:用于一次性地或在一段时间内核对两个或两个以上设计信号之间关系的声明性代码。断言覆盖率可以测量某断言被触发的频繁程度。断言可以跟随设计和测试平台一起仿真,也可以被形式验证工具所证实。可以使用SV的程序性代码编写等效性检查,但使用SVA(SV断言)来表达会更容易。断言最常用于查找错误,例如两个信号间的相位关系,是否应该互斥或者请求与许可信号之间的时序等。

    一旦检测到问题,仿真就可以立即停止。

    断言可以用于查找感兴趣的信号的值或者状态。 可以使用cover property来测量这些关心的信号的值或者状态是否发生。在仿真结束时,仿真工具可以自动生成断言覆盖率数据。断言覆盖率数据以及其它覆盖率数据都会被集成在同一个覆盖率数据库中,verifier可以对其展开分析。

漏洞率以及漏洞曲线:

在一个项目实施期间,应该保持追踪每周有多少个漏洞被发现。一开始,当创建测试程序时,通过观察可能就会发现很多漏洞。当设计逐渐稳定时,需要利用自动化的检查方式来协助发现可能的漏洞。在设计临近流片时,漏洞率会下降,甚至有望为零。即便如此,验证工作仍然不能结束。每次漏洞率下降时,就应该寻找各种不同的办法去测试可能的边界情况(corner case)

漏洞率可能每周都有变化,这跟很多因素都有关。不过漏洞率如果出现意外的变化,可能预示着潜在的问题。

在这里插入图片描述

1.2 覆盖策略及覆盖组

  • 覆盖策略:只测量需要的内容,例如初始化阶段不需要测试

    • 只测试需要的内容:验证工程师需要懂得,在使能覆盖率收集时,这一特性会降低很大的仿真性能。由于收集功能覆盖率数据的开销很大,所以应该只测量你会用来分析并且改进测试的那部分数据。同时也需要设定合理的覆盖率采样的事件,一方面提升采样效率,一方面也可以降低收集覆盖率的开销。

    • 验证的完备性:完备的覆盖率测量结果和漏洞增长曲线,可以帮助确认设计是否被完整地验证过。如果功能覆盖率高但代码覆盖率低,这说明验证计划不完整,测试没有执行设计的所有代码。如果代码覆盖率高但功能覆盖率低,这说明即使测试平台很好地执行了设计的所有代码,但是测试还是没有把设计定位到所有感兴趣的状态上。

      你的目标是同时驱动高的代码覆盖率和功能覆盖率。

    • 覆盖率比较:

      在这里插入图片描述

  • 覆盖组covergroup

覆盖组与类相似,一次定义就可以多次实例化且全都在同一时间采集。一个覆盖组包含覆盖点,选项,形式参数和可触发(trigger),一个或多个数据点。

covergroup可以定义在类中,也可以定义在interface或者module中,一个类里可以包含多个covergroupcovergroup可以采样任何可见的变量,例如程序变量、接口信号或者设计端口。

当你拥有多个独立的covergroup时,每个covergroup可以根据需要自行使能或者禁止。每个covergroup可以定义单独的触发采样事件,允许从多个源头收集数据。covergroup必须被例化才可以用来收集数据。

  • 入门案例:一个简单对象的功能覆盖率,测试程序使用CovPort覆盖组对port字段的数值进行采样,八种可能的数值,32次随机。

    interface busifc;
        logic clk;
        logic [31:0] data;
        logic [2:0] port;
    endinterface
    
    class Transaction;
        rand bit [31:0] data; 
        rand bit [2:0] port; // 八种端口数据
    endclass 
    
    program automatic test(busifc.TB ifc);
        covergroup CovPort
            coverpoint ifc.port; //测试覆盖率:检测8个port是否都出现过
        endgroup 
    
        initial begin 
            Transaction tr; 
            CovPort ck; 
            ck = new(); // 实例化组
            tr = new(); 
            repeat (32) begin 		  // 运行几个周期
                assert(tr.randomize); 
                ifc.port <= tr.port;  // 发送到接口上
                ifc.data <= tr.data;  
                ck.sample(); 		  // 收集覆盖率
                @ifc.clk; 		      // 等待一个周期
            end 
        end 
    endprogram
    

    8中可能,32次随机,该例的VCS的覆盖率报告(一部分)如下:

    Coverage:87.50
    	..........
    bin         #hit       at least
    :::::::::::::::::::::::::::::::::::
    auto[1]       7           1
    auto[2]       7           1
    auto[3]       1           1
    auto[4]       5           1
    auto[5]       4           1
    auto[6]       2           1
    auto[7]       6           1
    
  • 例:在类里使用功能覆盖率

    class Transaction;
        rand bit [31:0] data; 
        rand bit [2:0] port;
    endclass 
    
    class Transactor;
        Transaction tr; 
        mailbox mbx_in;
        virtual busifc ifc;
    
        covergroup CovPort;
            coverpoint tr.port;
        endgroup 
    
        // 由于覆盖组实例化在new里面,因此在program不需要实例化
        function new(mailbox mbx_in, virtual busifc ifc); 
            CovPort = new(); 
            this.mbx_in = mbx_in; 
            this.ifc = ifc;
        endfunction 
    
        task main; 
            begin
                mbx_in.get(tr); 
                ifc.port <= tr.port;
                ifc.data <= tr.data; 
                CovPort.sample();
            end 
        endtask 
    endclass
    
    interface busifc;
        logic clk;
        logic [31:0] data;
        logic [2:0] port;
    endinterface
    
    program automatic test(busifc ifc);
        Transaction tr;
        Transactor tr_c;
        mailbox mbx_in;
        initial begin
            tr = new();
            mbx_in = new();
            tr_c = new(mbx_in,ifc);
            repeat (32) begin 
                assert(tr.randomize); 
                mbx_in.put(tr);
                tr_c.main();
                @(posedge ifc.clk);
            end
        end
    endprogram
    
    module top;
        busifc ifc();
        initial begin
            ifc.clk=0;
            forever #10 ifc.clk=~ifc.clk;
        end
        test test_u(ifc);
    endmodule
    

触发: covergroup由采样的数据和数据被采样的事件构成。当这两个条件都准备好以后,测试平台便会触发covergroup。这个过程可以通过直接使用sample()函数完成,也可以在covergroup中采样阻塞表达式或者使用wait或@实现在信号或事件上的阻塞。如果想在代码中显式地触发covergroup采样,或者不存在采样时刻的信号或事件,又或者一个covergroup被例化为多个实例需要单独触发,那么可以使用sample()方法。如果你想借助已有的事件或者信号触发covergroup,可以在covergroup声明中使用阻塞语句。

  • 使用事件触发:

    event trans_ready;
    covergroup CovPort @(trans_ready);
        coverpoint ifc.cb.port;
    endgroup
    

    与直接调用sample()相比,使用事件触发的好处在于你能够借助已有的事件,来决定何时开始收集数据

1.3 覆盖率数据采样

当在覆盖点上指定一个变量或者表达式时,SV便会创建很多的仓bin来记录每个数值被捕捉到的次数。这些bin是衡量覆盖率的基本单位。为了计算出一个点上的覆盖率,首先要了解域的概念,域是所有可能值的的个数。覆盖率就是采样值的数目除以域中bin的数目。

covergroup中可以定义多个coverpoint,coverpoint中可以自定义多个cover bin或者SV帮助自动定义多个cover bin,每次covergroup采样,SV都会在一个或者多个cover bin中留下标记,用来记录采样时变量的数值和匹配的cover bin。

例如:一个3bit变量的域为0:7,正常情况下会除以8个bin,如果在仿真的过程中只有7个值被采样到,那么报告就会给出这个coverpoint点的覆盖率是7/8也即87.5%。所有这些coverpoint点组合在一起就是covergroup组的覆盖率,所有covergroup组组合在一起就是整个的仿真覆盖率。而这也仅仅是单个仿真的情况,在实际中需要追踪覆盖率随时间的变化情况,分析趋势。

1.3.1 bin的创建与使用

SV会默认为某个coverpoint创建bin,用户也可以自己定义bin的采样域。如果采样变量的域范围过大而又没有指定bin,那么系统会默认分配64个bin,将值域范围平均分配给这64个bin。用户可以通过covergroup的选项auto_bin_max来指定自动创建bin的最大数目。

实际操作中,自动创建bin的方法不实用,建议用户自行定义bin(整体的去定义,或定义单独的点),或者减小auto_bin_max的数值。

covergroup covport;
    // 所有coverpoint auto_bin数量=8
    option.auto_bin_max = 8; 
    
    // 特定coverpoint auto_bin数量=2
    coverpoint tr.port {
        option.auto_bin_max = 2;
    } 
endgroup

延用本章的入门案例,port位宽为3,值域是0个可能的值,因此第一个仓保存的是值域范围的前半段0-3,第二个仓保存的是后半段4-7,VCS给出的覆盖率报告部分如下:

bin         #hit     at least
:::::::::::::::::::::::::::::::::::
auto[0-3]    15        1
auto[4-7]    17        1

除此之外,还可以单独命名覆盖点的仓,例如下面对一个4bit的变量kind进行采样,有16种可能,对其进行单独的覆盖采样:

第一个仓:命名为zero,对kind=0的情况进行计数

第二个仓:命名为lo,对1~3,5这四个值进行计数

第三个仓:命名为hi,将8~15保存到单独的仓里,分别为:hi_8,hi_9,hi_a,hi-b … hi_f

第四个仓:命名为misc,对前面没有出现过的值进行采样,也即4,6,7

covergroup CovKind;
    coverpoint tr.kind {
        bins zero = {0}; // 1个仓代表kind**0
        bins lo = {[1:3], 5}; // 1个仓代表1:35
        bins hi[] = {[8:$]}; // 8个独立的仓代表8:15
        bins misc = default; // 1个仓代表剩余的所有值
    } // 没有分号
endgroup

注意coverpoint定义使用{}而不是begin…end,大括号的结尾没有带分号;,VCS的部分报告如下

Bin       #hits     at least
:::::::::::::::::::::::::::::::::::
hi_8        0          1
hi_9        5          1
hi_a        3          1
hi_b        4          1
hi_c        2          1
hi_d        2          1
hi_e        9          1
hi_f        4          1
lo          16         1
misc        15         1
zero        1          1

1.3.2 条件覆盖率

可以使用关键词iff给coverpoint添加条件。这种做法常用于在复位期间关闭覆盖以忽略不合理的条件触发

例:仅收集reset=0(高电平有效)时port的值。

covergroup CoverPort;
    coverpoint port iff(!bus_if.reset);
endgroup

同样的,也可以使用start和stop函数来控制 covergroup各个独立实例。

initial begin
    covport ck = new();
    // 复位期间停止收集覆盖率
    #1ns;
    ck.stop();
    bus_if.reset = 1;
    #100ns bus_if.reset = 0;
    // 复位结束
    ck.start();
    ... 
end

1.3.3 翻转覆盖率

coverpoint也可以用来记录变量从A值到B值的跳转情况。还可以确定任何长度的翻转次数。

例:查询port有没有从0变为1,2,3。

covergroup CoverPort;
    coverpoint port {
        bins t1 = {(0 => 1), (0 => 2), (0 => 3)};
    }
endgroup

1.3.4 wildcard覆盖率

可以使用关键字wildcard来创建多个状态或者翻转。在表达式中,任何X,Z或者?都会被当成0或1的通配符。

例如:采样偶数与奇数,两个仓分别代表奇数偶数。

bit [2:0] port;
covergroup CoverPort;
    coverpoint port {
        wildcard bins even = {3'b??0};
        wildcard bins odd = {3'b??1};
    }
endgroup

1.3.5 忽略bin与非法bin

  • 忽略bin:排除那些不用来计算覆盖率的数值

    在某些coverpoint可能始终无法得到全部的域值。对于那些不计算功能的域值可以使用ignore_bins来排除,最终它们并不会计入coverpoint的覆盖率。

    例1:使用ignore_bins的覆盖点

    bit [2:0] low_ports_0_5; // 只使用数值0-5
    covergroup CoverPort;
        coverpoint low_ports_0_5 {
            ignore_bins hi = {[6,7]}; // 忽略数值6-7
        }
    endgroup
    

    由于忽略了数值6-7,因此总仓数是6

    例2:使用ignore_binsauto_bin_max的覆盖点

    bit [2:0] low_ports_0_5; // 只使用数值0-5
    covergroup CoverPort;
        coverpoint low_ports_0_5 {
            options.auto_bin_max=4;   // 0:1 2:3 4:5 6:7
            ignore_bins hi = {[6,7]}; // 忽略数值6-7
        }
    endgroup
    

    创建了四个仓,但由于最后一个仓被ignore_bins所忽略,所以只有三个仓被创建,因此覆盖率只有四种可能值,分别为0%,33%,66%和100%。

  • 非法bin:有些采样值不仅应该被忽略,如果出现了还应该报错。

    这种情况可以在测试平台中监测,也可以使用illegal_bins对特定的bin进行标示。

    bit [2:0] low_ports_0_5; // 只是用数值0-5
    covergroup CoverPort;
        coverpoint low_ports_0_5 {
            illegal_bins hi = {[6,7]}; // 如果出现6-7便报错
        }
    endgroup
    

1.4 交叉覆盖率

coverpoint是记录单个变量或者表达式的观测值。如果像记录在某一时刻,多个变量之间值的组合情况,需要使用交叉(cross) 覆盖率。cross语句只允许带coverpoint或者简单的变量名。

class Transaction;
    rand bit [3:0] kind;
    rand bit [2:0] port;
endclass

Transaction tr;
covergroup CovPort;
    kind: coverpoint tr.kind;
    port: coverpoint tr.port;
    cross kind, port;
endgroup

关心kind与port的组合情况的覆盖率。

1.4.1 排除部分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

1.4.2 精细化交叉覆盖率

随着cross覆盖率越来越精细,需要花费不少的时间来指定哪些bin应该被使用或者被忽略。更适合的方式是不使用自动分配的cross bin,自己声明感兴趣的cross bin。假如有两个随机变量a和b,只关心下面三种状态,{a**0,b**0}、 {a**1,b**0}和{b**1}

class Transaction;
    rand bit a, b;
endclass
covergroup CrossBinNames;
    a: coverpoint tr.a
    { 
        bins a0 = {0};
        bins a1 = {1};
         // 不计算覆盖率
        type_option.weight=0;
    }
    b: coverpoint tr.b
    { 
        bins b0 = {0};
        bins b1 = {1};
         // 不计算覆盖率
        type_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 Transaction;
    rand bit a, b;
endclass
covergroup CrossBinsofIntersect;
    a: coverpoint tr.a
    { 
        // 不计算覆盖率
        type_option.weight=0; 
    }
    b: coverpoint tr.b
    {
        // 不计算覆盖率
        type_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

1.4.3 单个实例的覆盖率

如果对一个covergroup例化多次,那么默认情况下SV会将所有实例的覆盖率合并到一起。如果需要单独列出每个covergroup实例的覆盖率,则需要设置覆盖选项。

covergroup CoverLength;
    coverpoint tr.length;
    // 设置单个实例覆盖率
    option.per_instance = 1;
endgroup

1.4.4 注释

如果有多个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.4.5 覆盖率次数限定与目标

  • 覆盖率次数限定:

    默认情况下,数值采样了1次就可以计入有效的bin。可以通过修改at_least来修改每个bin的数值最少的采样次数,如果低于at_least数值,则不会被计入bin中。

    option.at_least可以在covergroup中声明来影响所有的coverpoint,也可以在coverpoint中声明来只影响该coverpoint下所有的bin。

    一般不会更改。

  • 覆盖率目标

    一个covergroup或者一个coverpoint的目标是100%覆盖率。

    不过你也可以将其设置为低于100%的目标。这个选项只会影响覆盖率报告。

    covergroup CoverPort;
        coverpoint port;
        option.goal = 90;
    endgroup
    

    添加上之后,如果达到了90%,报告就会显示100%,一般也不会更改

1.4.6 covergroup方法总结

sample():采样。

get_coverage()/get_inst_coverage():获取覆盖率,返回0-100的real数值。

set_inst_name(string):设置covergroup的名称。

start()/stop():使能或者关闭覆盖率的收集。

  • 覆盖率分析
    1. 使用$get_coverage()可以得到总体的覆盖率。
    2. 也可以使用covergroup_inst.get_inst_coverage()来获取单个covergroup实例的覆盖 。
    3. 这些函数最实际的用处是在一个测试当中监测覆盖率的变化。
    4. 如果覆盖率水平在一段时间之后没有提高,那么这个测试就应该停止。
    5. 重启新的随机种子或者测试可能有望提高覆盖率。
    6. 如果测试可以基于功能覆盖率采取一些深入的行动,例如重新限定随机的约束,那是一件非常好的事情,但这种测试很难编写。

现在很少去用命令行分析了,都是采用图形化界面的方式去分析

1.4.7 案例

添加bins,并收集coverage信息,查看 bins的收集情况

TIPS: makefile elab的时候要添加覆盖率选项: -cm tgl+branch run中也要添加: -cm tgl+branch -cov_dir top_cov

查看报告: dve -full64 -cov -dir top_cov/top.simv.vdb

生成报告:urg -dir top_cov/top.simv.vbd -format both -report report

-dir:vbd文件

-format:生成的格式:txt或html,both是两者都要生成

-report:报告文件目录

class Transaction;
    rand bit [4:0] data; 
    rand bit [2:0] port;
endclass 

class Transactor;
    Transaction tr; 
    mailbox mbx_in;
    virtual busifc ifc;

    covergroup CovPort;
        port: coverpoint tr.port 
        {
            bins port[] = {[0:$]};
        }
        data: coverpoint tr.data
        {
            bins zero = {0}; 
            bins lo = {[1:3]};
            bins hi[] = {[8:$]};
            bins misc = default;
        }
        cross port,data;
    endgroup 

    // 由于覆盖组实例化在new里面,因此在program不需要实例化
    function new(mailbox mbx_in, virtual busifc ifc); 
        CovPort = new(); 
        this.mbx_in = mbx_in; 
        this.ifc = ifc;
    endfunction 

    task main; 
        begin
            mbx_in.get(tr); 
            ifc.port <= tr.port;
            ifc.data <= tr.data; 
            CovPort.sample();
        end 
    endtask 
endclass

interface busifc;
    logic clk;
    logic [31:0] data;
    logic [2:0] port;
endinterface

program automatic test(busifc ifc);
    Transaction tr;
    Transactor tr_c;
    mailbox mbx_in;
    initial begin
        tr = new();
        mbx_in = new();
        tr_c = new(mbx_in,ifc);
        repeat (32) begin 
            assert(tr.randomize); 
            mbx_in.put(tr);
            tr_c.main();
            @ifc.clk;
        end
    end
endprogram

module top;
    busifc ifc();
    initial begin
        ifc.clk=0;
        forever #10 ifc.clk=~ifc.clk;
    end
    test test_u(ifc);
endmodule

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楚生辉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值