SVA简介
SVA是systemverilog的一个子集,主要用来验证设计的行为,一条断言是嵌入的一条检查语句,可以在特定条件或事件序列的故障上生成警告或错误;断言一般跟在RTL代码后面,如下:
module ABC ();
rtl 代码
SVA断言
endmodule
//注意:不要将SVA写在enmodule外面
也可以将断言封装成一个module,然后通过 bind 的方式链接到不同的设计点中,格式如下:
bind rtl实例名 断言模块名 断言实例名(signal1,signal2...);
断言被用来:
(1)检查特定条件或事件序列的发生;
(2)提供功能覆盖(functional coverage),使用 cover 关键字 ;
存在两种断言:立即断言和并发断言;
(1)立即断言 Immediate Assertions--用的很少
立即断言在当前仿真时间检查条件,类似于if...else语句,但是断言带有控制.立即断言必须放在过程块定义中.
下面展示一个简单的断言:
assert (a && b )
断言的格式如下:
assert(condition)
$display(“Condition is True”);
else
$display(“Condition is False”);
assert(condition)
$display(“Condition is True”);
assert(condition)
$(“Condition is True”);
else
$fatal(“Condition is False”);
assert(condition)
else begin
…….
…….
$fatal(“Condition is False”);
end
assert(condition)
else
$warning(“Condition is False”);
//推荐使用的格式
label: assert(condition)
$(“Condition is True”);
else
$warning(“Condition is False”);
如果断言失败,并且没有指定任何其他else子句,则默认情况下,该工具将调用$error;
(2)并发断言 Concurrent Assertions--与时序相关,常用
并发断言可以认为是一个连续运行的模块,为整个仿真过程检查信号,所以需要在并发断言内指定一个采样时钟。
需要指出的是:
- 并发断言只有在时钟沿才会执行;
- 可以在过程块、module、interface和program块内定义并发断言;
看下面例子:
c_assert: assert property(@(posedge clk) not(a && b));
//下面为通用格式
断言名:assert property (判断条件);
SVA的层次结构
SVA中可以存在内建的单元,这些单元可以是以下的几种:
Boolean expressions 布尔表达式
布尔表达式是组成断言的最小单元,断言可以由多个逻辑事件组成,这些逻辑事件可以是简单的布尔表达式.在SVA中,信号或事件可以使用常用的操作符,如:&&, ||, !, ^,&等;
Sequence序列
sequence是布尔表达式更高一层的单元,一个sequence中可以包含若干个布尔表达式,同时在sequence中可以使用一些新的操作符,如 ## 、重复操作符、序列操作符等;sequence的定义格式如下:
sequence name_of_sequence(参数列表);
……
endsequence
Property 属性
property是比sequence更高一层的单元,也是构成断言最常用的模块,其中最重要的性质是可以在property中使用蕴含操作符(|-> |=>);property的定义格式如下:
property name_of_property(参数列表);
测试表达式或复杂的sequence;
endproperty
property就是SVA中需要用来判定的部分,用来模拟过程中被验证的单元,它必须在模拟过程中被断言来发挥作用,SVA提供了关键字 assert 来检查属性,assert的基本语法是:
assertion_name: assert property(property_name) else $display("SVA error");
//else语句只有在断言失败时才会触发,可以不写
所以综上可以描述出创建一个断言的步骤:
property与sequence的相同和不同之处:
- 任何在sequence中的表达式都可以放到property中;
- 任何在property中的表达式也可以搬到sequence中,但是只有在property中才能使用蕴含操作符;
- property中可以例化其他property和sequence,sequence中也可以调用其他的sequence,但是不能例化property;
- property需要用cover /assert/assume 等关键字进行实例化,而sequence直接调用即可;
序列sequence
在任何设计中,功能总是由多个逻辑事件的组合来表示,这些事件可以是简单的同一个时钟沿被求值得布尔表达式,也可以是经过几个时钟周期的求值事件,SVA用关键字 sequence来表示这些事件,sequence可以让断言易读,复用性高;
sequence具有如下特性:
- 可带参数;
- 可以在 property 中调用;
- 可以使用局部变量;
- 可以定义时钟周期;
- 可以使用序列方法,如 ended、matched、triggered,这些序列方法只能在sequence中使用;
看个简单的例子:
module asertion_ex;
bit clk,a;
always #5 clk = ~clk; //clock generation
//generating 'a'
initial begin
a=1;
#15 a=0;
#10 a=1;
#10 a=0;
#10 a=1;
#10;
$finish;
end
//assertion sequence SVAsequence
sequence seq_1;
@(posedge clk) a==1;
endsequence
//calling assert property
a_1: assert property(seq_1);//SVA 也就是说在断言的property中调用sequence
//wave dump
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
endmodule
结果:
带逻辑关系的sequence
sequence seq_2;
@(posedge clk) a || b;//如果a和b都为0,则断言失败
endsequence
带参数的sequence
sequence在定义时可以带形参,这样可以让sequence复用;看下面的例子:
sequence seq_lib (a, b)
a || b ;
endsequence
//可以在一个sequence中调用另一个sequence
sequence s_lib_inst
seq_lib(req1,req2);//在当前sequence中调用 seq_lib
endsequence
带时序关系的sequence
在SVA中时钟延时用符号"##"来表示,如“##2”表示延时两个时钟周期;看例子:
sequence seq;
@(posedge clk) a ##2 b; //在时钟上升沿到来是检查a是否为1,若不为1则断言失败
//若a为1,则检查两个周期之后b是否为1,若不为1,断言失败
endsequence
请注意,sequence只有在时钟上升沿到来后检查a是否为1才会继续检查后面的b;
也可以在property中调用sequence:
sequence seq;
a ##2 b;
endsequence
property p;
@(posedge clk) seq;
endproperty
a_1 : assert property(p);
请注意在断言中调用一个property,并且再定义时钟是非法的,如下是非法的:
a_3: assert property(@(posedge clk) p) ; //不允许
Forbidding a property 禁止一个property
sequence seq;
@(posedge clk) a ##2 b;
endsequence
property p;
not seq;//not表示对sequence取反,即sequence成功时,property失败
endproperty
a_1: assert property(p);
Binary Operators 双目操作符
- and : 断定两个事件同时发生
- intersect :断定两个事件同时发生和结束
- or :断定两个事件至少有一个发生
- if....else
sequence s1;
$past(!req,1) ##1 ($rose(gnt) and $past(!gnt,1));
endsequence
sequence s2;
$past(!req,1) ##1 $rose(gnt) ##1 $fell(gnt);
endsequence
sequence s3;
req ##1 gnt ##1 busy ##1 (!req and !gnt and !busy);
endsequence
sequence s4;
req ##[1:3] gnt;
endsequence
property and_prop;
@ (posedge clk)
req |-> s1 and s2;
endproperty
property intersect_prop;
@ (posedge clk)
req |-> s1 intersect s3;
endproperty
property or_prop;
@ (posedge clk)
req |-> s1 or s4;
endproperty
property ifelse_prop;
@ (posedge clk)
if (check)//如果check=1,判断delay1,否则判断delay2
delay1
else
delay2;
endproperty
Match Operators 匹配操作符
- a within b :断定b事件发生的时间段里包含a事件发生的时间段
- c throughout a :断定在a事件成立的过程中,c事件“一直”成立
- firstmatch(a b c) :只要判断a成立就不需要再判断后面的b、c了
property first_match_prop;
@ (posedge clk)
$rose(burst_enable) |=>
first_match(burst_enable ##[0:4] !master_busy);
endproperty
property throughout_prop;
@ (posedge clk)
$rose(burst_enable) |->
(burst_enable) throughout
( ##2 (!slave_busy && !master_busy) [*6]);
endproperty
property within_prop;
@ (posedge clk)
$rose(burst_enable) |=>
(!slave_busy[*6]) within
(($fell(master_busy)) ##1 !master_busy[*7]);
endproperty
Clocks in Sequence
sequence的时钟有两种,一种是直接在sequence内部定义时钟,另一种是使用外部property的时钟,如下:
// Sequence Layer
// Here clock is specified inside the sequence
//=================================================
sequence req_gnt_seq;
@ (posedge clk)
(~req & gnt) ##1 (~req & ~gnt);
endsequence
//=================================================
// Property Specification Layer
// Still requires clock for the property
//=================================================
property req_gnt_prop;
@ (posedge clk)
disable iff (reset)
req |=> req_gnt_seq;
endproperty
//=================================================
// Assertion Directive Layer
//=================================================
req_gnt_assert : assert property (req_gnt_prop)
else
$display("@%0dns Assertion Failed", $time);
Sequence Arguments sequence中的参数
在定义sequence时允许带参数,参数可以定义数据类型也可以不定义,带参数的好处是允许向sequence传递参数,从而可以使sequecne达到类似函数的效果.如下:
logic clk = 0;
logic req,gnt;
logic a,b;
//=================================================
// Sequence Layer with args (NO TYPE)
//=================================================
sequence notype_seq (X, Y);
(~X & Y) ##1 (~X & ~Y);
endsequence
//=================================================
// Sequence Layer with args (NO TYPE)
//=================================================
sequence withtype_seq (logic X, logic Y);
(~X & Y) ##1 (~X & ~Y);
endsequence
//=================================================
// Property Specification Layer
//=================================================
property req_gnt_notype_prop(M,N);
@ (posedge clk)
req |=> notype_seq(M,N);
endproperty
property a_b_type_prop(logic M, logic N;
@ (posedge clk)
a |=> withtype_seq(M,N);
endproperty
//=================================================
// Assertion Directive Layer
//=================================================
req_gnt_notype_assert : assert property (req_gnt_notype_prop(req,gnt));
a_b_type_assert : assert property (a_b_type_prop(a,b));
Local Variables
sequence当中允许定义本地变量,如下:
sequence local_var_seq;
logic [7:0] lport;
(in_en, lport = portin) ##[1:5]
(out_en and lport == portout);
endsequence
//=================================================
// Property Specification Layer
//=================================================
property local_var_prop;
@ (posedge clk)
in_en |-> local_var_seq;
endproperty
//=================================================
// Assertion Directive Layer
//=================================================
local_var_assert : assert property (local_var_prop);
此外,在sequence中还可以调用方法.
Recursive Property
如果在property中调用自身,则这种property称为递归property.如下:
property recursive_prop(M);
M and (1'b1 |=> recursive_prop(M));
endproperty
//=================================================
// Assertion Directive Layer
//=================================================
recursive_assert : assert property (recursive_prop(req));
Implication operator 关联操作
首先看下面的例子:
sequence seq;
@(posedge clk) a ##2 b;
endsequence
在每一个时钟的上升沿来临时sequence都会启动,检查a是否为1,若不为1,则检查失败;如果我们想sequence只在a为1的情况下被检查,这就需要用到关联操作.
关联(implication)的意思等价于if...then结构,关联左边的先行词,右边的为后继;只有先行词判断为真时,后继才会被检测。
implication结构只能用在property定义中,不能用于sequence;
存在两种implication:
- Overlapped implication
- Non-overlapped implication
(1)overlapped implication
overlapped implication采用"|->"操作符,只有先行词判断为真后,后继才会在同一个时钟沿被检测.例子:
property p;
@(posedge clk) a |-> b;//在上升沿到来时,若a=1,则在同一个时钟周期继续检查b是否为1,若为1,则判断为真
endproperty
a: assert property(p);
(2)Non-overlapped implication
Non-overlapped implication采用"|=>"操作符,只有先行词判断为真后,后继才会在下一个时钟沿被检测.例子:
property p;
@(posedge clk) a |=> b;//当时钟沿到来时检测a是否为1,若为1,则在下一个时钟沿继续检测b
endproperty
a: assert property(p);
Implication with a fixed delay on the consequent 在implication的后继上加上延时
property p;
@(posedge clk) a |-> ##2 b;//如果在当前时钟沿检查到a=1,则再经过两个时钟沿后检测b是否为1
endproperty
a: assert property(p);
Implication with a sequence as an antecedent 使用sequence作为implication的先行词和后继
sequence seq_1;
(a && b) ##1 c;
endsequence
sequence seq_2;
##2 !d;
endsequence
property p;
@(posedge clk) seq_1 |-> seq_2;//在当前时钟沿若seq_1为真,则继续检测seq_2(2个时钟沿后,检测d=0)
endpeoperty
a: assert property(p);
Timing windows in SVA Checkers SVA带时间窗口
property p;
@(posedge clk) a |-> ##[1:4] b;//在当前时钟沿检查到a=1后,检测接下来的1~4个时钟周期内b=1
endproperty
a: assert property(p);
property p;
@(posedge clk) a |-> ##[0:4] b;//在当前时钟沿检查到a=1后,检测接下来的0~4个时钟周期内b=1,也就是在当前时钟沿b也必须为1
endproperty
a: assert property(p);
property p;
@(posedge clk) a |-> ##[1:$] b;//在当前时钟沿检查到a=1后,检测下一个时钟周期到仿真时间结束b=1
endproperty
a: assert property(p);
Repetition Operators 重复操作
property p;
@(posedge clk) a |-> ##1 b ##1 b ##1 b;//在当前时钟沿若a=1,则在接下来的3个时钟b=1
endproperty
a: assert property(p);
上面的重复操作写的比较繁琐,SVA提供了Repetition Operators重复操作符 来代替上面的写法,格式如下:
signal [*n]
或
sequence [*n]
所以上面的语句可以重写为:
property p;
@(posedge clk) a |-> ##1 b[*3];
endproperty
a: assert property(p);
go-to repetition 非连续周期的重复
格式如下:
signal [->n] //重复n次,且不必是连续时钟周期
property p;
@(posedge clk) a |-> ##1 b[->3] ##1 c;//在当前时钟沿若a=1,则在接下来的时钟沿b=1必须满足3个时钟周期,这3个周期不一定连续
//检测到3个b=1后,下一个时钟沿c=1
endproperty
a: assert property(p);
Non-consecutive repetition 非连续周期重复
它与上面go-to类似,唯一的区别就是它不要求信号重复的最后一次匹配发生在整个序列匹配结束之前的时钟周期中,格式如下:
signal [=n]
[=n]和[->n]的区别:
a ##1 b[->1:3] ##1 c // E.g. a !b b b !b !b b c
//也就是说最后一次必须匹配
a ##1 b [=1:3] ##1 c // E.g. a !b b b !b !b b !b !b c
//在跳变到c之前的最后一次不一定要匹配
注意sequence不能使用go-to和 Non-consecutive 重复结构,只有表达式可以使用!
SVA built in methods
$rose
格式:
$rose(boolean expression or signalname);//如果在时钟有效沿,布尔表达式或signal跳变为true或1,则返回true,否则返回false
//也就是该方法是检测信号的上升沿跳变的
sequence seq_rose;
@(posedge clk) $rose(a);//如果在时钟有效沿a跳变为1,则断言成功
endsequence
$fell
格式:
$fell( boolean expression or signalname);//如果在时钟有效沿,布尔表达式或signal跳变为false或0,则返回true,否则返回false
//也就是说该方法是检测信号的下降沿跳变的
$fell类似于取反操作,和$rose正好相反.
sequence seq_fell;
@(posedge clk) $fell(a);//如果在时钟有效沿a=0,则断言成功
endsequence
$stable
格式:
$stable(boolean expression or signalname) ;//在时钟沿到来时,如果信号没有发生跳变,则返回1,否则返回0;
//该方法是用来检测信号是否稳定的
sequence seq_stable;
@(posedge clk) $stable(a);//在时钟沿到来时,若a不发生跳变,则断言成功
endsequence
$past
格式:
$past(signal_name, number of clock cycles);//将前number个周期时的值赋给信号signal_name
property p;
@(posedge clk) b |-> ($past(a,2) == 1);//当时钟沿到来时,检测b=1,若为1,则将前2个周期时的a值赋给a
//若前2个周期时的a=1,则断言成功
endproperty
a: assert property(p);
$past construct with clock gating 带时钟门控
格式:
$past (signal_name, number of clock cycles, gating signal);//如果gating signal为真
//则将前number个周期时的signal_name的值赋给signal_name
Property p;
@(posedge clk) b |-> ($past(a,2,c) == 1);//在时钟沿到来时,若b=1且c=1
//则将前2个周期时的a值赋给a,若a=1,则断言成功
endproperty
a: assert property(p);
Built-in system functions 内建的函数
$onehot(expression)
//检查表达式的结果是否为one-hot,即只有一个bit为1,其余为0,若是则返回1
- checks that only one bit of the expression can be high on any given clock edge.
$onehot0(expression)
//检查表达式的结果是否为one-hot或者全部bit为0,若是则返回1
- checks only one bit of the expression can be high or none of the bits can be high on any given clock edge.
$isunknown(expresslon)
//检查表达式的结果的bit位中是否存在X或Z,若存在则返回1
- checks if any bit of the expression is X or Z.
$countones(expression)
//返回表达式结果的bit位中为1的位数
- counts the number of bits that are high in a vector.
a_1: assert property( @(posedge clk) $onehot(state) );//断言state为one-hot码
a_2: assert property( @(posedge clk) $onehot0(state) );//断言state为全零码或one-hot码
a_3: assert property( @(posedge clk) $isunknown(bus) ) ;//断言bus中存在X或Z等不定值
a_4: assert property( @(posedge clk) $countones(bus)> 1 );//断言bus中为1的bit数大于1
disable iff and ended construct
disable iff
在某些设计条件下,如果某些条件为真,我们不希望继续进行检查。这可以通过使用disable iff实现。
property p;
@(posedge clk)
disable iff (reset) a |-> ##1 b[->3] ##1 c;
endproperty
a: assert property(p);
//在正常的情况下当时钟沿到来后若a=1,则在接下来的3个周期b=1,且在下1个周期,c=1,则断言为真;
//但是当时钟沿到来后,如果reset=1,则不再执行后面的检查
ended
在级联sequence的时候,sequence的结束点可以用作同步点,这通过将关键字"ended"附加到sequence名称后来实现.
sequence seq_1;
(a && b) ##1 c;
endsequence
sequence seq_2;
d ##[4:6] e;
endsequence
property p;
@(posedge clk) seq_1.ended |-> ##2 seq_2.ended;//当时钟沿到来后,检查seq_1,两个时钟后再检查seq_2,最后将两个sequence的结束点做同步
endpeoperty
a: assert property(p);
Variable delay in SVA SVA中的可变延时
Static Delay
a ##2 b;//在时钟沿到来后a=1,且在延时2个周期后b=1
Variable Delay
int v_delay;
v_delay = 4;
a ##v_delay b;//这样写会报错,因为在断言中不能使用变量!
module asertion_variable_delay;
bit clk,a,b;
int cfg_delay;
always #5 clk = ~clk; //clock generation
//generating 'a'
initial begin
cfg_delay = 4;
a=1; b = 0;
#15 a=0; b = 1;
#10 a=1;
#10 a=0; b = 1;
#10 a=1; b = 0;
#10;
$finish;
end
//calling assert property
a_1: assert property(@(posedge clk) a ##cfg_delay b);//利用变量作为延时周期数会报错
endmodule
即用非常量表达式作为property、sequence、assertion的延时数或复制范围是非法的!
下面的例子是合法的:
module asertion_variable_delay;
bit clk,a,b;
int cfg_delay;
always #5 clk = ~clk; //clock generation
//generating 'a'
initial begin
cfg_delay = 4;
a=1; b = 0;
#15 a=0; b = 1;
#10 a=1;
#10 a=0; b = 1;
#10 a=1; b = 0;
#10;
$finish;
end
//delay sequence
sequence delay_seq(v_delay);
int delay;
(1,delay=v_delay) ##0 first_match((1,delay=delay-1) [*0:$] ##0 delay <=0);
endsequence
//calling assert property
a_1: assert property(@(posedge clk) a |-> delay_seq(cfg_delay) |-> b);
endmodule
多时钟 Multi Clock
sva允许序列或属性使用多个时钟来采样独立的信号,sva会自动同步不同信号或者子序列使用的时钟域,如下面的例子:
property multi;
@(posedge clk1) a ##1 @(posedge clk2) b;
endproperty
当信号 a 在clk1 的上升沿为1时,属性开始匹配,接着 ##1 会将检验时间转移到 clk2 最近的一个上升沿去检查 b 是否为1;
注意:当在一个序列或属性中使用了多个时钟信号时,只允许使用 ##1 的延时构造;上面的例子换成 ##2或者 ##0 都是不允许的,因为使用 ##0 会产生混淆,即在 a 匹配后究竟哪个时钟信号才是最近的时钟,这将引起竞争,而如果换成 ##2 ,则无法同步到 clk2 最近的上升沿;
上面的 ##1 可以用非交叠蕴含来代替,也就是说:
@(posedge clk1) a |=> @(posedge clk2) b;
这两个操作是等效的,需要注意的是上面的 |=> 不能用 交叠蕴含 |-> 来代替,因为 |->同样会引起竞争;
matched 构造
如果一个序列定义了多个时钟, matched 可以用来监测第一个子序列的结束点,如下所示:
sequence s_a;
@(posedge clk1) $rose(a);
endsequence
sequence s_b;
@(posedge clk1) $rose(b);
endsequence
property p_match;
@(posedge clk2) s_a.matched |=> s_b;
endproperty
注意:matched 方法只有 sequence 才能调用!!