1.1 芯片验证概述

测试平台:对DUT创建测试序列、观察DUT的输入输出、对DUT的输出数据与预期数据进行对比、报告检查结果。
芯片开发流程:用户需求->设计结构和产品描述->系统设计->模块功能详述->硬件设计->硬件描述语言文件->功能验证->验证环境文件->后端综合->芯片产品。
只有经过充分量化验证才能有足够的信心去流片。

1.2 SystemVerilog概述

SystemVerilog是Verilog标准的扩展,旨在通过一种统一的语言来帮助工程师对大型复杂硬件系统进行建模,并且对其功能进行验证。

1.3 数据类型

SV将硬件信号分为“类型”和“数据类型”。
类型:变量(variables、可以使用连续赋值或者过程赋值),线网类型(wire、只能使用连续赋值语句assign)
数据类型:四值逻辑(logic)、二值逻辑(bit)。
四值逻辑类型:logic、integer、reg、wire
二值逻辑类型:bit、byte、shortint、int、longint
无符号类型:logic、bit构成的向量(vector)、reg、wire
有符号类型(加上unsigned变为无符号类型):integer、byte、shortint、int、longint

1.4 自定义类型

通过typedef创建用户自定义类型
通过enum创建枚举类型
通过struct创建结构体类型

1.5 字符串类型

SV引入string类型用来容纳可变长度的字符串,存储单元为byte,长度为N时,索引值从0到N-1,结尾没有null字符。
str.len():返回字符串的长度
str.putc(i,c):将第i个字符替换成字符c,等同于str[i]=c
str.getc(i):返回第i个字符
str.substr(i,j):将从第i个字符到第j个字符的字符串返回
str.{atoi(),atohex(),atooct,atobin}:将字符串转变为十进制、十六进制、八进制、二进制数据

1.6设计特性

添加接口(interface)从而将通信和协议检查进一步封装
添加always_comb(组合逻辑)、always_latch(锁存逻辑)、always_ff(时序逻辑)等过程语句块
添加priority(可重复、按优先级选择)和unique(不可重复) case语句

1.7 接口

SV在Verilog语言基础上扩展了接口(interface),提供一种新型的对抽象级建模的方式,可以简化建模和验证大型复杂设计
Interface允许多个信号被整合到一起来表示一个单一的抽象端口,多个模块可以使用同一个interface避免多个分散的端口信号连接
接口可以包含变量或者线网,封装模块之间的通信协议,还可以嵌入与协议有关的断言检查、功能覆盖率收集等
接口不同于模块的地方在于接口不允许包含设计层次,即接口不能例化module,但是接口可以例化接口
接口可以进一步声明modport来约束不同模块连接时信号方向

2.1 验证环境结构

测试平台是整个验证系统的总称,包括各个组件、组件之间的连接关系、测试平台的配置和控制,还包括编译仿真的流程、结果分析报告和覆盖率检查等。
各个组件之间相互独立、验证组件与设计之间需要连接、验证组件之间也需要通信、验证环境也需要时钟和复位信号的驱动。

2.2 验证环境组件

Stimulator的主要职责是模拟与DUT相邻设计的接口协议,只需要关注于如何模拟接口信号,使其能够以真实的接口协议来发送激励给DUT,不应该违反协议,但不拘束于真实的硬件行为,比真实硬件行为更丰富的激励,接口主要同DUT之间连接,可以有其它配置接口,也可以有存储接口数据生成历史的功能。
Monitor的主要功能用来观察DUT的边界信号或者内部信号(尽量少),并且结果打包整理传送给其他验证平台的组件(Checker比较器)。
Checker负责模拟设计行为和功能检查的任务,将DUT输入接口数据汇集给内置的reference model(参考模型),通过数据比较检查DUT功能。

2.3 任务和函数

函数function:
不会消耗仿真时间,无法调用task,可以调用function,可以返回一个单一数据或者不返回,可以作为一个表达式中的操作数
任务task:
会消耗仿真时间,可以调用function和task,不会返回数值
参数传递:input、output、inout属于值传递,只发生在方法的调用和返回时;ref属于指针传递;添加const修饰符变为只读变量。可以有默认参数。

2.4 数组

