参考书目:《SystemVerilog Assertion应用指南》、RKV课程
1. Assertion
- 用来与设计功能和时序做比较的属性描述(设计的属性描述)
- 在模拟中被检查的属性不像期望的表现或被禁止的出现的属性在模拟过程中发生,那么这个断言失败
1.1 类型划分
-
立即断言(immediate assertion)
- 非时序
- 执行时同过程语句
- 可以在initial/always过程块或task/function中使用
always_comb begin a_ia: assert (a && b); end
-
并行断言(concurrent assertion)
- 时序性
- 关键词property
- 与设计模块并行执行
- 只会在时钟边沿激活,变量的值是采样到的值
a_cc: assert property (@(posedge clk) not (a && b);
Request-Grant协议描述:request拉高,在2个周期后,grant拉高,在1个周期后,request拉低,在1个周期后,grant拉低
property req_grant_prop
@(posedge clk) req ##2 gnt ##1 !req ## !gnt;
endproperty
assert property req_grant_prop else $error("Req-Gnt Protocol violoation");
sequence(序列)property(属性)assert(断言)
sequence name_of_sequence;
<test expression>;
endsequence
property name_of_property;
<test expression>; or
<complex sequence expressions>;
endproperty
assertion_name: assert property (property_name);
assertion_name:
assert property (property_name)
<success message>
else
<fail message>
1.2 property
直接包含一个property
assert property (@(posedge clk)
disable iff (!reset)
a | => b ##1 c);
独立声明property
assert property(my_prop);
property my_prop;
@(posedge clk)
disable iff (!reset)
a |=> b ##1 c;
endproperty
property可以直接包含sequence
此处的sequence用来描述时序,表示在一个或多个时钟周期内的时序描述
a |=>b
时钟上升沿a满足,则下一周期要满足b
sequence s1;
@(posedge clk) a ##1 b ##1 c;
endsequence
sequence s2;
@(posedge clk) a ##1 c;
endsequence
property p1;
@(posedge clk) disable iff (!reset)
s1 |=> s2;
endproperty
- sequence 可以在module、interface、program、clocking块和package块中声明;不可以在class中声明
- sequence可以提供参数
sequence s20_1(data, en);
(!fram && (data==data_bus)) ##1 (c_be[0:3] == en);
endsequence
1.3 蕴含(implication)操作符
|->
交叠交错符号
- 如果满足,则评估后续算子序列(同周期);不满足,则表现为空成功,不执行后续算子
property p_req_ack;
@(posedge clk) mem_en |-> (req ##2 ack);
endproperty
|=>
非交叠交错符号
- 如果条件满足,则在下一个周期评估其后续算子序列,不满足则表现为空成功
property p_req_ack;
@(posedge clk) mem_en |=> (req ##2 ack);
endproperty
2. Sequence
2.1 基本操作符
##
周期延迟符号
##n
表示在n个时钟周期后,##0
表示在当前周期
sequence a_b
@(posedge clk) a ##1 b;
endsequence
##[min:max]
在一个范围内的时钟周期延迟, 序列会在从min到max时间窗口中最早的时间来匹配
[*n]
表示连续重复
sequence a_b
@(posedge clk) a ##1 b[*2];
endsequence
[*m:n]
表示一定范围内的重复事件
[->n]
跟随重复,主要要求被检验重复的表达式的最后一个匹配应该发生在整个序列匹配结束之前
[=m]
重复发生m次,不需要在连续周期被发生,非连续重复
sequence a_b
@(posedge clk) a ##1 b[=3];
endsequence
//b[=3]表示必须在3个周期内为1,但不需要是连续的3个周期
[=m:n]
表示从最小m到最大n的重复发生的给连续周期次数
a[*0]
表示没有在任何正数时钟周期内有效
2.2 and操作符
and
表示两个序列需要保持匹配
SEQ1 and SQE2
- 同一起始点开始后,seq1和seq2均满足(同时刻开始)
- 满足时刻在稍晚序列的满足时刻
- 两个序列的满足时间可以不同
2.3 intersect操作符
intersect
需要两边的序列在同一时钟周期内匹配(同时刻开始,同时刻满足,两个序列的长度与必须相等)
2.4 or操作符
or
表示两个序列至少需要有一个满足
- 同时刻触发
- 最终满足seq1或满足seq2
- 结束时间以序列满足的最后一个序列时间为准
2.5 first_match操作符
first_match
从多次满足的序列中选择第一次满足时刻,放弃其他满足时刻
sequence ts1;
first_match (te1 ##[2:5] te2);
endsequence
2.6 throughout操作符
throughout
检查一个信号或表达式在贯穿一个序列时是否满足要求(保证某些条件在整个序列的验证过程中一直为真)
在burst模式信号拉低后的2个周期时,irdy/trdy也应该在连续7个周期内保持为低,同时burst模式信号也应该在这一连续周期内保持为低 (9拍拉低)
sequence burst_rule1;
@(posedge mclk)
$fell(burst_mode) ##0
(!burst_mode) throughout (##2 ((trdy==0)&&(irdy==0)) [*7]);
endsequence
2.7 within操作符
within
检查一个序列与另一个序列在部分周期长度上的重叠
- SEQ1 within SEQ2
- 当seq1满足在seq2的一部分连续时钟周期内成立,seq1 within seq2成立(起始点可以不同)
- seq1在seq2的开始带结束的范围内发生,且序列seq2的开始匹配点必须在seq1的开始匹配点之前发生,序列seq1的结束匹配点必须在seq2的结束匹配点之前结束。
trdy需要在irdy下拉的1个周期后保持7个周期为低,同时irdy也将保持8个周期为低,以下序列会在第11个时钟周期满足
!trdy[*7] within (($fell irdy) ##1 !irdy[*8])
2.8 if操作符
当master_req为高时,下一个周期,req1或req2应该为高,如果req1为高,则下一个周期ack1为高,如果req2为高,则下一个周期ack2为高
propery master_child_reqs;
@(posedge clk) master_req ##1 (req1 || req2)
if(req1)
(##1 ack1);
else
(##1 ack2);
endproperty
在cache访问时,如果有cache lookup满足,那么状态机的状态应该为READ_CACHE,否则应该为REQ_OUT
property cache_hit_check;
@(posedge clk) (state==CACHE_LOOKUP) ##1 (CHit || CMiss) |->
if(CHit)
##1 (state==CACHE_READ);
else
##1 (state==REQ_OUT);
endproperty
assert property(cache_hit_check) else $error;
2.9 ended 检测序列的终点
- SEQ.ended
- 使用序列的结束点作为同步点
- 在某一时刻,序列如果及时抵达终点,则条件满足
在inst为高的下一个周期,序列e1应该结束或已经结束
sequence e1;
@(posedge sysclk) $rose(ready) ##1 proc1 ##1 proc2;
endsequence
sequence rule;
@(posedge sysclk) reset ##1 inst ##1 e1.ended ##1 branch_back;
endsequence
在c拉起的下一周期,a拉低b拉高的序列应应该结束
sequence aRbseq (aFell, nRose)'
@(posedge clk) $fell(aFell) ##1
$rose(bRose);
endsequence
property endCycle;
@(posedge clk) $rose(c) |=>
aRbseq(a,b).ended;
endproperty
2.10 局部变量
- 可以在sequence或property中使用
- 动态创建
在cache rdDone拉高后,读出的rdData会在2个周期后,在其基础上加1,并作为wrData写入
sequence rd_cache_done;
##[1:5] rdDone;
endsequence
sequence check_reg_wr_data;
int local_data;
(rd_cache_done, local_data=cache_rd_data) ##2 (reg_wr_data == (local_data+1));
endsequence
如果read拉高,伴随readid,则下一次raed必须在这一次read对应的readAck返回后,才可以发起
需要先记录上一次read的readId,继而在接下来的周期,检查没有相同readId的read发起,直到readId的上一次read的readAck拉起,并且readAckId与之相同
property checkReadIdAck;
int loc_id;
($rose(read), loc_id=readId) |=>
not (($rose(read) && readId==loc_id) [*1:$]) ##0
($rose(readAck) && readAckID == loc_id);
endproperty
2.11 调用方法
在序列匹配时,可以调用task,void function和系统函数
可以在s1序列末尾,分别打印e和f变量被采样时的数值
sequence s1;
logic v, w;
(a, v=e) ##1
(b[->1], w=f, $display("b after a with v = %h, w = %h\n", v,w));
endsequence
2.12 访问采样方法
$rose()
与上一采样周期相比,变量最低位是否变为1
$fell()
与上一采样周期相比,变量最低位是否变为0
$stable()
在连续两个采样周期内,表达式的值保持不变
$past(expr[, num_cycles])
访问在过去若干采样周期前的数值
$past(signal_name,number of clock cycles,gating signal)只有当门控时钟为真时才检查后续算子情况
在ack拉高时的前两个周期,req信号应该为高
property ReqCauseAck;
@(posedge clk) $rose(ack) |-> $past(req, 2);
endproperty
2.13 系统函数和方法
$countbits(exp, control_bit)
计算exp中匹配control_bit数值的位数
$onehot(exp)
检查exp中是否有且只有1位为1
$onehot0(exp)
检查exp中是否有且只有1位为1或没有1
$isunknown(exp)
检查exp中是否有x或z
$countones(exp)
计算exp中为1的位数
disable iff
给assertion做局部的条件控制
$assertion
打开所有assertion
$assertoff
暂时停止assertion运行
$assertkill
终止所有执行的assertion
module assert_control();
initial begin: disable_assert_during_reset
@(negedge top_tb.reset_n)
$display("Disabling assertion during reset");
$assertoff(0, top_tb.cpu_inst1);
@(posedge top_tb.reset_n)
$display("enabling assertions after reset");
$assertion(0, top_tb.cpu_inst1);
end
endmodule
property p34;
@(posedge clk)
disable iff (reset)
$rose(start) |=> a[=2] ##1 b[=2] ##1 !start;
property
a34 : assert property(p34);
2.14 matched
用来监测第一个子序列的结束点
3. Property
- 结合sequence对时序和逻辑的描述,property可用来描述设计得确切行为
- Property可在验证中用来做assumption,checker或coverage
- assert关键词,用作checker来检查设计是否遵循property的描述
- assume关键词,作为环境的假设条件,对仿真环境和形式验证均起到对激励进行假设的作用
- cover关键词,将property是否真正通过作为断言覆盖率来衡量
- Property可以在module、interface、clocking或package中声明
七种property
1. sequence类型
当出现满足该sequence条件是,property才可以通过
2. negation类型 not
not property_expr
,如果property_expr不满足,那么negation类型的property即通过
property rule2;
@(clkev) disable iff (foo) a |-> not(b ##1 c ##1 d);
endproperty
3. disjunction类型 or
property_expr1 or propert_expr2
,当至少一个property_expr满足时, property即通过
property rule3;
@(posedge clk) a[*2] |-> ((##[1:3] c) or (d |=> e));
endproperty
4. conjunction类型 and
property_expr1 and property_expr2
,当两个property_expr均满足时,property通过
property rule4;
@(posedge clk) a[*2] |-> ((##[1:3] c) and (d |-> e));
endproperty
5. if-else 类型
6. implication蕴含
sequence_expr{|->, |=>} property_expr
7.instantiation
一个命名后的property在另一个property_expr中所使用
property rule6(x,y);
##1 |-> y;
endproperty
property rule5a;
@(posedge clk)
a ##1 (b||c) [->1] |->
if(b)
rule6(d,e)
else
f;
endproperty
时钟声明
- 对sequence或property,默认使用同一时钟对数据做采样,不排除多时钟的采样
- 如果一个sequence或property需要声明多个时钟用来做数据采样,可以使用##1结合第二个时钟沿采样
- @(posedge clk1) s1 ##1 @(posedge clk2) s2
- sequence,操作符and、or、intersect等无法被使用在多时钟sequence
- property中的and、or、not可以用在多时钟property声明中,因为代表逻辑运算不参与sequence之间的时序关系
如果clk0上升沿a为高,且下一个clk1和clk2的上升沿,b和c分为高时,则该property成立
property mclk_prop;
@(posedge clk1) b and @(posedge clk2) c;
endproperty
assert property (@(posedge clk0) a |=> mclk_prop) else $error();
- 在sequence独立指定时钟
sequence s2; @(posedge clk) a ##2 b; endsequence
property p2; not s2; endproperty
assert property (p2);
- 在property中独立指定时钟
property p3; @(posedge clk) not (a ##2 b); endproperty
assert property(p3);
- 在过程块中,继承过程块的时钟
always @(posedge clk) assert property (not (a ##2 b));
- 在过程块中,继承过程块的时钟
clocking master_clk @(posedge clk);
property p3; not (a ##2 b); endproperty
endclocking
assert property (master_clk.p3);
-
断言的时钟由以下条件的优先级逐级判定
- 显示声明断言时钟
- 继承断言所嵌入环境的时钟
- 继承默认的时钟
-
并行断言必须具备时钟
-
多时钟的断言,必须显式声明时钟,无法继承或者使用默认时钟
-
多时钟断言无法嵌套入由时钟驱动的过程块语句
always @(clk) assert property (mult_clock_prop); //illegal
initial @(clk) assert property (mult_clock_prop); //illegal -
多时钟断言,无法嵌套入时钟块
绑定
- 断言既可以嵌入到设计中,也可以在设计外部定义
- 嵌入到设计中,可能存在无法综合的问题,需要同时考虑添加编译定向(compiler directive)
- 在设计外部定义,不用担心综合问题
- bind方法可以满足在设计外部定义断言,而将其绑定到设计内部或接口上
bind design_block_or_instance_name block_with_assertions
- bind可以将包含断言的模块与设计模块或实例进行绑定,既可以满足对设计内部信号的可视性,又能够满足断言模块的独立性
- 使用绑定的优势在于,无法修改原有设计代码,也无需添加检测信号,即可以实现断言的添加
interface range(input clk, enable, input int minval, expr);
property crange_en;
@(posedge clk) enable |-> (minval <= expr);
endproperty
range_clk : assert property (crange_en);
endinterface
bind cr_unit range r1(c_clk, c_en, v_low, (in1&&in2));
expect
- expect是一种阻塞的property使用方式
- expect语法同assert一致,但他会等待property执行通过,才会执行后续的语句
- 可以使用wait语句的地方,即可以使用expect语句
仿真在200ms开始,如果在连续的3个周期,依次可以看到a=1,b=1,c=1,那么expect语句就会通过,否则会出现运行错误
initial begin
#200ms;
expect (@posedge clk) a ##1 b ##1 c) else $error("expect failed");
end
- 同assert语句的调用方式类似,可以使用在function和task中,也可以引用静态变量或动态变量
integer data;
...
task automatic wait_for(integer value, output bit success);
expect(@(posedge clk) ##[1:10] data == value) success = 1;
else success = 0;
endtask
initial begin
bit ok;
wait_for(23,ok);
end
断言覆盖率
<cover_name> : cover property(property_name)