有两项重要的新技术,几乎所有的验证工程师都用到他们。
(1)约束随机测试平台
(2)代码覆盖率工具
功能覆盖率用于衡量验证完整性,他的衡量标准要包含两项指定的信息:
(1)协议覆盖
(2)测试计划覆盖
断言是设计属性的描述。
·如果一个在模拟中被检查的属性(property)不像我们期望的那样表现,那么这个断言失败。
·如果一个被禁止在设计中出现的属性在模拟过程中发生,那么这个断言失败。
断言的评估和执行包括一下三个阶段:
预备(Preponed)在这个阶段,采样断言变量,而且信号或变量的状态不能改变。这样确保在时隙开始的时候采样到最稳定的值。
观察(Observed)在这个阶段,对所有的属性表达式求值。
相应(Reactive)在这个阶段,调度评估属性成功或失败的代码。
system verilog语言中定义了两种断言:并发断言和即时断言。
·基于时钟周期
·在时钟边缘根据调用的变量的采样值计算测试表达式。
·变量的采样在预备阶段完成,而表达式的计算在调度器的观察阶段完成。
·可以被放到过程块(function或task)、模块(module)、接口(interface),或者一个程序(program)的定义中。
·可以在静态(形式的)验证和动态验证(模拟)工具中使用。
·基于模拟事件的语义。
·测试表达式的求值就像在过程块中的其他verilog的表达式一样。他们的本质不是时序相关的,而且是立即被求值。
·必须放在过程块的定义中。
·只能用于动态模拟。
简单的即时断言的例子:
bus.cb.request <= 1;
repeat(2) @bus.cb;
a1: assert (bus.cb.grant== 2'b01);
在任何设计模型中,功能总是由多个逻辑事件的组合来表示的。这些事件可以是简单的同一个时钟边缘被求值的布尔表达式,或者是经过几个时钟周期的求值的事件。SVA用关键词“sequence”来表示这些事件。序列的基本语法是:
sequence name_of_sequence;
;
endsequence
许多序列可以逻辑或者有序地组合起来生成更复杂的序列。SVA提供了一个关键词“property”来表示这些复杂的有序行为。
属性(property)的基本语法是:
property name_of_property;
; or
;
endproperty
属性是在模拟过程中被验证的单元。它必须在模拟过程中被断言来发挥作用。SVA提供了关键词“assert”来检查属性。断言(assert)的基本语法是:
assertion_name: assert property (property_name);
序列s1检查信号a在每个时钟上升沿都为高电平。
sequence s1;
@(posedge clk) a;
endquence
$rose(bollean experssion or signal_name)
·当信号/表达式的最低位变成1时返回真。
$fell(bollean expression or signal_name)
·当信号/表达式的最低位变成0时返回真。
$stable(boolean expression or signal_name)
·当表达式不发生变化时返回真。
一个序列或者属性在模拟过程中本身并不能起什么作用。他们必须像下面的例子那样被断言才能发挥作用。
sequence s5;
@(posedge clk) a ##2 b;
endsequence
property p5;
s5;
endproperty
a5 : assert property(p5);
下面的代码显示了在属性p5a的定义中指定时钟。
sequence s5a;
a ##2 b;
endsequence
property p5a;
@(posedge clk) s5a;
endproperty
a5a : assert property(p5a);
通常情况下,在属性(property)的定义中指定时钟,并保持序列(sequence)独立于时钟是一种好的编码风格。这可以提高基本序列定义的可重用性。
在之前的所有的例子中,属性检查的都是正确的条件。属性也可以被禁止发生。当属性为真时,断言失败。
序列s6检查当信号“a”在给定的时钟上升沿为高电平,那么两个时钟周期以后,信号“b”不允许时高电平。关键词“not”用来表示属性永远不为真。
sequence s6;
@(posedge clk) a ##2 b;
endsequence
property p6;
not s6;
endproperty
a6:assert property (p6);
system verilog语言被定义成每当一个断言检查失败,模拟器在默认情况下都会打印出一条错误信息。模拟器不需要对成功的断言打印任何东西。读者同样也可以使用断言陈述中的“执行块”来打印自定义的成功或失败信息。执行块的基本语法如下所示。
assertion_name:
assert property(property_name)
;
else
;
蕴含等效于一个if-then结构。蕴含左边叫做“先行算子”(antecedent),右边叫做“后续算子”(consequent)。先行算子是约束条件。当先行算子成功时,后续算子才会被计算。如果先行算子不成功,那么整个属性就默认地被认为成功。这叫做“空成功”。蕴含结构只能被用在属性定义中,不能在序列中使用。
蕴含可以分为两类:交叠蕴含和非交叠蕴含。
交叠蕴含用符号“|->”表示。如果先行算子匹配,在同一个时钟周期计算后续算子表达式。
非交叠蕴含用符号“|=>”表示。如果先行算子匹配,那么在下一个时钟周期计算后续算子表达式。后续算子表达式的计算总是有一个时钟周期的延迟。
SVA允许使用时序窗口来匹配后续算子。时序窗口表达式左手边的值必须小于右手边的值。左手边的值可以是0。如果它是0,表示后续算子必须在先行算子成功的那个时钟边沿开始计算。
property p12;
@(posedge clk)(a&&b) |-> ## [1:3] c;
endproperty
a12 : assert property(p12);
上述代码表示如果表达式为真,那么在接下去的1~3个周期内,信号c至少在一个时钟周期为高。
p13的后续算子在先行算子成功的同一个时钟沿开始计算。
property p13;
@(posedge clk) (a&&b) |-> ##[0:2] c;
endproperty
a13:assert property(p13);
在时序窗口的窗口上限可以用符号“$”定义,这表明时序没有上限。这叫做“可能性”运算符。
属性p14在任何给定的时钟上升沿检查信号“a”是否为高。如果为高,那么信号“b”从下一个时钟周期往后最终将为高,而信号“c”在信号“b”为高的时钟周期开始往后最终将为高。
property p14;
@(posedge clk) a |-> ##[1:$] b ##[0:$] c;
endproperty
a14:assert property(p14);
到目前为止,定义的序列都只是用了简单的连接的机制。换句话说,就是将多个序列以序列的起始点作为同步点,来组合成时间上连续的检查。SVA还提供了另一种使用序列的结束点作为同步点的连接机制。这种机制通过给序列名字追加上关键词“ended”来表示。
sequence s15a;
@(posedge clk)a ##1 b;
endsequence
sequence s15b;
@(posedge clk)c ##1 d;
endsequence
porperty p15a;
s15a |=> s15b;
endproperty
porperty p15b;
s15a.ended |=> ##2 s15b.ended;
endproperty
a15a: assert property(p15a);
a15b: assert property(p15b);
断言a15a的第一个真正的成功发生在时钟周期2。当信号“a”被检测为高,检验在时钟周期2被激活。当信号“d”在时钟周期5检测为高时,检验完成。断言a15b的第一次真正成功出现在时钟周期3。在时钟周期3,当序列s15a成功,即信号“b”被检测为高时检验被激活。接着在时钟周期5当序列s15b成功,或者说信号“d”被检测为高时,检验完成。
使用参数的SVA检验器的例子
module generic_chk (input logic a,b,clk);
parameter delay = 1;
property p16;
@(posedge clk) a |-> ##delay b;
endproperty
a16:assert property(p16);
endmodule
module top(...);
logic clk,a,b,c,d;
.
.
generic_chk #(.delay(2)) i1 (a,b,clk);
generic_chk i2 (c,d,clk);
.
.
endmodule
SVA允许在序列和属性中使用逻辑运算符。属性p17检查如果信号“c”为高,那么信号“d”的值与信号“a”的相等。如果信号“c”不为高,那么信号“d”的值与信号“b”的相等
property p17;
@(posedge clk) c ? d == a:d == b;
endproperty
a17:assert property(p17);
使用true表达式,可以在时间上延长SVA检验器。这代表一种忽略的状态,它使得序列延长了一个时钟周期。这可以用来实现同时监视多个属性且需要同时成功的复杂协议。
`define true 1
sequence s18a;
@(posedge clk) a ##1 b;
endsequence
sequence s18a_ext;
@(posedge clk) a ##1 b##1 `true;
endsequence
sequenc s18b;
@(posedge clk) c ##1 d;
endsequence
property p18
@(posedge clk) s18a.ended |-> ##2 s18b.ended;
endproperty
property p18_ext
@(posedge clk) s18a_ext.ended |=> s18b.ended;
endproperty
a18 : assert property (p18);
a18_ext : assert property(p18_ext);
SVA提供了一个内嵌的系统任务“ p a s t ”,它可以得到信号在几个时钟周期之前的值。在默认情况下,他提供信号在前一个时钟周期的值。结构的基本语法如下。 ‘ past”,它可以得到信号在几个时钟周期之前的值。在默认情况下,他提供信号在前一个时钟周期的值。结构的基本语法如下。 ` past”,它可以得到信号在几个时钟周期之前的值。在默认情况下,他提供信号在前一个时钟周期的值。结构的基本语法如下。‘past(signal_name,number of clock cycles)`
这个任务能够有效地验证设计达到当前时钟周期的状态所采用的通路是正确的。
property p19;
@(posedge clk) (c && d) |-> ($past (a && b),2) == 1'b1);
endproperty
a19: assert property(p19);
属性p19检验的是在给定的时钟上升沿,如果表达式(c&&d)为真,那么两个周期前,表达式(a&&b)为真。
p a s t 构造可以由一个门控信号控制。使用门控信号的 past构造可以由一个门控信号控制。使用门控信号的 past构造可以由一个门控信号控制。使用门控信号的past构造基本语法如下:
$past(signal_name,number of clock cycles,gating signal)
property p20;
@(posedge clk)(c && d) |-> ($past ((a &&b),2,e) == 1'b1);
endproperty
a20:assert property(p20);
属性p20与属性p19相似。但是只有当门控信号“e”在任意给定时钟上升沿有效时检验才被激活。
如果信号“start”在任何给定的时钟上升沿跳变为高,接着从下一个时钟周期起,信号a保持三个连续时钟周期为高,然后下一个时钟周期,信号stop为高。
@(posedge clk) $rose(start) |-> ##1 a ##1 a ##1 a ##1 stop
如果信号a需要在很多个周期中保持高电平,编写这样一个检验器可能会非常冗长。信号a需要连续的或者间歇地重复自己三次。
SVA语言提供三种不同的重复运算符:连续重复,跟随重复,非连续重复。
连续重复——允许用户表明信号或者序列将在指定数量的时钟周期内都连续地匹配。信号的每次匹配之间都有一个时钟周期的隐藏延迟。连续重复运算符的基本语法如下所示。
signal or sequence [*n]
"n"是表达式应该匹配的次数。
比如,a[*3]可以被展开成下面的式子。
a ##1 a ##1 a
而序列(a ## b)[*3]可以展开为
(a##1b) ##1 (a ##1 b) ##1 (a ##1 b)
跟随重复——允许用户表明一个表达式将匹配达到指定的次数,而且不一定在连续的时钟周期上发生。这些匹配可以是间歇的。跟随重复的主要要求是被重复检验的表达式的最后一个匹配应该发生在整个序列匹配结束之前。跟随重复运算符的基本语法如下所示。
signal [->n]
参考下面的序列:
start ##1 a[->3] ##1 stop
这个序列需要信号“a”的匹配(即信号”a“的第三次,也就是最后一次重复的匹配)正好发生在”stop“成功之前。信号stop在序列的最后一个时钟周期匹配,而且在前一个时钟周期,信号a有一次匹配。
非连续重复——与跟随重复相似,除了它并不要求信号的最后一次重复匹配发生在整个序列匹配前的那个时钟周期。非连续重复运算符的基本语法如下所示。
signal [=n]
在跟随重复和非连续重复中只允许使用表达式,不能使用序列。
属性p23指定了一个重复序列的时序窗口。同样的,重复的次数也可以是一个窗口。比如a[*1:5]表示信号a从某个时钟周期开始重复1~5次。
重复窗口的边界规则与延迟窗口的相同。左边的值必须小于右边的值。右边的值可以是符号”$“,这表示没有重复次数的限制。
二进制运算符and可以用来逻辑地组合两个序列。当两个序列都成功时整个属性才成功。两个序列必须具有相同的起始点,但是可以有不同的结束点。检验的起始点是第一个序列成功时的起始点,而检验的结束点是使得属性最终成功的另一个序列成功时的点。
intersect运算符和and运算符很相似,它有一个额外的要求。两个序列必须在相同时刻开始且结束于同一时刻。两个序列的长度必须相等。
二进制运算符or可以用来逻辑的组合两个序列。只要其中一个序列成功,整个属性就成功。
任何时候使用了逻辑运算符(如and和or)的序列中指定了时间窗,就有可能出现同一个检验具有多个匹配的情况。first_match构造可以确保只用第一次序列匹配,而丢弃其他的匹配。当多个序列被组合在一起,其中只需时间窗内的第一匹配来检验属性剩余的部分时,first_match构造非常有用。
蕴含是目前讨论到的允许定义前提条件的一项技术。例如,要对一个指定的序列进行检验,必须某个前提条件为真。为了保证某些条件在整个序列的验证过程中一直为真,可以使用throughout运算符。运算符throughout的基本语法如下所示:
(expression) throughout (sequence definition)
within构造允许在一个序列中定义另一个序列。
seq1 within seq2
这表示seq1在seq2的开始到结束的范围内发生,且序列seq2的开始匹配点必须在seq1的开始匹配点之前发生,序列seq1的结束匹配点必须在seq2的结束匹配点之前结束。
SVA提供了几个内建的函数来检查一些最常用的设计条件。
$onehot——检验表达式满足one-hot,就是在任意给定的时钟沿,表达式只有一位为高。
$onehot0——检验表达式满足zero one-hot,就是在任意给定的时钟沿,表达式只有一位为高或者没有任何位为高。
$isunknown——检验表达式的任何为是否为X或者Z。
$countones——计算向量中为高的位的数量
在某些设计情况中,如果一些条件为真,则我们不想执行检验。换句话说,这就像是一个异步的复位,使得检验在当前时刻不工作。disable iff的基本语法如下:
disable iff(expression)
property p34;
@(posedge clk)
disable iff (reset)
$rose(start) |=> a [=2] ##1 b[=2] ##1 !start;
endproperty
a34:assert property(p34)
如果reset被检测为高,检验器会停止并默认地发出一个空成功的信号。
运算符intersect提供了一个定义可能性运算符可以使用的最小和最大时钟周期数的机制。
property p35;
@(posedge clk) 1[*2:5] intersect (a ##[1:$] b ##[1:$] c));
endproperty
a35:assert property(p35);
这个序列每当信号a为高时就开始,并且可能一直到整个模拟结束时才成功。这可以使用带1[*2-5]的intersect运算符加以约束。这个intersect的定义检查从序列的有效开始点,到序列成功的结束点,一共经过2-5个时钟周期。
可以用定义形参的方式来重用一些常用的属性。SVA允许使用属性的形参来定义时钟。这样,属性可以应用在使用不同时钟的相似设计模块中。同样的,时序延迟也可以参数化,这使得属性的定义更具有普遍性。
断言a36_1,a36_2和a36_3定义了每个主控接口用的检验器,分别使用了各个接口对应的信号作为属性arb的参数。
property arb(a,b,c,d);
@(posedge clk) ($fell(a) ##[2:5] $fell(b)) |-> ##1 ($fell(c) && $fell(d)) ##0 (!c&&!d) [*4] ##1 (c$$d) ##1 b;
endproperty
a36_1: assert property(arb(a1,b1,c1,d1));
a36_2: assert property(arb(a2,b2,c2,d2));
a36_3: assert property(arb(a3,b3,c3,d3));
SVA允许使用嵌套的蕴含。当我们有多个门限条件指向一个最终的后续算子时,这种构造十分有用。
属性p_nest检验如果信号a有一个下降沿,则是一个有效开始,接着在一个周期后,信号b,c,d应该为低电平有效信号以保持这个有效开始。如果第二个条件匹配,那么在6到10个周期内期望free为真。注意,当且仅当信号b,c和d都匹配时,在后续状况free才会被检验是否为真。
`define free (a&&b&&c&&d)
property p_nest;
@(posedge clk) $fell(a) |-> ##1 (!b&&!c &&!d) |-> ##[6:10] `free;
endproperty
a_nest:assert property(p_nest);
SVA允许在使用蕴含的属性的后续算子中使用if/else语句。
property p_if_else;
@(posedge clk) ($fell(start) ##1 (a||b)|->
if(a)
(c[->2] ##1 e)
else
(d[->2] ##1 f);
endproperty
a_if_else: assert property(p_if_else);
如果不用if/else构造来重写这个属性,需要用三个单独的属性来实现。
SVA允许序列或者属性使用多个时钟定义来采样独立的信号或者子序列。SVA会自动地同步不同信号或子序列使用的时钟域。
下面的代码显示了一个序列使用多个时钟的简单例子。
sequence s_multiple_clocks;
@(posedge clk1) a ##1 @(posedge clk2) b;
endsequence
任何时候如果一个序列定义了多个时钟,构造matched可以用来监测第一个子序列的结束点。序列s_a查找信号a的上升沿。信号a是根据时钟clk1来采样的。序列s_b查找信号b的上升沿。信号b则是根据时钟clk2来采样的。属性p_match验证在给定的时钟clk2的上升沿,如果序列s_a匹配,那么在一个周期后,序列s_b也必须为真。
sequence s_a;
@(posedge clk1) $rose(a);
endsequence
sequence s_b;
@(posedge clk2) $rose(b);
endquence
proprety p_match;
@(posedge clk2) s_a.matched |=> s_b;
endproperty
a_match: assert property(p_match);
SVA支持一种叫expect的构造,他与verilog中的等待构造相似,关键的区别在于expect语句等待的是属性的成功检验。expect构造后面的代码是作为一个阻塞的语句来执行。expect构造的语法与assert构造很相似。expect语句允许在一个属性成功或者失败后使用一个执行块。使用expect构造的实例如下所示。
initial begin
@(posedge clk);
#2ns cpu_ready = 1'b1;
expect(@(posedge clk) ##[1:16] memory_ready == 1'b1)
$display("Hand shake successful\n"):
else
begin
$display("Hand shake failed: exiting\n")
$finish();
end
for(i=0;i<64;i++)
begin
send_packet();
$display("PACKET %0d sent\n",i);
end
end
在序列或者属性的内部可以局部定义变量,而且可以对这种变量进行赋值。变量接着子序列放置,用逗号隔开。如果子序列匹配,那么变量赋值语句执行。每次序列被尝试匹配时,会产生变量的一个新的备份。
property p_local_var1;
int lvar1;
@(posedge clk)
($rose(enable1) ,lvarq = a) |->##4 (aa == (lvar1*lvar1*lvar1));
endproperty
a_local_var1: assert property(p_local_var1);
属性p_local_var查找信号enable的上升沿。如果找到,局部变量率lvar1保存设计中向量a的值。在四个周期后,检查设计的输出向量aa是否与局部变量的值的立方相等。属性的后续算子等待设计满足延迟(4个时钟周期),然后将设计的实际输出和属性局部计算的值比较。
SVA可以在序列每次成功匹配时调用子程序。同一序列中定义的局部变量可以作为参数传给这些子程序。对于序列的每次匹配,子程序调用的执行与他们在序列定义中的顺序相同。
sequence s_display1;
@(posedge clk)
($rose(a),$display("Signal a arrived at %t\n",$time));
endsequence
sequence s_display2;
@(posedge clk)
($rose(b),$display("Signal b arrived at %t\n",$time));
endsequence
property p_display_window;
@(posedge clk)
s_display1 |-> ##[2:5] s_display2;
endproperty
a_display_window: assert property(p_display_window);
有两种方法可以将SVA检验器连接到设计中。
(1)在模块定义中内建或者内联检验器。
(2)将检验器与模块、模块的实例或者一个模块的多个实例绑定。
注意,定义检验器模块时,他是一个独立的实体。检验器用来检验一组通用的信号,检验器可以与设计中任何的模块或者实例绑定,绑定的语法如下所示。
bind
;
断言可以用来获得有关协议覆盖的穷举信息。SVA提供了关键词cover来实现这一功能,cover语句的基本语法如下所示。
cover_name是用户提供的名称,用来标明覆盖语句,property_name是用户想获得覆盖信息的属性名。
cover语句的结果包含下面的信息:
(1)属性被尝试检验的次数。
(2)属性成功的次数。
(3)属性失败的次数。
(4)属性空成功的次数。
检验器mutex_chx在一次模拟中的覆盖日志的实例如下所示
c_mutex,12 attempts,12 match,0 vacuous match
就像断言(assert)语句一样,覆盖(cover)语句可以有执行块。在一个覆盖成功匹配时,可以调用一个函数(function)或者任务(task),或者更新一个局部变量。