非组合型数组:int a[7:0][1023:0]; 8\*1024
组合型数组:int [3:0][7:0] a; 4\*8
混合型数组,先看右边,再看左边。如:int[1:0][2:0] arr[3:0][4:0];的维度是4*5*2*3。
初始化:

  • 组合型:logic [3:0][7:0] a = 32’h0;同向量初始化一致;
  • 非组合型:int d[0:1][0:3] = ‘{‘{7,3,0,5},’{2,0,1,6}};需要通过’{}来对数组的每一个维度进行赋值。

赋值:

  • 非组合型:byte a[0:3][0:3]; a[3] = ‘{‘hF, ‘hA ,’hC, ‘hE};a[1][0] = 8’h5;
  • 组合型:logic[1:0][1:0][7:0] a;a=32'hF1A3C5E7;a[1][1][0]=1'b0;a[1][0][3:0]=4'hF;

拷贝:

  • 组合型数组:当位数、维度不一致时自动扩展
  • 非组合型数组:维度、大小必须严格一致
  • 组合型与非组合型数组无法直接相互赋值

动态数组:一开始为空,使用new[]来为其分配空间,有delete、size等内置函数

int d[];
d=new[5];
d=new[20](d);\\分配20个空间并且前五个空间进行拷贝
d=new[100];\\分配100个空间,之前20个空间丢失、
d.delete();

队列:使用[$]关键字,int q[$] = {3,4};,有insert、push_back、push_front、pop_buck、pop_front等内置函数
关联数组:索引值可以为任意类型,不连续的值等
定位方法:返回值为队列,有min、max、unique等,也可以用foreach

2.5 类的封装

类(class):包含成员变量和成员方法。
对象(object):类在例化后的实例。
句柄(handle):指向对象的指针。
原型(prototype):程序的声明部分,包含程序名、返回类型和参数列表
成员默认都是public,有私有的local(只有类里面才能访问),受保护的protected(只有类以及子类里面才能访问)

2.6 类的继承

使用关键字 extends 继承父类的所有成员(变量和方法)。

class packet;integer i = 1;function new(int val);i = val + 1;endfunctionfunction shift();i << 1;endfunction
endclassclass linkedpacked extends packet;// integer i = 3;function new(int val);super.new(val);//子类必须要继承父类的new函数if(val >= 2)i = val;endfunctionfunction shift();// super.shift();i << 2;endfunction
endclassmodule tb;initial beginpacket p = new(3);linkedpacked lp = new(1);//packet tmp;//tem = p;$display("p.i = %0d", p.i);//4$display("lp.i = %0d", lp.i);//2,会先进入父类的new在调用自己的newp.shift();$display("after shift, p.i = %0d", p.i);//8lp.shift();$display("after shift, lp.i = %0d", lp.i);//8,不会进入父类的shift//$display("tem.i = %0d", tem.i);end
endmodule
  • 子类在创建的时候必须先执行父类的new(),也就是子类在new的时候一定会调用父类的new。
  • 如果子类中声明了与父类同名的成员(变量和方法),那么子类中对其同名成员的访问都将指向子类,而父类的成员将被隐藏。
  • super用来访问当前对象其父类的成员(变量和方法)。如果子类成员与父类成员同名,需要使用super来指定访问父类的成员。

2.7 包的使用

Package里面只能有软件(类里面的一些类、变量、方法、结构体、枚举)的部分,module属于硬件的部分。Interface也不能在package中,interface介于软件和硬件之间。

通过域的索引号::直接引用,通过通配符*将包中的所有类导入到指定容器中。

package pkg_a;class packet_a;int pkg_a_a;endclasstypedef struct{int data;int command;} struct_a;int va = 1;
endpackagepackage pkg_b;class packet_b;int pkg_b_b;endclasstypedef struct{int data;int command;} struct_b;int vb = 2;
endpackagemodule mod_a;
endmodulemodule mod_b;
endmodulemodule tb;class packet_tb;endclasstypedef struct{int data;int command;} struct_tb;class packet_a;int tb_a;endclassclass packet_b;int tb_b;endclassmod_a ma();//可以识别mod_b mb();//添加import可以省去作用域的麻烦//import pkg_a::packet_a;//import pkg_a::va;import pkg_a::*;import pkg_b::*;initial begin//packet_a pta = new();//module里面没有packet_a时,没有import出错,无法识别packet_a和packet_b//packet_b ptb = new();//pkg_a::packet_a pta = new();//module里面没有packet_a时,没有import不会出错//pkg_b::packet_b ptb = new();//packet_a pta = new();//module里面没有packet_a时,有import不会出错//packet_b ptb = new();packet_a pta = new();//module里面有packet_a时先找到自己的,没有的时候再去找packet的packet_b ptb = new();packet_tb mptb = new(); //$display("pkg_a::va = %0d , pkg_b::vb = %0d", pkg_a::va, pkg_b::vb);$display("pkg_a::va = %0d , pkg_b::vb = %0d", va, vb);end
endmodule

