SystemVerilog Assertions应用指南 第一章

1.1什么是断言

断言是设计的属性的描述。

● 如果一个在模拟中被检查的属性( property)不像我们期望的那样表现,那么这个断言失败。

● 如果一个被禁止在设计中出现的属性在模拟过程中发生,那么这个断言失败。

一系列的属性可以从设计的功能描述中推知,并且被转换成断言。这些断言能在功能的模拟中不断地被监视。使用形式验证技术,相同的断言能被重用来验证设计。断言,又被称为监视器或者检验器,已经被用作一种调试技术的方式,在设计验证流程中使用了很长时间。传统上,它们由过程语言,比如 Verilog,来实现。它们也能用PLI和C/C++的程序来实现。下面的代码显示了相互断言条件检查的 Verilog实现,其中信号a和信号b不能同时为高电平。如果这种情况发生,则显示这是一个错误信息。

复制代码
`ifdef ma 
	if(a & b )
	$display("Error ;Mutually asserted check failed .\n");
`endif 

这种监视器仅作为模拟的一部分而存在,因此只有当需要时才被纳入设计环境中。这可以通过允许 Verilog代码条件编译的指令"'`ifdef"来实现。

1.2为什么使用 System Verilog断言(SVA)

虽然Verilog可以很容易地用来实现一些检查,它仍有一些不足之处

(1) Verilog是一种过程语言,因此并不能很好地控制时序。

(2) Verilog是一种冗长的语言,随着断言的数量增加,维护代码将变得很困难。

(3)语言的过程性这一本质使得测试同一时间段内发生的并行事件相当困难。在一些情况下,一个 Verilog的检验器甚至可能无法捕捉到所有被触发的事件。

(4) Verilog语言没有提供内嵌的机制来提供功能覆盖的数据。用户必须自己实现这部分代码。

SVA是一种描述性语言,可以完美地描述时序相关的状况。语言的描述性本质提供了对时间卓越的控制。语言本身非常精确且易于维护。SVA也提供了若干个内嵌函数来测试特定的设计情况,并且提供了一些构造来自动收集功能覆盖数据。例子1.1显示了分别用Ⅴerilog和SVA实现的检验器。这个检验器验证当信号a在当前时钟周期为高电平时,下面1~3个时钟周期内,信号b应该变为高电平。

例子1.1分别用 Verilog和SVA实现的断言实例

用Verilog实现的断言示例

复制代码
//simple verilog checker

always @(posedge a)
	begin
		repeat (1) @(posedge clk );
			fork :a_to_b
				begin
					@(posedge b)
					$display("SUCESS :b arrived in time \n",$time);
					disable a_to_b;
					
				end
				begin
					repeat (3) @(posedge clk);
						$display("ERROR :b did not arrive in time \n",$time);
						disable a_to_b;
				end
				join
	end

用Systemverilog实现的断言示例

复制代码
//SVA checker

a_to_b_chk:
	assert property
	@(posedge clk) $rose (a)|->##[1:3] $rose(b);

例子1.1很清楚地显示了SVA的优势。本章将讨论SVA语法。检验器代表着一种非常简单的协议。使用SVA实现只需要一行代码,而相同的协议描述用 Verilog则需要好几行。此外,失败和成功的条件必须在 Verilog里额外地被定义,而SVA中断言失败会自动显示错误信息。

1.3 System Verilog的调度

System Verilog语言被定义成一种基于事件的执行模式。在每时隙( time slot),许多事件按照安排的顺序发生。这个事件的列表依照标准定义的算法执行。依照这个算法,模拟器可以防止任何在设计和测试平台互动中的不一致。断言的评估和执行包括以下三个阶段

