SVA中的基本元素sequence和蕴含操作
1 序列块sequence
序列是断言最基本的组成结构之一,主要描述一些逻辑行为关系,但是在实际的操作过程中该结构也可以省略,当然其存在就有其合理之处,其中一点就是从复用性的角度来说,将可重用的逻辑结构定义于sequence中,然后在property中通过时钟采样触发sequence,这样可以使sequence不依赖于特定的时钟而存在于不同的时钟设计中。当然这并不是说sequence中不能使用时钟等采样事件,只是一般在构建断言时其时钟都是继承于使用调用其的property或者assert的,这个在后续的部分将进行详细的说明。实际中是为了复用性不在其中增加特定时钟而已。另外还需要说明的是property中可以包含sequence,但是sequence中不能包含property,sequence中可以包含其他的sequence。
`timescale 1 ns / 1 ps module top_tb; logic clk; logic sig1,sig2,sig3; initial begin clk = 1'b0; forever #1 clk = ~clk; end initial begin sig1 = 1'b0;sig2 = 1'b0;sig3 = 1'b0; #2 sig1 = 1'b1; #2 sig1 = 1'b0;sig2 = 1'b1; #4 sig3 = 1'b1; #4 $stop; end // sequence sequence sp; sig2 ##2 sig3; endsequence // sp // property property pr; @(posedge clk) sig1 |=> sp; endproperty // pr // no sequence in property property pr1; @(posedge clk) sig1 |=> sig2 ##2 sig3; endproperty // pr1 // sequence triggered in property a1 : assert property(pr) $display("@%0t | a1 : PASSED!",$time); else $display("@%0t | a1 : FAILED!",$time); // sequence triggered in assert-property a2 : assert property(@(posedge clk) sig1 |=>sp) $display("@%0t | a2 : PASSED!",$time); else $display("@%0t | a2 : FAILED!",$time); a3 : assert property(pr1) $display("@%0t | a3 : PASSED!",$time); else $display("@%0t | a3 : FAILED!",$time); endmodule // top_tb【仿真结果】
示例中,sequence和property本身在设计中并不会进行检查,而是需要使用assert property断言他们才会被激活进行相应的检查。sequence不仅可以通过property被assert property触发,也可以直接跳过property直接被assert property触发,但是需要注意的是如果直接在assert property中使用sequence,那么需要在使用的时候指定对应的采样事件(clocking_event),否则将会compile error。如果在property调用sequence时指定了clocking_event,那么在assert property时完全可以不同再次指定clocking_event。可见断言一个序列并不一定要独立的定义一个property,但是如果跳过property直接将sequence加入到assert property中,会造成代码的冗繁不易理解,可重用性和可维护性都很差。所以,出于代码的简单易懂、高可重用性和可维护性建议采用sequence-property-assert property三步式定义和使用。
sequence除了可以被property调用,还可以被其他的sequence调用,其使用格式比较灵活方便。
【示例】
`timescale 1 ns / 1 ps module top_tb; logic clk; logic sig0,sig1,sig2,sig3; initial begin clk = 1'b0; forever #1 clk = ~clk; end initial begin sig0 = 1'b0;sig1 = 1'b0; sig2 = 1'b0;sig3 = 1'b0; #2 sig0 = 1'b1; #2 sig1 = 1'b1;sig0 = 1'b0; #2 sig2 = 1'b1;sig1 = 1'b0; #2 sig3 = 1'b1;sig2 = 1'b0; #4 $stop; end // base sequence sequence s0; sig2 ##1 sig3; endsequence // s0 // upper sequence sequence s1; sig1 ##1 s0; endsequence // s1 // property property pr; @(posedge clk) sig0 |-> ##1 s1; endproperty // pr ap : assert property(pr) $display("@%0t | ap : PASSED!",$time); else $display("@%0t | ap : FAILED!",$time); endmodule // top_tb【仿真结果】
2 蕴含操作
蕴含操作在并行断言中可以说是无处不在,基本上所有的并行断言中都会用到蕴含操作,那么为什么要使用蕴含操作呢?首先先看下面的这个示例。
【示例】
`timescale 1 ns / 1 ps module top_tb; logic clk; logic sig0,sig1; initial begin clk = 1'b0; forever #1 clk = ~clk; end initial begin sig0 = 1'b0;sig1 = 1'b0; #2 sig0 = 1'b1;#2 sig0 = 1'b0; #2 sig1 = 1'b1;#2 sig1 = 1'b0; #4 $stop; end property p; @(posedge clk) sig0 ##2 sig1; endproperty // p a : assert property(p) $display("@%0t | a : PASSED!",$time); else $display("@%0t | a : FAILED!",$time); endmodule // top_tb【仿真结果】
示例中属性在每个时钟的上升沿都是待检测序列的有效开始,即会在每个时钟周期的上升沿检测sig0是否为高。如果sig0在某个时钟周期的上升沿被采样到不为高,就会产生错误信息,这些信息有时我们并不关心。如果sig0在一段时间内一直保持低,那么将会产生大量的我们不关心的信息,而我们关心的是序列起始点有效后,其后续的序列是否满足要求。示例中,我们关心的是在sig0为高后的第二个时钟周期sig1也为高的情况,如果此时sig1不为高我们才认为序列不匹配。可见在每个采样周期对sig0进行判断后输出信息是不需要的,为了避免冗余的信息,SVA中引入了蕴含操作,当起始序列不满足时将不会输出冗余的不关心的信息,即蕴含操作可以有效的控制时序建模的起点。
【示例】
`timescale 1 ns / 1 ps module top_tb; logic clk; logic sig1,sig2,sig3; initial begin clk = 1'b0; forever #1 clk = ~clk; end initial begin sig1 = 1'b0;sig2 = 1'b0;sig3 = 1'b0; #2 sig1 = 1'b1;sig3 = 1'b1; #2 sig1 = 1'b0;sig2 = 1'b1;sig3 = 1'b0; #2 sig2 = 1'b0; #4 $stop; end // sig1在时钟上升沿时为高,在当前时钟上升沿的下一个时钟上升沿时sig2也必须为高 property p1; @(posedge clk) sig1 |=> sig2; endproperty // p1 // sig1在时钟上升沿时为高,在当前时钟上升沿sig2也必须为高 property p2; @(posedge clk) sig1 |-> sig3; endproperty // p2 a1 : assert property(p1) $display("@%0t | a1 : PASSED!",$time); else $display("@%0t | a1 : FAILED!",$time); a2 : assert property(p2) $display("@%0t | a2 : PASSED!",$time); else $display("@%0t | a2 : FAILED!",$time); endmodule // top_tb【仿真结果】
示例中的仿真可以看出,此时的检测虽然也是在每一个时钟上升沿去检测判断sig1的情况,但是检查结果仅在“|=>”和“|->”前的表达式为真的情况下才会输出,不会出现前例中的每个采样时钟上升沿都输出信息的现象。在这个示例中,我们注意到在属性property中使用了一些特殊的语法结构,其具体含义如下:
示例 | 说明 |
property sp1; sig1 |=> sig2; endproperty // sp1 property sp2; sig3 |-> sig4; endproperty // sp2 property sp3; sig5 ##n sig6; endproperty // sp3
property pr1; clocking_event sp1 |=> sp2 |-> sp3; endproperty // pr1
assert property(pr1); | |=>和|->是蕴含操作符,分别表示非交叠蕴含(Non-Overlapped Implication)和交叠蕴含(Overlapped Implication),位于|=>和|->左侧的称为“先行算子(Antecendent)”,右侧的称为“后续算子(Consequent)”,先行算子为约束条件,当先行算子匹配成功时,后续算子才会被计算,如果此时后续算子也匹配成功了,那么该断言“真”成功,如果此时后续算子计算失败,那么该断言失败。这里需要注意的是如果先行算子匹配失败,那么整个属性并不会认为失败,会被认为“成功”,这个成功为“假”成功,叫做空成功(Vacuous Success)。同时需要注意蕴含操作只能使用在property或者assert property中,不能在sequence中使用。 |
##n,表示延迟n个采样周期 | |
不管是sequence还是property都需要assert property才会被激活进行check,否则他们本身在设计中是不会执行的。 | |
这里的sig1、sig2、sig3、sig4等可以为各种复杂的序列表达式,从而可以实现复杂的序列功能。 |
这里需要非常注意的是,“|=>和|->”不能用于sequence中,作为蕴含操作符的|=>和|->语法结构完全一样,都可以用if-else结构替换,但是从示例中可以看到两者的断言成功时间不一样,那么这个差别怎么表现出来呢?请看下例。
【示例】
`timescale 1 ns / 1 ps module top_tb; logic clk; logic sig1,sig2,sig3; initial begin clk = 1'b0; forever #1 clk = ~clk; end initial begin sig1 = 1'b0;sig2 = 1'b0;sig3 = 1'b0; #2 sig1 = 1'b1;sig2 = 1'b1; #2 sig1 = 1'b0;sig3 = 1'b1; #4 $stop; end // |=> property pr0; @(posedge clk) sig1 |=> sig3; endproperty // pr0 // |-> property pr1; @(posedge clk) sig1 |-> sig2; endproperty // pr1 // |-> ##1 property pr2; @(posedge clk) sig1 |-> ##1 sig3; endproperty // pr2 a1 : assert property(pr0) $display("@%0t | p0 : PASSED!",$time); else $display("@%0t | p0 : FAILED!",$time); a2 : assert property(pr1) $display("@%0t | p1 : PASSED!",$time); else $display("@%0t | p1 : FAILED!",$time); a3 : assert property(pr2) $display("@%0t | p2 : PASSED!",$time); else $display("@%0t | p2 : FAILED!",$time); endmodule // top_tb【仿真结果】
a1和a3的检查结果一样,这是因为“|=>”和“|-> ##1”等效,两者都是在sig1(先行算子)匹配后,在下一个时钟周期sig3(后续算子)匹配,断言成功。a2中,sig1和sig2在同一个时钟周期计算,如果此时sig1和sig2同时满足要求,则断言成功。由a1、a2和a3的仿真结果可以看出,“|=>”描述的是先行算子比后续算子提前一个时钟周期,“|->”描述的是先行算子和后续算子在同一个时钟进行计算判断匹配情况。这里有个小建议:在编写断言时尽量使用“|->”而不是“|=>”,因为你可以通过“|->”后增加时钟数实现“|=>”的功能,但是不能通过“|=>”实现“|->”的功能。下面两图示意了“|=>”和“|->”的判断机理。
s1 |->s2
s1 |=> s2