3.1 随机约束

用系统函数std::randomize()可以产生随机数。

$urandom(),可以产生一个32位的无符号随机数。

$urandom_range(maxval, minval=0),可以产生maxval和minval之间的随机数。

**在面向DUT的随机激励发生过程中,为了符合协议、满足测试需求,我们需要添加一些“约束”。**使变量随着希望的变化方向去随机。用类来作为“载体”来容纳这些变量以及它们之间的约束,可将类的成员变量声明为“随机”属性,用randrandc来表示。

任何类中的整型变量都可以声明为随机的,定长数组、动态数组、关联数组和队列都可以声明为rand/randc,可以对动态数组和队列的长度加以约束。指向对象的句柄也可以声明为rand(不可以为randc),随机时该句柄指向对象中的随机变量也会一并被随机。

  • rand在随机化的时候每次的取值的概率是一样的
  • randc在随机化的时候会在取值范围内先将所有取值遍历一遍后再开始下一轮,即如果取值范围为0-9,若第一次取2,则接下来的9次取值不会取到2。c 的含义是 cycle。
rand bit [7:0] len;
rand integer data[1];
constraint db { data.size == len; }
class packet;rand bit [31:0] src, dst, data[4];rand bit [7:0] kind;constraint cstr {src > 10;src < 15;}function print();$display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", src, dst, data, kind);endfunction
endclassmodule tb;packet p;initial beginp = new();$display("before randomize");p.print();p.randomize();$display("after randomize");p.print();end
endmoduletypedef struct{rand bit [31:0] src;rand bit [31:0] dst;rand bit [31:0] data[4];rand bit [7:0] kind;
} packet_t;module tb2;packet_t pkt;initial begin$display("before randomize");$display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", pkt.src, pkt.dst, pkt.data, pkt.kind);//添加临时约束 in-line/dynamic/temporary constraintstd::randomize(pkt) with { pkt.src > 10; pkt.src < 15; };$display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", pkt.src, pkt.dst, pkt.data, pkt.kind);end
endmodulemodule tb3;bit [31:0] src;bit [31:0] dst;bit [31:0] data[4];bit [7:0] kind;initial begin$display("before randomize");$display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", src, dst, data, kind);std::randomize(src, dst, data, kind);$display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", src, dst, data, kind);end
endmoduleclass packet2;rand packet_t pkt;//结构体也可以随机function print();$display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", pkt.src, pkt.dst, pkt.data, pkt.kind);endfunction
endclassmodule tb4;packet2 p;initial beginp = new();$display("before randomize");p.print();p.randomize();$display("after randomize");p.print();end
endmodule

约束块一般用类来实现,可以是一些随机的取值也可以为变量之间的关系。

rand integer x,y,z;
constraint c1 {x inside {3, 5, [9:15], [24:32], [y:2*y], z};}rand integer a,b,c;
constraint c2 {a inside {b, c};}integer fives[4] = '{5, 10, 15, 20 };
rand integer v;
constraint c3 {v inside {fives};}

也可以加上权重发布:①:=操作符,表示每一个值的权重是相同的;②\=操作符,表示权重会平均分配到每个值。

x dist {[100:102] := 1, 200 := 2, 300 := 5}//100,101,102,200,500分别为1-1-1-2-5x dist {[100:102] :/ 1, 200 := 2, 300 := 5}//100,101,102,200,500分别为1/3-1/3-1/3-2-5

unique可以用来约束一组变量,在随机化之后变量之间的取值不会一样。

//b,a[2],a[3],excluded在随机化后将包含不同的取值
constraint u {unique {b, a[2:3], excluded};}

条件约束,可以使用if-else或者->操作符来表示条件约束

mode == little -> len < 10;
mode == big -> len > 100;bit [3:0] a,b;
constraint c {(a == 0) -> (b == 1);}if(mode == little)len < 10;
else if(mode == big)len > 100;

迭代约束,foreach可以用来迭代约束数组(定长、动态、关联数组,队列)中的元素