预备( Preponed) 在这个阶段,采样断言变量,而且信号(net或变量( variable)的状态不能改变。这样确保在时隙开始的时候样到最稳定的值。

观察( Observed) 在这个阶段,对所有的属性表达式求值。

响应( Reactive) 在这个阶段,调度评估属性成功或失败的代码。图1-2显示了一个简化了的 System Verilog事件进程安排流程表。要彻底地理解 System Verilog的进程安排算法,请参考<<System Verilog 3.1a LRM>>[1]。

1.4SVA术语

System Verilog语言中定义了两种断言:并发断言和即时断言。

14.1并发断言

(1)基于时钟周期。

(2)在时钟边缘根据调用的变量的样值计算测试表达式。

(3)变量的采样在预备阶段完成,而表达式的计算在调度器的观察阶段完成。

(4)可以被放到过程块( procedural block)、模块( module)、接口interface),或者一个程序( (program)的定义中

(5)可以在静态(形式的)验证和动态验证(模拟)工具中使用。

一个并发断言的例子如下:

复制代码
a_cc : assert property ((@posedge clk ) not (a&&b));

图1-3显示了并发断言acc的结果。所有的成功显示成向上的箭头,所有的失败显示成向下的箭头。这个例子的核心内容是属性在每一个时钟的上升沿都被检验,不论信号a和信号b是否有值的变化。

1.4.2即时断言

(1)基于模拟事件的语义。

(2)测试表达式的求值就像在过程块中的其他 verilog的表达式一样。

(3)它们本质不是时序相关的,而且立即被求值。

(4)必须放在过程块的定义中。

(5)只能用于动态模拟。

一个即时断言的例子如下:

复制代码
always_com
	begin
		a_ia:assert(a && b);
		
	end

即时断言a_ia被写成一个过程块的一部分,它遵循和信号a、b相同的事件调度。当信号a或者信号b发生变化时, always块被执行。区别即时断言和并发断言的关键词是" property"。图1-4显示了即时断言a_ia的结果:

1.5建立SVA块

在任何设计模型中,功能总是由多个逻辑事件的组合来表示的。这些事件可以是简单的同一个时钟边缘被求值的布尔表达式,或者是经过几个时钟周期的求值的事件SA用关键词" sequence"来表示这些事件。序列( sequence)的基本语法是:

复制代码
sequence name_of_sequence;
    <test expression>
endsequence

许多序列可以逻辑或者有序地组合起来生成更复杂的序列。SVA提供了一个关键词" property"来表示这些复杂的有序行为。属性 (roperty)的基本语法是:

复制代码
property name_of_property;
	<test expression >; or 
	<complex sequence expression>
endproperty

属性是在模拟过程中被验证的单元。它必须在模拟过程中被断言来发挥作用。SVA提供了关键词" assert"来检查属性。断言( assert)的基本语法是:

复制代码
assertion_name: assert property (property_name)

建立SA检验器的步骤如图1-5所示

一个简单的序列序列s1检查信号"a"在每个时钟上升沿都为高电平。如果信号"a"在任何一个时钟上升沿不为高电平,断言将失败。注意,这相当于"a==1'b1"。

复制代码
sequence s1
	@(posedge clk ) a;
endsequence

图1-6显示了信号"a"和序列在模拟中对这个信号响应的波形。信号"a"在第七个时钟上升沿变为0。这一变化在第八个时钟周期被采样到。因为并行断言使用进程安排中预备(" prepend")阶段采样到的值,在第七个时钟周期,序列s1采样到的信号"a的最稳定的值是1。因此序列成功。在第八个时钟周期,信号"a"被采样的值为0,因此序列失败。一个向上的箭头表示一次成功,个向下的箭头表示一次失败。表1-1总结了信号"a"每个时钟周期的采样值,直到第十五个时钟周期。

1.7边沿定义的序列

序列s1使用的是信号的逻辑值。SVA也内嵌了边缘表达式,以便用户监视信号值从一个时钟周期到另一时钟周期的跳变。这使得用户能检查边沿敏感的信号。三种这样有用的内嵌函数如下:

(1)$rose(boolean expression or signal_name

当信号/表达式的最低位变成1时返回真

(2)fell(boolean expression or signal_name

当信号表达式的最低位变成0时返回真。

(3)$stable(boolean expression or signal_name

当表达式不发生变化时返回真

序列s2检查信号"a"在每一个时钟上升沿都跳变成1。如果跳变没有发生,断言失败。

复制代码
        sequence s2;
                @(posedge clk)  $rose(a)
        endsequence

图1-7显示序列s2响应信号"a"跳变的情况。标记1显示了序列s2的第一个成功。在时钟周期1,信号"a"的值从0变到1在这个时钟周期,信号"a"在序列中的采样值是0。在时钟周期1之前,信号"a"没有被赋值,因此值被认定为"x"。值从x到0的转化不是上升沿,因此序列失败。在时钟周期2,信号"a"在序列中的采样值是1。值从0到1的转化是上升沿,因此序列2在时钟周期2成功。在时钟周期9的标记2显示了另一个成功。表1-2总结了信号"a"前9个时钟周期的转化以及序列如何样和更新值。

1.8逻辑关系的序列

序列s3检査每一个时钟上升沿,信号"a"或信号"b"是高电平。如果两个信号都是低电平,断言失败。

复制代码
sequence s3;
	@(posedge clk) a|| b;
endsquence

图1-8显示序列S3如何根据信号"a"和"b"做出反应。标记1显示了时钟周期12,信号"a"和"b"的采样值都是0,因此序列失败。同理,在标记2所在的时钟周期17,序列也失败。在所有其他时钟周期,信号"a"和信号"b"至少有一个其值为1,因此在这些时钟周期,序列都成功。

1.9序列表达式

通过在序列定义中定义形参,相同的序列能被重用到设计中具有相似行为的信号上。例如,我们可以定义下面这个序列

复制代码
sequence s3_lib(a, b);
        a||b;
endseqruence

通用的序列s3_lib能重用在任何两个信号上。例如,我们有两个信号"req1"和"req2",它们中至少一个信号应该在时钟周期的上升沿为1,我们可以使用下列的序列

复制代码
sequence s3_lib_inst1;
	s3_lib (req1,req2);
endsequence

这些在设计中常见的通常的属性可以被开发成一个库以便于重用。比如,one-hot状态机检查,等效性检查等都适合放在这样的检验器库中。

1.10时序关系的序列

简单的布尔表达式在每个时钟边缘都会被检查。换句话说,它们只是简单的组合逻辑检査。很多时候,我们关心的是检查需要几个时钟周期才能完成的事件。也就是所谓的"时序检查"。在SVA中,时钟周期延迟用"##"来表示。例如,##3表示3个时钟周期。序列s4检查信号"a"在一个给定的时钟上升沿为高电平如果信号"a"不是高电平,序列失败。如果信号"a"在任何个给定的时钟上升沿为高电平,信号"b"应该在两个时钟周期后为高电平。如果信号"b"在两个时钟周期后不为1,断言失败。注意,序列以信号"a"在时钟上升沿为高电平开始。

复制代码
sequence s4;
	@(posedge clk) a ##2 b;
endsequence

图1-9显示了序列s4在模拟过程中的响应。表1-3总结了信号"a"和信号"b"在每个时钟周期的采样值。

与前面小节中的例子不同的是,序列s4的开始时间和结束时间不同。如果信号"a"在任何时钟周期不为高电平,序列在同个时钟周期开始并失败。如果信号"a"是高电平,序列开始。在两个时钟周期后,如果信号"b"是高电平,序列成功(第5和第4时钟周期)。另一方面,如果在两个时钟周期后,信号"b"不是高电平,序列失败。应注意的是,在图中,成功的序列总是标注在序列开始的位置。

1.11SVA中的时钟定义

一个序列或者属性在模拟过程中本身并不能起什么作用。它们必须像下面的例子那样被断言才能发挥作用。

复制代码
sequence s5;
	@(posedge clk) a ##2 b;
endsequence

property p5;
	s5;
endproperty

a5 :assert property (p5);

注意,序列s5中指定了时钟。这是一种把检查和时钟关联起来的方法,但是还有其他的方法。在序列、属性,甚至一个断言的语句中都可以定义时钟。下面的代码显示了在属性p5a的定义中指定时钟。

复制代码
sequence s5a;
	a ##2 b;
endsequence

property p5a;
	@(posedge clk) s5a;
endproperty

a5a : assert property (p5a);

通常情况下,在属性( property)的定义中指定时钟,并保持序列( sequence)独立于时钟是一种好的编码风格。这可以提高基本序列定义的可重用性。断言一个序列并不一定需要定义一个独立的属性。因为断言语句调用属性,在断言的语句中可以直接调用被检查的表达式,如下面的断言a5b所示。

复制代码
sequence s5b;
	a ##2 b;
endsequence

a5b :assert property (@(posedge clk) s5b);

当我们在断言的陈述中要调用已经定义了时钟的序列,就不能再次在断言语句中定义时钟。下面的断言a5c就显示了这种错误的编程风格。下列情况是不允许的。

复制代码
ac5 :assert property (@(posedge clk) s5);

1.12禁止属性

在之前的所有例子中,属性检查的都是正确的条件。属性也可以被禁止发生。换句话说,我们期望属性永远为假。当属性为真时,断言失败。

序列s6检查当信号"a"在给定的时钟上升沿为高电平,那么两个时钟周期以后,信号"b"不允许是高电平。关键词"not"用来表示属性应该永远不为真。

复制代码
sequence s6;
	@(posedge clk)  a ##2 b;
endsequence

property p6;
	not s6;
endproperty

a6 :assert property(p6);

图1-10显示了断言a6如何在模拟过程中响应。我们注意到检验器在标记1和2显示的两个位置(时钟5和14)失败。在这两个时钟周期,发生了被禁止的序列,断言因此失败。另一方面,在信号"a"有效的两个位置(时钟2和时钟9检验器成功。因为从这两个时钟周期开始检查,两个时钟周期以后言号"b"不为高,因此检验器成功。在其他时钟周期中,信号"a"都不为高,因此检验器都自动成功。表1-4总结了信号"a"和信号"b"在每个时钟周期的采样值。

1.13一个简单的执行块

System Verilog语言被定义成每当一个断言检查失败,模拟器在默认情况下都会打印出一条错误信息。模拟器不需要对成功的断言打印任何东西。读者同样也可以使用断言陈述中的"执行块"action block)来打印自定义的成功或失败信息。执行块的基本语法如下所示。

复制代码
assert_name :
	assert property(property_nmae)
		<success message>;
	else 
		<fail message>;

下面显示的检验器a7在执行块中使用了简单的显示语句来打印成功和失败信息。

复制代码
property p7;
	@(posedge clk) a ##2 b;
endproperty

a7: assert property(p7)
	$display("Property p7 successed \n");
	else
	$display("property p7 failed \n");

执行块不仅仅局限于显示成功和失败。它可以有其他的应用,例如:控制模拟环境和收集功能覆盖数据。这些主题将在第2章详细讨论。

1.14蕴含操作符

属性p7有下列特别之处

(1)属性在每一个时钟上升沿寻找序列的有效开始。在这种情况下,它在每个时钟上升沿检查信号"a"是否为高。

(2)如果信号"a"在给定的任何时钟上升沿不为高,检验器将产生一个错误信息。这并不是一个有效的错误信息,因为我们并不关心只检查信号"a"的电平。这个错误只表明我们在这个时钟周期没有得到检验器的有效起始点。虽然这些错误是良性的,它们会在一段时间内产生大量的错误信息,因为检查在每个时钟周期都被执行。为了避免这些错误,某种约束技术需要被定义来在检查的起始点不有效时忽略这次检查SVA提供了一项技术来实现这个目的。这项技术叫作"蕴含"(Implication)。

蕴含等效于一个if-then结构。蕴含的左边叫作"先行算子"( antecedent),右边叫作"后续算子"( consequent)。先行算子是约束条件。当先行算子成功时,后续算子才会被计算。如果先行算子不成功,那么整个属性就默认地被认为成功。这叫作"空成功"( vacuous success)。蕴含结构只能被用在属性定义中,不能在序列中使用。蕴含可以分为两类:交叠蕴含( Overlapped implication)和非交叠蕴含(Non- overlapped implication)。

1.14.1交叠蕴含

交叠蕴含用符号"|->"表示。如果先行算子匹配,在同一个时钟周期计算后续算子表达式。下面用一个简单的例子解释。属性p8检查信号"a"在给定的时钟上升沿是否为高电平,如果a为高,信号"b"在相同的时钟边沿也必须为高。

复制代码
property p8;
	@(posedge clk)  a|->b;
endproperty

a8 : assert property (p8);

图1-11显示了断言a8在模拟中的响应。表1-5总结了信号"a"和信号"b"的采样值和断言的状态。表中一共显示了三种结果。当信号"a"检测为有效的高电平,而且信号"b"在同一个时钟沿也检测为高,这是一个真正的成功。若信号"a"不为高,断言默认地自动成功,则称为空成功。相应的,失败指的是信号"a"检测为高且在同一个时钟沿信号"b"未能检测为有效的高电平。

1.14.2非交叠蕴含

非交叠蕴含用符号"|=>"表示。如果先行算子匹配,那么在下一个时钟周期计算后续算子表达式。后续算子表达式的计算总是有一个时钟周期的延迟。下面以属性p9举个简单的例子。该属性检查信号"a"在给定的时钟上升沿是否为高,如果为高,信号"b"必须在下一个时钟边沿为高。

复制代码
property p9;
	@(posedge clk) a |=> b;
endproperty

a9 : assert property (p9);

图1-12显示了断言a9在模拟中的响应。表1-6总结了信号"a和信号"b"的采样值以及断言的状态。应注意的是,断言在当前时钟周期开始,在下一个时钟周期成功的情况才是真正的成功。相应的,如果属性有一个有效的开始(信号"a"为高),且信号"b"在下一个时钟周期不为高,属性失败。



1.14.3后续算子带固定延迟的蕴含

属性p10检查如果信号"a"在给定时钟上升沿为高,在两个时钟周期后信号"b"应该为高。类似的检查在前面已经用不使用蕴含的方式介绍过了。使用蕴含使得所有误报的错误都被消除。只有属性有效开始(信号"a"为高)时,才进行后续算子的检查(信号"a")。图1-13显示了属性p10的一个模拟的例子。表1-7总结了属性p10中信号的采样值。

复制代码
property p10;
	@(posedge clk) a-> ## 2 b;
endproperty

a10 : assert property (p10);


1.1.44使用序列作为先行算子的蕴含

属性p10在先行算子的位置使用的是信号。先行算子同样可以使用序列的定义。在这种情况下,仅当先行算子中的序列成功时,才计算后续算子中的序列或者布尔表达式。在任何给定的时周期,序列slla检查如果信号"a"和信号"b"都为高,一个时钟周期之后信号"c"应该为高。序列s11b检查当前时钟上升沿的两个时钟周期后,信号"d"应为低。最终的属性检查如果序s11a成功,那么序列s11b被检查。如果没有监测到有效的序列slla,那么序列s11b将不被检查,属性检査得到一次空成功。

复制代码
sequence s11a;
	@(posedge clk) (a && b )  ##1 c;
endsequence

sequence s11b;
	@(posedge clk) ##2 !d;
endsequence

property p11;
	s11a |-> s11b;
endproperty

图1-14显示了断言a11在模拟中的表现。标记1s和1e表明了一个成功的属性检查的起始和结束。标记2s和2e标出了一个失败的起始和结束。在时钟周期11,信号"a"和信号"b"都为高。这表明2个时钟周期以后,即时钟周期14,信号"d"应该为低。但是在例子中的波形上信号"d"为高电平,因此属性失败。

图中所有的空成功都用简单的竖线表示标记3s和3e显示了个成功的属性检查的起始和结束。表达式"a&&b"在时钟周期17为真,在一个时钟周期后,信号"c"像预期的一样为高。因此在时钟周期18,序列s11a成功。正如被期望的那样,接着信号"d"在两个时钟周期后为低。因此,属性在时钟周期20成功。

11.5SVA检验器的时序窗口

到目前为止,带延迟的例子使用的都是固定的正延迟。在下面几个例子中,我们将讨论几种不同的描述延迟的方法属性p12检查布尔表达式"a&&b"在任何给定的时钟上升沿为真。如果表达式为真,那么在接下去的1-~3周期内,信号"c"应该至少在一个时钟周期为高。SA允许使用时序窗口来匹配后续算子。时序窗口表达式左手边的值必须小于右手边的值。左手边的值可以是0。如果它是0,表示后续算子必须在先行算子成功的那个时钟边沿开始计算。

复制代码
property p12;
	@(posedge clk) (a && b ) |-> ##[1:3] c;
endproperty

a12: assert property(p12);

图1-15显示了属性p12在模拟中的响应。每声明一个时序窗口,就会在每个时钟沿上触发多个线程来检查所有可能的成功。p12实际上以下面三个线程展开。

复制代码
(a && b ) |-> ##1 c  或
(a && b ) |-> ##2 c 或
(a && b ) |-> ##3 c

属性有三个机会成功。所有三个线程具有相同的起始点,但是一旦第一个成功的线程将使整个属性成功。应当注意,在任何时钟上升沿只能有一个有效的开始,但是可以有多个有效的结束。这是因为每个有效的起始可以有三个机会成功。

表1-8总结了属性计算过程中所有相关信号的采样值。在任何给定的时钟上升沿,如果信号"a"和信号"b"不全为高,那么属性得到一个空成功。另一方面,如果信号"a"和信号"b都为高,那么属性就有了一个有效的开始。如果信号"c"在之后的1~-3个时钟周期内都没被检测到高电平,属性失败。


注意,属性在时钟周期2和3都有有效的开始。这两个有效的开始同时在时钟周期4成功。时钟周期2开始的检查在两个时钟周期后检测到信号"c"为高。而时钟周期3开始的检查在一个时钟周期后检测到信号"c"为高。这两个都是有效情况,因此它们都成功了。在时钟周期12同样有一个有效的开始。属性在时钟周期13,14和15都检测信号"c"是否为高。由于信号"c"在这所有三个可能的时钟周期始终为低,检测失败。

1.15.1重叠的时序窗口

属性p13与属性p12相似。两者最大的区别是p13的后续算子在先行算子成功的同一个时钟沿开始计算。

复制代码
property p13;
	@(posedge clk) (a && b ) -> ##[0:2] c;
endproperty 


a13: assert property(p13);

图1-16显示了p13在模拟中的响应。与属性p12最大的区别在于一个成功的开始发生在时钟周期12。这个成功是因为检查发生了重叠。信号"c"的值在先行算子成功的同一个时钟沿被检测为高。


1.15.2无限的时序窗口

在时序窗口的窗口上限可以用符号"$"定义,这表明时序没有上限。这叫作"可能性"( eventuality)运算符。检验器不停地检查表达式是否成功直到模拟结束。因为会对模拟的性能产生巨大的负面影响,所以这不是编写SVA的一个高效的方式。最好总是使用有限的时序窗口上限。属性p14在任何给定的时钟上升沿检查信号"a"是否为高。如果为高,那么信号"b"从下一个时钟周期往后最终将为高,而信号"c"在信号"b"为高的时钟周期开始往后最终将为高。

复制代码
property p14;
	@(posedge clk)  a |-> ##[1:$] b ##[0:$] c;
endproperty

a14: assert property(p14);

图1-17显示了属性p14在模拟中的响应。表1-9总结了断言a14和相关信号的采样值。值得注意的是,真正的成功可能在任意个时钟周期后结束。如果一个有效的开始发生,而信号"b"或信号"c"在模拟结束前始终不为高,这些检查被报告为"未完成检验"( incomplete check)。因为信号"b"和信号"c"可以重叠地满足检验,整个检查有可能在一个时钟周期内结束。时钟周期17显示了这样一种情况,当信号"a"在时钟周期17被检测为高,且信号"b"和信号"c"在时钟周期18都被检测为高。



1.16"ended"结构

到目前为止,定义的序列都只是用了简单的连接( concatenation)的机制。换句话说,就是将多个序列以序列的起始点作为同步点,来组合成时间上连续的检查。SVA还提供了另种使用序列的结束点作为同步点的连接机制。这种机制通过给序列名字追加上关键词" ended"来表示。例如,s. ended表示序列的结束点。关键词" ended"保存了一个布尔值,值的真假取决于序列是否在特定的时钟边沿匹配检验。这个 s.ended的布尔值只有在相同时钟周期有效。

序列sl5a和s15b是两个需要多个时钟周期来完成的简单序列,属性pl5a检查序列s15a和序列s15b满足两者间隔一个时钟周期的延迟分别匹配检验。属性p15b检査相同的协议,但是使用了关键词" ended"。在这种情况下,两个序列在结束点同步。由于使用了结束点,两个序列间加上了两个时钟周期的延迟,来保证断言检验的协议与pl5a相同。

复制代码
sequence s15a ;
	@(posedge clk) a ##1 b;
endsequence

sequence s15b;
	@(posedge clk) c ##1 d;
endsequence

property p15a;
	s15a |-> s15b;
endproperty

property p15b;
	s15a.ended |-> ##2 s15b.ended;
endproperty

a15a: assert property(p15a);
a15b: assert property(p15b);

图1-18显示了属性p15a和p15b在模拟中的响应。表1-10总结了断言a15a和a15b的状态。断言a15a的第一个真正的成功发生在时钟周期2。当信号"a"被检测为高,检验在时钟周期2被激活。当信号"d"在时钟周期5检测为高时,检验完成。断言a15b的第一次真正成功出现在在时钟周期3。在时钟周期3,当序列s15a成功,即信号"b"被检测为高时检验被激活。接着在时钟周期5当序列s15b成功,或者说信号"d"被检测为高时,检验完成。


断言a15a的第一次失败发生在时钟周期5。当信号"a"在给定的时钟上升沿为高,且一个时钟周期以后(时钟周期6)信号"b紧接着为高时,检测到一个有效的起始点。这样就会检查后续算子,因为信号"c"在下一个时钟周期不为高,检验失败于时钟周期7。
相应地,断言a15b的第一个失败出现在时钟周期6。当序列s15a在时钟周期6成功结束时,检测到一个有效的起始点。接着检验后续算子在时钟周期8是否得到一个有效的结束点。因为信号"c"在时钟周期7不像期望的那样为高,序列的结束点的值为假,导致检验在时钟周期8为假。上述例子中,我们用了两种不同的方法来实现统一个检验。
第一种方法基于序列的起始点来同步序列。第二种方法基于序列的结束点来同步序列。

1.17使用参数的SVA检验器

SVA允许像 Verilog那样在检验器中使用参数( parameter),这为创建可重用的属性提供了很大的灵活性。比如,两个信号间的延迟信息可以在检验器中用参数表示,那么这种检验器就可以在设计只有时序关系不同的情况中重用。例子1.2显示了个带延迟默认值参数的检验器。如果这个检验器在设计中被调用,它使用一个时钟周期作为延迟默认值。如果在实例化时重设检验器中延迟参数值,那么同一个检验器就可以被重用。在例子1.2中,模块"top"有两个" generic_chk"的实例。实例i1将延迟参数改写为2个时钟周期,而实例i使用默认的1个时钟周期。
例12使用参数的SA检验器的例子

复制代码
module generic_chk(input logic a,b,clk);
	parameter delay=1;
	
	property p16;
		@(posedge clk) a |-> ## delay b;
	endproperty
	
	a16 :assert property (p16);
	
endmodule

//call checker from the top level module
module top(...);
	logic clk,a,b,c,d;
	
	generic_chk #(.delay(2)) i1 (a,b,clk);
	generic_chk #(c,d,clk);

endmodule

图1-19显示了两个检验器实例i和i在模拟过程中对信号变化的响应。


1.18使用选择运算符的SVA检验器

SVA允许在序列和属性中使用逻辑运算符。属性p17检查如果信号"c"为高,那么信号"d"的值与信号"a""的相等。如果信号"c"不为高,那么信号"d"的值与信号"b"的相等。这是个组合的检验,在每个时钟上升沿被执行。

复制代码
property p17;
	@(posedge clk) c ? d==a :d==b;
endproperty

a17 :assert property(p17);

图1-20显示了属性p17在模拟中的响应。表1-11总结了断言的状态和涉及的信号的采样值。在时钟周期1,信号"c"被检测为高,因此检验期望信号"d"和信号"a"有相等的值。但是信号"d"被检测为高,而信号"a"为低,所以检验失败。



1.19使用true表达式的SVA检验器

使用true表达式,可以在时间上延长SⅤA检验器。这代表一种忽略的状态,它使得序列延长了一个时钟周期。这可以用来实现同时监视多个属性且需要同时成功的复杂协议。

序列s18a检查一个简单的条件。序列s18a_ext检查相同的条件,但是序列的成功被往后移了一个时钟周期。当这个序列被用在一个属性的现行算子时,它会造成一些差异。两个序列的结束点不同,因此开始检査后续算子的时钟周期也不一样。

属性p18检查先行算子中的sl8a.end,如果成功,两个时钟周期后,检查s18b.end是否成功。属性p18_ext检查s18a_ext在先行算子中是否成功。这个成功与s18a.ended的成功相同,但是早了个时钟周期。因此属性pl8_ext的后续算子需要在一个时钟周期而不是像p18中定义的两个时钟周期后成功。属性p18和p18_ext检查相同的情况,但是他们在先行算子中的成功点却不同。

复制代码
`define true 1

sequence s18a;
	@(posedge clk) a ##1 b;
endsequence

sequence s18a_ext;
	@(posedge clk) a ##1 b  ##1 `true;
endsequence

sequence 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);

图1-21显示了属性p18和p18_ext在模拟中的响应。可以清楚地看到与断言a18比较,断言a18_ext的起始点被推迟了一个时钟周期。

1.20" $past"构造

SVA提供了一个内嵌的系统任务"$past",它可以得到信号在几个时钟周期之前的值。在默认情况下,它提供信号在前一个时钟周期的值。结构的基本语法如下

复制代码
$past (signal_name ,number of clock cycles)

这个任务能够有效地验证设计到达当前时钟周期的状态所采用的通路是正确的。属性p19检验的是在给定的时钟上升沿,如果表达式(c&&d)为真,那么两个周期前,表达式(a&&b)为真。

复制代码
property p19;
	@(posedge clk) (c && d) |-> ($past ((a  && b),2)==1'b1);
endproperty

a19: assert property(p19);

图1-22显示了属性p19在模拟中的响应。表1-12总结了断言a19的状态和相关信号的采样值。断言在时钟周期1失败。在时钟周期1,信号"c"和信号"d"都为高,断言有一个有效的开始。于是检验器的后续算子需要比较两个周期前的表达式(a&&b)的值。但是由于不可能得到两个信号在时钟周期1之前两个周期的历史,信号的值被当作"x",因此检验器在时钟周期1失败。

检验在时钟周期5有一个真正的成功。在时钟周期5,由于信号"a"和信号"b"都为高,断言有一个成功的开始。后续算子检查在时钟周期3时表达式(a&&b)是否为真。正如期望的那样,在时钟周期3,信号"a"和信号"b"都被检测为高,因此检验成功。

检验在时钟周期16失败。在时钟周期16,由于信号"a"和信号"b"都为高,断言也有一个成功的开始。后续算子检查在时钟周期3时表达式(a&&b)是否为真。信号"a"如期望的那样为高,但是信号"b"为低。这使得表达式(a&&b)为假,检验失败。


带时钟门控的$past构造

past构造可以由一个门控信号 (gating singal)控制。比如,在个给定的时钟沿,只有当门控信号的值为真时才检查后续算子的状况。使用门控信号的past构造的基本语法如下

复制代码
$past (signal_name, number of clock cycles ,gating signal);

属性p20与属性p19相似。但是只有当门控信号"e"在任意给定的时钟上升沿有效时检验才被激活。

复制代码
property p20;
	@(posedge clk) (c && d) |-> ($past ((a && b ),2,e)==1'b1);
endproperty

a20: assert property(p20);

1.21重复运算符

如果信号"stat"在任何给定的时钟上升沿跳变为高,接着从下一个时钟周期起,信号"a"保持三个连续时钟周期为高,然后下一个时钟周期,信号"stop"为高,像上述描述的序列可以使用下面的SVA代码来检验。

复制代码
sequence ss ;
	@(posedge clk) $rose(start) |-> ##1 a ##1 a ##1 a ##1 stop;
endsequence

如果信号"a"需要在很多个周期中保持高电平,编写这样个检验器可能会非常冗长。而且这个例子要求信号"a"连续地保持高电平。当我们只希望检查信号"a"是否在被检测时保持为高,而不一定是三个连续的时钟周期的时候,协议就会变得复杂起来。换句话说,信号"a"需要连续地或者间歇地重复自己三次。

SVA语言提供三种不同的重复运算符:连续重复( consecutiverepetition),跟随重复( go to repetition),非连续重复( non -consecutiverepetition)。

连续重复允许用户表明信号或者序列将在指定数量的时钟周期内都连续地匹配。信号的每次匹配之间都有一个时钟周期的隐藏延迟。连续重复运算符的基本语法如下所示。

复制代码
signal or sequence [*n]

"n"是表达式应该匹配的次数。比如,a[*3]可以被展开成下面的式子。

复制代码
a ##1 a ##1 a

而序列(a ##1 b) [*3]可以展开为

复制代码
(a ##1 b) ##1 (a ##1 b ) ##1 (a ##1 b)

**跟随重复------**允许用户表明一个表达式将匹配达到指定的次数,而且不一定在连续的时钟周期上发生。这些匹配可以是间歇的。跟随重复的主要要求是被检验重复的表达式的最后一个匹配应该发生在整个序列匹配结束之前。跟随重复运算符的基本语法如下所示。

复制代码
signal [->n]

参考下面的序列:

复制代码
start ##1 a[->3] ##1 stop

这个序列需要信号"a"的匹配(即信号"a"的第三次,也就是最后一次重复的匹配)正好发生在"stop"成功之前。换句话说,信号"stop"在序列的最后一个时钟周期匹配,而且在前一个时钟周期,信号"a"有一次匹配。
非连续重复---与跟随重复相似,除了它并不要求信号的最后一次重复匹配发生在整个序列匹配前的那个时钟周期。非连续重复运算符的基本语法如下所示。

复制代码
signa1 [=n]

在跟随重复和非连续重复中只允许使用表达式,不能使用序列。

1.21.1连续重复运算符[*]

属性p21检査在检验有效地开始两个时钟周期后,信号"a在连续的三个时钟周期为高,再过两个时钟周期,信号"stop"为高。下一个时钟周期,信号"stop"为低。

复制代码
property p21;
	@(posedge clk) $rose(start) |-> ##2 (a[*3]) ##2 stop ##1 !stop;
endproperty

a21: assert property(p21);

图1-23显示了属性p21在模拟中的响应。波形中显示了2个失败和1个真正的成功。其他成功都是空成功。

断言失败于时钟周期2---时钟周期2有一个有效的开始信号。检验器接着检验信号"a"是否从时钟周期4的上升沿开始有连续三个时钟周期为高。信号"a"在时钟周期4和5为高,但是在时钟周期6为低。因此检验失败。检査从时钟周期2开始,在时钟周期6失败。
断言成功于时钟周期9------在时钟周期9检测到一个有效的开始。于是检验器检查信号"a"是否在时钟周期11开始的3个连续时钟周期都为高。信号"a"在时钟周期11、12、13都像预期的那样被检测为高。两个时钟周期后(时钟周期15),信号"stop"如预期地为高。一个时钟周期以后,"stop"被检测为低。至此检验成功。注意,检査从时钟周期9开始,结束于时钟周期16。
断言失败于时钟周期17------在时钟周期17检测到一个有效的开始。于是检验器检查信号"a"是否在时钟周期19开始的3个连续时钟周期都为高。信号"a"在时钟周期19、20、21都像预期的那样为高。接着检验器检查信号"stp"在时钟周期23是否为高,但是没检测到。因此检验失败。可以看到,信号"a"保持了4个时钟周期的高电平。但是检验器只需要检查3个重复,因此直接继续检査信号"stop"。整个检査从时钟周期19开始,失败于时钟周期23。

1.21.2用于序列的连续重复运算符[*]

属性p22检查有效开始的两个时钟周期以后,序列(a#2b)重复三次,接着再过两个时钟周期,信号"stop"为高。

复制代码
property p22;
	@(posedge clk) $rose(start) -> ##2 ((a ##2 b)[*3])##2 stop;
endproperty

a22 :assert property(p22);

图1-24显示了属性p22在模拟中的响应。图中共显示了2个失败和1个真正的成功。
失败1---第一个失败由标记 1s 标出。有效的开始在这个点被检测到。两个时钟周期后,检验器期望序列(a##2 b)重复3次。但是序列只重复了两次。因此检验器失败,失败点由标记1e标出。
成功1---唯一一个真正的成功由标记2s标出。有效的开始在这个点被检测到。两个时钟周期后,检验器开始检查序列(a##2 b)是否重复3次。序列如预期地重复了3次。在序列重复被检验后,再过两个时钟周期,信号"stop"也如期望地为高。因此检验器成功,成功点由标记2e标出。

失败2---第二个失败由标记3s标出。有效的开始在这个点被检测到。两个时钟周期后,检验器开始检查序列(a##2b)是否重复3次。序列如预期地重复了3次。当序列重复被检验到后,信号"stop"被期望在两个时钟周期后为高,但是失败了。因此检验器失败,失败点由标记3e标出。

1.21.3用于带延迟窗口的序列的连续重复运算符[ * ]

属性p23检查在有效开始的两个时钟周期后,序列(a ##[1 : 4] b)重复3次,接着再过两个时钟周期,信号"stop"为高。实际上,这个序列有一个时序窗口,使得情况变得有些复杂。

复制代码
property p23;
	@(posedge clk) $rose (start) |-> ##2 ((a ##[1 :4] b)[*3]) ##2 stop;
endproperty

a23: assert property(p23);

主序列(a ## [1:4] b)*3]可以被扩展成

复制代码
((a##1b)or(a##2b)or(a##3b)or(a##4b))##1
((a#1b)or(a##2b)or(a#3b)or(a##4b))##1
((a ##1 b)or (a ##2 b)or(a ##3 b)or (a ##4 b))

图1-25显示了属性p23在模拟中的响应。 图中有2个失败和1个真正的成功。

失败1 ---第一个失败由标记ls标出。这一点有个有效的开始。从这一点开始两个时钟周期后,检验器期望序列(a#爿4]b)重复3次。但是序列只重复了2次。因此检验器失败,失败点由标记le标出。可以看到成功的两个重复分别是(a ##1 b)和(a ##2 b)。

成功1 ------唯一的真正成功由标记2s标出。这一点有一个有效的开始。从这一点开始两个时钟周期后,检验器期望序列(a ## [1:4] b)重复3次。序列如预期地重复了3次。在成功重复之后,信号"stop"如期望地在两个时钟周期后为高。因此检验器成功了成功点由标记2e标出。可以看到成功的三个重复分别是(a ## 2 b),(a ## 4 b)和(a ## 2 b)。

失败2------第二个失败由标记3s标出。这一点有一个有效的开始。从这一点开始两个时钟周期后,检验器期望序列(a ## [1:4] b)重复3次。序列如预期地重复了3次。在成功地重复之后,信号"stop"被期望地在两个时钟周期后为高,但是失败了。因此检验器失败,失败点由标记3e标出。可以看到成功的三个重复分别是(a ## 2 b),(a ## 2 b)和(a ## 3 b)

1.21.4连续运算符[ * ]和可能性运算符

属性p23指定了一个重复序列的时序窗口。同样的,重复的次数也可以是一个窗口。比如,a[*1:5]表示信号"a"从某个时钟周期开始重复1~5次。这个定义可以展开成下面的表达式。

复制代码
a or 
(a ##1 a ) or
(a ##1 a ##1 a ) or
(a ##1 a ##1 a ##1 a) or 
(a ##1 a ##1 a ##1 a ##1 a ) or 

重复窗口的边界规则与延迟窗口的相同。左边的值必须小于右边的值。右边的值可以是符号"$",这表示没有重复次数的限制。

属性p24显示了一个带没有重复次数限制的有限的检查。它检验有效开始两个时钟周期后,信号"a"将保持为高,直到信号"stop"跳变为高。

复制代码
property p24;
	@(posedge clk) $rose(start) |->
		##2 (a[*1:$]) ##1 stop;
endproperty

a24: assert property(p24);

图1-26显示了属性p24在模拟中的响应。图中有1个失败和1个真正的成功。

失败------1个有效的开始发生在时钟周期3,如标记ls所示。检查期望在两个时钟周期后,信号"a""将保持为高直到信号"stop"有效。信号"a"一直为高直到时钟周期7。在时钟周期8,"a"被检测为低,但是信号"stop"仍然不为高。因此检验在时钟周期8失败,如标记le所示。

成功------1个有效的开始发生在时钟周期11,如标记1s所示。检查期望在两个时钟周期后,信号"a"将保持为高直到信号"stop"有效。信号"a"一直为高直到时钟周期15。在时钟周期,"a"被检测为低,但是信号"stop"如期望地为高。因此检验在时钟周期16成功,如标记2e所示。

图1-26使用连续重复和可能性运算符的SⅤA检验器的波形

1.21.5跟随重复运算符[->]

属性p25检查如果在任何时钟上升沿有有效的开始,两个时钟周期后,信号"a"连续或者间断地出现3次为高,接着信号"stop在下一个时钟周期为高。

复制代码
property p25;
	@(posedge clk) $rose(start) |->
		##2 (a[->3])  ##1 stop;
endproperty

a25: assert property(p25);

图1-27显示了属性p25在模拟中的响应。图中显示共有1个失败、1个成功和一个未完成的检查。

失败1---标记ls标出了检验器的一个有效开始。检验器期望在有效开始的两个时钟周期后,信号"a"重复3次。信号如预期地重复3次,在信号"a"的第3次匹配后,信号"stop"没能如期望的那样在下一个时钟周期为高。因此检查在标记1e所示位置失败了。

成功1 ---标记2s标出了检验器的一个有效开始。检验器期望在有效开始的两个时钟周期,信号"a"将重复3次。信号如预期地重复3次。在信号"a"的第3次匹配后,在下一个时钟周期信号"stop"如期望的那样为高。因此检查在标记2e所示位置成功了。

未完成1---一标记3s标出了检验器的一个有效开始。检验器期望在有效开始的两个时钟周期后,信号"a"重复3次。信号重复了两次,模拟就结束了。应注意到,在模拟周期结束前,信号"stop"出现了一次有效。由于重复语句还没有完成,这个有效的"stop"并没有发生任何作用。检验3个重复的语句阻塞了信号"stop"的检验。因此在模拟结束时这个检査未能完成。

1.21.6非连续重复运算符[=]

属性p26检查如果在任何时钟上升沿有有效的开始信号,两个时钟周期后,在一个有效的"stop"信号前,信号"a"连续或者间断地出现3次为高,然后一个时钟周期后"stop"应该为低。p26和p25做的是相同的检查,唯一的不同是p26使用的是非连续(non- consecutive)重复运算符而不是跟随(goto)重复运算符。这表示在属性p26中,在信号"stop"有效匹配的前一个时钟周期,信号"a"不一定需要有有效的匹配。

复制代码
property p26;
	@(posedge clk) $rose(start) |-> 
		##2 (a[=3]) ##1 stop ##1 !stop;
endproperty

a26: assert property(p26);

图1-28显示了属性p26在模拟中的响应。图中显示有2个真正的成功和1个未完成的检查。

成功1 ------标记ls标出了检验器的一个有效开始。检验器期望在有效开始的两个时钟周期后,信号"a"重复3次。信号"a"如预期地重复3次,在"a"的第个匹配后,期望一个有效的信号"stop",但是不必在下一个时钟周期发生。实际上,在信号"a"的第三次匹配的两个时钟周期后有一个有效的信号"stop",因此检验如标记le所示的成功了。这就是跟随重复和非连续重复的不同之处。在相同情况下,属性p25由于使用的是跟随重复而失败了。

成功2 ------标记2s标出了检验器的一个有效开始。检验器期望在有效开始的两个时钟周期后,信号"a"重复3次。信号"a"如预期地重复3次,在"a"的第三次匹配后,期望一个有效的信号"stop",但不必在下一个时钟周期发生。实际上,在信号的第三次匹配的1个时钟周期后有一个有效的信号"stop",因此检验如标记2e所示的成功了。

未完成1------标记3s标出了检验器的一个有效开始。检验器期望在有效开始的两个时钟周期后,信号"a"重复3次。实际上信号"a"重复了两次,在第3次重复出现前,模拟结束了。同样应注意,信号"stop"在模拟周期结束前曾经出现为高。因为重复语句还没有完成,所以这个"stop"并没有起到任何作用。信号"a"重复三次的语句阻塞了信号"stop"的检验。因此在模拟结束时检验未完成。这个行为与跟随重复(" go to"repetition)相同。

相关推荐
芯门13 小时前
基于 Xilinx K7 FPGA 的全套万兆 10G GigE Vision 商业级传输方案
计算机视觉·fpga开发·万兆gige
ehiway13 小时前
FPGA在未来产业中的应用潜力与商业机会分析
fpga开发
GateWorld14 小时前
FPGA内部模块详解之第1篇 FPGA内部结构总览
fpga开发·fpga内部模块
爱吃汽的小橘14 小时前
驱动GPIO使用GPIO中断模式
fpga开发
普密斯科技15 小时前
精准把控每一处细节——FPGA焊点高度精准检测实施方案
人工智能·深度学习·数码相机·计算机视觉·fpga开发·测量
FPGA_小田老师17 小时前
Xilinx AXI UART Lite IP核:IP核深度解析
fpga开发·uart·串口通讯·axi转uart
GateWorld19 小时前
FPGA内部模块详解之二 FPGA的逻辑“心脏”——可编程逻辑块(PFU/CLB)深度解析
fpga开发·fpga内部结构
Saniffer_SH20 小时前
【高清视频】如何针对电动汽车进行通信可靠性测试、故障注入与功率分析?
服务器·驱动开发·测试工具·fpga开发·计算机外设·硬件架构·压力测试
博览鸿蒙1 天前
基于FPGA技术的数字存储示波器设计探讨
fpga开发
Saniffer_SH1 天前
【高清视频】企业级NVMe SSD (E3.S, U.2)和消费类M.2 SSD拆解分析
服务器·网络·数据库·驱动开发·测试工具·fpga开发·压力测试