class c;rand byte A[];constraint c1 {foreach (A[i]) A[i] inside {2,4,8,16}; }constraint c2 {foreach (A[j]) A[j] > 2 * j; }
end class
module tb5;class packet;rand byte arr[];constraint cstr {foreach(arr[i]) arr[i] inside {2, 4, 8, 16};foreach(arr[i]) arr[i] > 2 * i;arr.size() < 8;}endclassinitial beginpacket p = new();repeat(10) beginif(p.randomize()) $display("arr size is %d ,data is %p", p.arr.size(), p.arr);else $error("randomize failure!");      endend
endmodule

约束里面也可以使用函数

软约束(soft),可以用来指定变量的默认值和权重,若在外部做二次约束时,硬约束会覆盖掉软约束,并且不会导致随机数产生失败。

module tb6;class packet_a;rand int length;constraint cstr {soft length inside {[5:15]};}endclassclass packet_b extends packet_a;//constraint cstr{ length inside {[10:20]};}//与父类同名会覆盖掉packet_a的约束//constraint cstr1 { length inside {[10:20]};}//不同名同时满足packet_a的约束//constraint cstr2 { length inside {[16:20]};}//满足硬约束constraint cstr2 {soft length inside {[16:25]};}//满足就近约束endclassinitial beginpacket_b pkt = new();repeat(10) beginif(pkt.randomize() with {soft length inside {[26:30]};}) $display("pkt.length is %d", pkt.length);else $error("randomize failure!");endend
endmodule

模糊指向,使用local::this来指定约束的指向。

module tb7;class packet1;rand bit[7:0] x;constraint cstr {soft x == 5;}endclassclass packet2;bit[7:0] x = 10;function int get_rand_x(input bit[7:0] x = 20);packet1 pkt = new();//pkt.randomize with {x == x;};//默认为5//pkt.randomize with {x == local::x;};//30,为传进来的值pkt.randomize() with {x == local::this.x;};//10,当前类中的值return pkt.x;endfunctionendclassinitial beginpacket2 pkt = new();$display("pkt.x = %0d", pkt.get_rand_x(30));end
endmodule

随机控制,使用rand_mode()来禁止随机化,使随机变量不参与到随机化中。p.rand_mode(0)禁止类中所有的随机变量参与到随机化中,p.value.rand_mode(1)将类中的指定变量解除禁止参与到随机化中。

约束控制,使用constraint()来禁止约束使用,用法同随机控制。

3.2 线程控制

fork join相当于begin end,此外还有fork join_any和fork join_none。

  • join:所有子线程执行结束再继续执行
  • join_any:任何一个线程结束就继续执行
  • join_none:不会等待子线程就直接继续执行
`timescale 1ns/1ps
module tb;task automatic exec(int id, int t);$display("@%t exec[%0d] entered", $time, id);#(t*1ns);$display("@%t exec[%0d] exited", $time, id);endtask//initial 之间并行initial beginexec(1, 10);endinitial beginexec(2, 20);endinitial beginexec(3, 30);endinitial beginexec(4, 40);end
endmodulemodule tb1;task automatic exec(int id, int t);$display("@%t exec[%0d] entered", $time, id);#(t*1ns);$display("@%t exec[%0d] exited", $time, id);endtask//initial 内部串行initial beginexec(1, 10);exec(2, 20);exec(3, 30);exec(4, 40);end
endmodulemodule tb2;task automatic exec(int id, int t);$display("@%t exec[%0d] entered", $time, id);#(t*1ns);$display("@%t exec[%0d] exited", $time, id);endtaskinitial begin//使用 fork join 并行forkexec(1, 10);exec(2, 20);exec(3, 30);exec(4, 40);joinend
endmodulemodule tb3;task automatic exec(int id, int t);$display("@%t exec[%0d] entered", $time, id);#(t*1ns);$display("@%t exec[%0d] exited", $time, id);endtaskinitial begin//使用 fork join_any/join_none 并行$display("@%t fork join_any entered", $time);forkexec(1, 10);exec(2, 20);join_any$display("@%t fork join_any exited", $time);$display("@%t fork join_none entered", $time);forkexec(3, 30);exec(4, 40);join_none$display("@%t fork join_none exited", $time);wait fork;//等待fork结束再继续执行disable fork;//直接结束fork继续执行$display("@%t ini_proc1 exited", $time);//无wait 10000,有wait 50000,有disable 10000end
endmodule

时序控制使用延迟控制或者事件等待来完成时序控制。

  • #使用延迟控制:#10 x = y ;
  • 事件(event)控制使用@来完成:@r x = y; @(posedge clock) x = y;
  • wait语句可以与事件或者表达式结合:

3.3 线程间同步与通信

3.3.1 事件(event)

触发事件用->。

`timescale 1ns/1ps
module tb;event e1, e2, e3;task automatic wait_event(event e, string name);$display("@%t start waiting event %s", $time, name);@e;//等待事件@$display("@%t finist waiting event %s", $time, name);endtaskinitial beginforkwait_event(e1, "e1");wait_event(e2, "e2");wait_event(e3, "e3");joinendinitial beginforkbegin #10ns -> e1; end//触发事件 ->begin #20ns -> e2; endbegin #30ns -> e3; endjoinendendmodule

等待信号的时候要用ref

module tb2;bit e1, e2, e3;task automatic wait_event(ref bit e, input string name);$display("@%t start waiting event %s", $time, name);@e;$display("@%t finist waiting event %s", $time, name);endtaskinitial beginforkwait_event(e1, "e1");wait_event(e2, "e2");wait_event(e3, "e3");joinendinitial begin$display("before fork, e1 = %0d", e1);forkbegin #10ns e1 = !e1; endbegin #20ns e2 = !e2; endbegin #30ns e3 = !e3; endjoin$display("after fork, e1 = %0d", e1);endendmodule

等待事件

  • @:边沿触发
  • wait():电平触发
  • wait_order(a,b,c):按顺序触发,事件从左到右依次完成

3.3.2 旗语(semaphore)

  • 旗语在创建的时候会为其分配固定的钥匙数量
  • 使用旗语的进程必须要先获得钥匙才可以继续执行
  • 旗语钥匙的数量可以有多个,等待旗语钥匙的进程也可以同时有多个
  • 常用于互斥,对共享资源的访问控制

创建旗语:semaphore sm; sm = new(N=0),new括号里面的数字就是旗语钥匙的数量。

从旗语获取一个或多个钥匙(阻塞型):get(N=1),将钥匙归还:put(N=1)

尝试获取一个或多个钥匙而不会被阻塞(非阻塞型):try_get(N=1),可以拿到正常执行,没有拿到返回0。

旗语的等待遵循先进先出(FIFO)。

`timescale 1ns/1ns
module tb;semaphore mem_acc_key;int unsigned mem[int unsigned];task automatic write(int unsigned addr, int unsigned data);mem_acc_key.get();#1nsmem[addr] = data;mem_acc_key.put(); endtasktask automatic read(int unsigned addr, output int unsigned data);mem_acc_key.get();#1nsif(mem.exists(addr))data = mem[addr];elsedata = 'x;mem_acc_key.put(); endtaskinitial beginint unsigned data = 100;mem_acc_key = new(1);forever beginforkbegin#10ns;write('h10, data+100);$display("@%t write with data %d",$time,data);endbegin#10ns;read('h10, data);$display("@%t read with data %d",$time,data);endjoinendendendmodule

3.3.3 信箱(mailbox)

信箱相当于FIFO来使用。当信箱写满时,后续写入的动作被挂起,直到信箱有空间才可以继续写入。

  • 创建信箱:new(int count = 0),可以限定大小,如果不传入参数,默认为不限定大小
  • 将信息写入信箱:put()
  • 试着写入信箱但不会阻塞:try_put()
  • 获取信息:get()同时会取出数据,peek()不会取出数据
  • 试着取出数据但不会阻塞:try_get()\try_peek()
  • 获取信箱信息的数目:num()
module tb;mailbox #(int) mb;//可以指定信箱中的数据类型是intinitial beginint data;mb = new(8);forever begincase ($urandom() % 2)0 : beginif(mb.num() < 8) begindata = $urandom_range(0, 10);mb.put(data);$display("mb put data %0d", data);  end        end1 : beginif(mb.num() > 0) beginmb.get(data);$display("mb get data %0d", data);end          endendcaseendend
endmodule

3.4 虚方法(多态)

在父类和子类中声明的虚方法,其方法名、参数名、参数方向等都应该保持一致。
在调用虚方法时,它将调用句柄指向对象的方法,而不受句柄类型的影响。

在父类方法前添加关键字virtual,子类可以不声明virtual。

module tb;class BasePacket;int A = 1;int B =2 ;function void printA;$display ( "BasePacket: :A is %d",A);endfunction : printAvirtual function void printB;$display ( "BasePacket: :B is %d",B);endfunction : printBendclass : BasePacketclass My_Packet extends BasePacket;int A = 3;int B = 4 ;function void printA;$display ( "My_Packet: :A is %d", A);endfunction : printAvirtual function void printB;$display("My_Packet: :B is %d",B);endfunction : printBendclass : My_PacketBasePacket P1 = new;My_Packet P2 = new;initial beginP1.printA; // displays 'BasePacket: :A is 1'P1.printB; // displays 'BasePacket: :B is 2'P1 = P2; // P1 has a handle to a My_packet objectP1.printA; // displays 'BasePacket: :A is 1'P1.printB;// displays 'My _Packet::B is 4 ' latest derived methodP2.printA; // displays 'My_Packet: :A is 3'P2.printB; // displays 'My_Packet: :B is 4'end
endmodule

3.5 类型转换

静态转换:Verilog对整数和实数类型,或者不同位宽的向量之间进行隐式转换。转换时指定目标类型,并在要转换的表达式前加上单引号即可。

int i;
real r;
i = int '(10.0 - 0.1);
r = real '(42);

动态转换:将子类的句柄赋值给父类的句柄。但是将父类的句柄赋值给子类句柄的时候会报错,可以使用$cast(sub_handle, base_handle)将父类句柄转换为子类句柄,只要该父类句柄指向的是一个子类的对象。

function int $cast( singular dest _var, singular source_exp ) ;
//或者
task $cast( singular dest_var, singular source_exp );
module tb;class BasePacket;endclass : BasePacketclass My_Packet extends BasePacket;endclass : My_PacketBasePacket P1 = new;My_Packet P2 = new;My_Packet P3 = new;initial beginP1 = P2; // 子类赋值给父类,父类P1指向子类对象P2$cast(P3, P1); // 父类赋值给子类,子类P3指向父类对象P1,前提父类P1指向相同的子类类型My_Packetend
endmodule

4.1 覆盖率概述

伴随着复杂SoC系统的验证难度系数成倍增加,无论是定向测试还是随机测试,我们在验证的过程中终究需要回答两个问题:

  • 是否所有设计的功能在验证计划中都已经验证?

  • 代码中的某些部分是否从未执行过?

覆盖率就是用来帮助我们在仿真中回答以上问题的指标。

一旦通过覆盖率来量化验证,我们可以在更复杂的情况下捕捉一些功能特性是否被覆盖:

  • 当我们在测试X特性的时候,Y特性是否也在同一时刻被使能和测试?

  • 是否可以精简我们已有的测试来加速仿真,并且取得同样的覆盖率?

  • 覆盖率在达到一定的数值的时候,是否停滞,不再继续上升?

简单而言,覆盖率就是用来衡量验证精度和完备性的数据指标。覆盖率可以告诉我们在仿真时设计的哪些结构被触发,当然,它也可以告诉我们设计在仿真时的哪些结构从未被触发过

只有满足以下三个条件,才可以在仿真中实现高质量的验证:

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

4.2 覆盖率种类

  • 没有任何一种单一的覆盖率可以完备地去衡量验证过程

  • 及时我们可以达到100%的代码覆盖率(隐性覆盖率),但这并不意味着100%的功能覆盖率(显性覆盖率)。原因在于代码覆盖率并不是用来衡量设计内部的功能运转,或者模块之间的互动,或者功能时序的触发等。

  • 类似地,我们即便达到了100%功能覆盖率,也可能只达到了90%的代码覆盖率。原因可能在于我们疏漏了去测试某些功能,或者一些实现的功能并没有被描述。

  • 从上述关于代码覆盖率和功能覆盖率简单的论述就可以证明,如果想要得到全面的验证精度,我们就需要多个覆盖率种类的指标。

  • 如果将上述两个分类的方式(隐性/显性、功能描述/设计实现)进行组合,那么可以将代码覆盖率、断言覆盖率以及功能覆盖率分别置入到不同的象限。

  • 但是需要注意,目前有一个象限仍然处于研究阶段,没有隐性的可以从功能描述生成某种覆盖率的方法,这也是为什么功能覆盖率依然需要人为定义的原因

4.3 代码覆盖率