第6章 运算符

 下表列出了SV断言提供的所有运算符(IEEE-1800,2005)。我们将在单独章节中讨论1800-2009 LRM的功能。我们将在本章详细介绍每个运算符,因为这些运算符每一个都有独特的功能(表6.1和6.2)。

运算符 描述
##m, ##[m:n] 时钟延迟
[*m],[*m:n] 重复连续
[=m],[=m:n]
重复 - 不连续
[->m],[->m:n]
goto重复 - 不连续
sig1 throughout seq1
信号sig1在序列seq1中必须为真
seq1 within seq2
序列seq1必须包含在序列s2中
seq1 intersect seq2
两个序列必须同时开始,  同时结束
seq1 and seq2
两个序列必须同时开始,但是可能会在不同的时间结束
seq1 or seq2
 如果任一序列成功,则成功,时间上最短的成功,则结束评价
first_match complex_seq1
仅匹配可能的多个匹配中的第一个
not <property_expr>
如果property_expr 评估为真,则取反
property_expr 评估为false; 反之亦然。

if (expression) property_expr1

else property_expr2

if...else...
|->
重叠蕴涵运算符
|=>
非重叠蕴涵运算符

                                                                         表6.1并发断言操作符

 6.1 ## m:时钟延迟


                                         图6.1##时钟延迟-基础

 时钟延迟运算符是所有运算符中最基本的,可能是您使用最多的那个!首先,请注意## m表示延迟'm'个采样边沿。在这个例子中,采样边沿是一个'posedge clk',因此##m表示m个posedge clks(图6.1)。

该属性评估左侧序列'z'在posedge clk为真,并且暗示序列' SAB”。 'Sab'在同一时钟边缘寻找'a'(因为属性中使用重叠蕴涵运算符),如果这是真的,等待两个posedge clks,然后查找'b'为真。

在仿真日志中,我们看到在时刻10,clk的上升沿,z = 1和a = 1。因此,序列评估继续进行。之后的两个时钟(在时刻30),它检查是否b = 1,它发现为真,那么属性通过。

类似的情景在时刻40开始,但是这一次,b在时刻60不等于1(时刻40之后的两个时钟),那么属性失败。

我们可以看到## m是绝对延迟。那么'm'是可以是一个变量吗?回答是否定的。但我有一个有趣的实现方式,使用“计数器”技术使其变得可变。请参见第14.8节 ‘时钟延迟:如果您想要可变时钟延迟?’

现在,让我们看看如果m = 0会发生什么。这意味着## m等于## 0 ... hmmm,没有延迟!

 6.1.1 时钟延迟运算符:: ## m其中m = 0


                                             图6.2## m时钟延迟,m = 0

 我们像之前一样检查属性,但是m = 0。正如预期的那样,序列'Sab'寻找'a'为真,然后在同一时钟寻找'b'为真。此外,在这个属性中,我们使用重叠蕴涵运算符,这意味着当'z'为真时,'a'应该是真的,而且同时'b'应该为真 。这是您可以在同一采样边沿(或“clk”边沿)检查多个表达式为真的方法之一(图6.2)。

这是一个很好的应用,在复杂的序列中,您可以有效地使用## 0。请注意,显然你也可以使用'&&'代替##0。

 6.1.1.1 应用:时钟延迟运算符:: ## m(m = 0)

 

                                                        图6.3## 0-应用

 见图6.3。

 6.2 ##[m:n]: 时钟延迟范围

由于通常我们需要在固定的时钟范围内评价信号或表达式(而不是固定的时钟数量)是正确的,所以我们需要一个描述此时序的操作符。

## [m:n]允许在一系列采样边沿(时钟边沿)检查其后的表达式。 图6.4解释了使用运算符的规则。 注意在这里也是,m和n需要是常数,他们不能是变量。

 图中的属性'ab'表示如果在clk的第一个上升沿, 'z'是真的,那么序列'Sab'被触发。序列'Sab'在与z'相同的时间评估'a',然后寻找'b'在1或2或3个时钟延迟后被评估为真。在3个时钟内发现“b”的第一个实例是真实的,该属性将会通过。如果'b'在3个时钟内没有被置位,则该属性将失败。


                                          图6.4 ##[m:n]时钟延迟范围

 请注意,在图中你会看到一共有3次PASS。这仅仅意味着,只要“b”在3个时间内第一次成立该属性就PASS,这并不意味着该属性将被评估并PASS 3次。重申一下,一旦(即第一次)“b”成立,属性就会PASS。

 6.2.1 时钟延迟范围操作符:##[m:n] ::多线程

 让我们回到多线程!这是多线程断言的一个非常有趣的行为。这是你真正需要了解的东西(图6.5)。

在下图的时序图中,标记s1时刻,'rdy'为高,前导(即|->左侧)表达式为真。这意味着'rdyAck'在接下来的5个时钟内是真实的。 s1线程开始寻找'rdyAck'为真。接下来的时钟,rdyAck还不是真的,但幸运的是'rdy'在下一个时刻(s2)还是高。这将分离出另一个线程,这个线程还将等待'rdyAck'在接下来的5个时间内成立。 'rdyAck'在s1的5个时间内出现,该线程满足条件并且会PASS。但第二个线程同时也会PASS,因为它也在它所等待的5个时钟内获得了'rdyAck'。


                                         图6.5##[m:n]—多线程

 这是一个非常重要的理解点。范围运算符可以使多个线程同时完成。这与我们之前在##m常量延迟中看到的相反,其中每个线程总是只在固定的##m时钟延迟之后才能完成。每个单独的线程都有一个单独的结束。使用范围延迟运算符,多个线程可以同时结束。

一个提示是保持前导词是一个边缘敏感函数。例如,在上面的例子中,我们可以用'@(posedge clk)$ rose(rdy)''来代替'@(posedge clk)rdy'',这样只会触发前一次,就没有多个线程同时结束的混乱。这也是一个影响性能的提示。尽可能使用边缘敏感的采样值函数。电信敏感的前导词可以阻止影响仿真性能的意外多个线程。

 6.2.2时钟延迟范围运算符:: ##[m:n] (m=0; n=$)

 

                        图6.6##[m:n], m=0 且 n=$的时钟延迟范围

 在图6.6中,我们在范围的两端(从0到无穷大)处于极限状态('$'表示无限延迟)。如上所述,序列'Sab'将在'a'的同一时间寻找'b',或者期望它在任何时候都是真的,直到仿真结束。如果在仿真结束之前发现“b”是真的,那很好。如果不是,仿真器将(应该)给出警告,该断言保持不完整。

 

                                            图6.7##[1:$] 时钟延迟范围应用

 这个例子与我们前面看到的类似。但在这个例子中,我们预计'tErrorBit'会在一定的时钟延迟范围内上升。该图解释了断言是如何工作的。还要注意,你可以使用'&&'代替##0来达到相同的结果。由于断言主要是时间域,所以我更愿意将时间域结构与所有东西联系起来。但这是一个偏好问题(图6.7)。

 6.3 [*m]: 连续重复运算符

 如图6.8所示,连续重复运算符[*m],可以看到与该运算符相关的信号/表达式对'm'个连续时钟保持为真。请注意,'m'不能是$(无限连续重复)。

该运算符要注意的重要一点是,它将在信号或表达式匹配的最后一次迭代结束时匹配。


                                         图6.8[*m]连续重复运算符-基础

图6.8中的例子表明,当'z'为真时,在下一个时钟,序列'Sc1'应该开始评估。 'Sc1'寻找'a'为真,然后等待1个时钟,然后在'b'上寻找连续的两个匹配。这在仿真日志中描述。在时刻10 'z'很高; 在时刻20,'a'如预期的那样为高(因为属性中应用非重叠蕴涵运算符);在时刻30和40,'b'仍然为高才能满足b[*2]的要求。在'b'的第二次为高,评价结束时,该属性符合其所有要求且通过。

日志的后半部分显示该属性失败,因为'b'在两个连续时钟内没有保持高电平。再次,比较在最后一个时钟处结束,其中连续重复应该结束,然后属性失败。


                                            图6.8[*m]连续重复运算符-应用

 图6.9显示了一个有趣的应用,我们有效地使用了重复操作符的'not'。在posedge busClk上,如果ADS为高,到启动同一busClk(重叠运算符),则检查ADS是否连续2个busClk保持高电平。如果确实如此,那么我们就取它的‘not’并宣称该属性已经失败。这是一个非常有用的属性,虽然它看起来那么简单。在许多协议中,需要确保某些信号遵循非常严格的协议。这个应用只是模拟这样的协议。还要注意使用参数化属性。

 有趣的是,如果ADS连续三个时钟为高,那么在这3个时钟周期内,属性将会失败两次。请看看你是否能找出原因。提示,'信号是电平敏感的'。

6.4 *[m:n]:连续重复范围

 这是另一个重要的运算符,您需要仔细了解它的运作方式。

让我们从基础开始。 sig[*m:n]意味着对于连续的最小'm'个时钟数,但不超过(最大)'n'个时钟数,sig应保持为真。这很简单。但是,这里的有件事与我们刚学过的sig[*m]算子不同。连续范围运算符匹配在满足所需条件的序列的第一个匹配处结束。仔细体会这一点。它在范围运算符的第一个匹配处结束(相比之下,非范围运算符[*m]在'm'的最后一个匹配处结束)。

图6.10概述了b[*2:5]本质上是四个不同序列的或。当这四个序列中的任何一个匹配时,则该属性被认为匹配并通过。换句话说,该属性首先等待'b'连续两个时钟为高。如果发现该种情况发生,则属性结束并通过。如果它没有发现第二个时钟'b'为真,则该属性将失败。它不等待'b'连续第三个时钟为高,因为如果第二个时钟不是连续高,那么在连续的第三个时钟,'b'没有连续。该链已经被打破。


                                 图6.10 [*m:n]连续重复范围-基础

 所以,如果'b'在第二个时钟到达时成立,那么该属性将会通过。如果'b'在第二个时钟不成立,那么该属性将失败。它不会等待最大范围。

回到范围b[*2:5]。如果你考虑一下,:5永远不会被执行!如果连续两个时钟的'b'为真,则属性匹配并结束(因为属性在第一次匹配时结束)。如果'b'在两个连续边缘不成立,属性将失败。请仔细研究图6.10中的仿真日志。那么,为什么我们需要最大范围?最大范围:5是什么时候进入画面?:5真的意味着什么?参见图6.11,它将解释最大范围:5的含义和如何使用。

 

                                        图6.11 [*m:n]连续重复范围-例子

 请注意,我们在序列Sc1中添加了'## 1 c'。这意味着连续的运算符匹配完成1个时钟后必须有'c'(高)。好吧,很简单。

现在让我们看看仿真日志。时刻30-90很简单。在时刻30,z = 1和a = 1时,下一个时钟'b'= 1并且对于两个连续时钟‘b’保持为1,并且在1个时钟之后,和要求的那样c= 1,所以该属性通过。但是如果在时刻90时'c'不等于'1'呢?这就是第二组事件显示的情况。

在时刻110处Z = 1和a = 1并且序列Sc1继续。好。接下来的2个时钟b = 1。正确。但为什么属性不在这里结束?是不是应该在第一次匹配就结束?那么,属性并没有在150时刻结束的原因是因为它需要在下一个时钟等待c = 1。好的,所以它在170等待C = 1,但它没有看到c =1。该属性现在是不是应该失败? 没有。这是最大范围:5该出现的地方。由于有一个范围[*2:5],如果属性在连续两次重复'b'后看不到ac = 1,它将等待下一个连续的'b'(现在总数为3),然后查找'c = 1' 。如果它没有看到c = 1,它等待下一个连续的b = 1(现在总共4),然后查找c = 1。仍然没有'c'?它最后等待最大范围5时 b = 1,然后下一个时钟查找c = 1。如果找到一个,则该属性结束并通过。如果没有,该属性失败。

继续看仿真日志,最后一部分显示属性将如何失败。失败的一种方式就是我上面所描述的。另一种方式显示在日志文件中。我在这里复述部分日志文件内容,以帮助我们只专注于日志文件的那部分内容。

# 250 clk=1 z=1 a=1 b=0 c=0
# 270 clk=1 z=0 a=0 b=1 c=0
# 290 clk=1 z=0 a=0 b=1 c=1
# 310 clk=1 z=0 a=0 b=0 c=0
# 310 Sc1 FAIL

 在时刻250,z = 1和a = 1,所以序列继续评估到连续的运算符。在接下来的两个连续时钟中'b'等于1。好。但是在时刻310,b = 0和-also-c = 0。因此,属性失败。连续两次'b'后,应该有第三个'b'或'c =1'。他们都没有出现,属性失败。如果在时间310 C = 1,该属性会通过。如果在时间310处b = 1且c = 0,则在连续五次遇到“b”之前,该属性将继续评估,直到它看到五个连续的'b'或一个c = 1。或者在连续五次“b”之后,仿真日志文件的前一部分显示c = 1。

是不是混淆了?起初情况可能如此。但是,请参阅接下来的几个应用,这个概念将会越来越清楚。这是该语言中最有用的操作符之一,你越了解它,用起来就越有效率。

 6.4.1 应用:连续重复范围操作符

 此应用与我们一直遵循的规则相同。举同样的例子的理由是为了说明规则如何能够在看似相似的逻辑上发生变化。

图6.12中的属性表示,在$ rose(tagError)时,检查tErrorBit保持为真直到mCheck被置位。如果在mCheck被置位之前tErrorBit没有保持置位,那么属性应该失败。

因此,在$ rose(tagError)成立一个时钟后,检查$ rose(tErrorBit)。如果确实如此,那么同时(## 0)tErrorBit [* 1:$]向前移动。 这就是说,我们检查tErrorBit是否连续断言(即在每个posedge时钟),直到符合条件的事件$ rose(mCheck)触发。

             

                                        图6.12 [*m:n]连续重复范围-应用

 换句话说,符合条件的事件使得连续的范围运算符非常有意义。将合格事​​件看作是终止属性评估的事件。通过这种方式,您可以检查某个表达式是否为真,直到符合条件的事件发生(图6.13)。

 

                                            图6.13[*m:n]连续重复范围-应用

 当FRAME_有效(变为低电平)并且CMD有效时,PCI评估周期开始。 CMD== 4'b0001指定PCI特殊周期的开始。在这样一个循环开始时(即前提为真),随后在每个posedge clk上永远连续检查DEVSEL_,直到FRAME_无效(变为高电平)。这是迄今为止最简单的方法来检查一个事件/表达式保持真实(并且我们不知道多长时间),直到另一个条件/表达式为真(即,我称之为限定事件为真)为止。

另请注意,您可以在单个逻辑表达式中混合边缘敏感表达式和电平敏感表达式。这的确令人印象深刻并且有用。

 

                                            图6.14[*m:n]连续重复范围-应用

 图6.14中的属性指出,如果状态机的currentState不是IDLE,并且currentState在32个时钟内保持稳定,那么属性应该失败。

有几点需要注意。

注意整个表达式((currentState!= IDLE)&& $ stable(currentState))被检查连续重复32次,因为我们需要在每个时钟检查,连续32个时钟,currentState不是IDLE,以及在之前的时钟仍然保持不变(即稳定)。换句话说,你必须确保在这32个时钟内,当前状态不会回到空闲状态。如果会到IDLE状态,则前提条件失败,那么将重新开始检查这个条件是否为真(即前提条件为真)。

请注意,如果前提条件确实是真的,则意味着状态机确实在32个时钟内处于相同状态。在这种情况下,我们要断言触发。这是由右侧表达式(1'b0)的严重失败导致的。我们只是简单地编程而没有任何先决条件而失败。

如您注意到的,该属性的独特之处在于前提条件中检查了规则。结果只是用来声明失败(图6.15)。

 

                                        图6.15[*m:n]连续重复范围-应用

 此应用指出状态机匹配状态转换规范。如果我们处于readStart状态,那么在1个时钟之后,状态机应该处于'readID状态并且保持在该状态,直到状态机达到'readData状态。然后预计将保持'readData状态直到'readEnd到达。


                                                 图6.16设计应用

 简而言之,我们确保状态机不会出现偏离,并进行非法状态转换,直到它达到'readEnd。

让我们再检查一个应用(图6.16和6.17)。


                                         图6.17设计应用-仿真日志

 6.5 [=m]: 非连续重复

 非连续重复是另一个有用的运算符(就像连续运算符一样)并且使用非常频繁。在许多应用中,我们希望检查一个信号在多个时钟是否保持有效或失效,并且我们不需要知道这些转换何时发生。例如(我们将在图6.21中看到),如果存在长度为8的非突发读取,那么您期望8 RDACK。这些RDACK可能会连续或不连续(基于读取延迟)。但是在读完之前你必须有8个RDACK。

在图6.18中,属性'ab'表示如果'a'在时钟上升沿采样为高电平,一个时钟后'b'应该不一定连续的出现两次。它们可以在“a”断言之后的任何时候发生。这里需要注意的是,即使属性使用非重叠的蕴涵算子(即第一个'b'应该在'a'= 1之后的1个时钟出现'),第一个'b'可以随时出现在'a'被发现高后1个时钟之后。不一定必须是'a'后的1个时钟!

在仿真日志中,第一部分显示'b'在'a'后1个时钟发生,然后在几个钟后再次置位。这符合属性要求,所以断言通过。

 

                                        图6.18不连续重复运算符-基础知识

 但请注意日志的第二部分。 'b'在'a'之后的1个时钟不会发生,而是2个时钟之后,然后几个时钟后再次出现。即使这样,行为被认为是符合属性要求,所以断言通过。

根据上面的描述,你认为这个属性会失败吗?请试验一下,看看你能否拿出答案。这也将进一步加深你的理解。提示:'b [= 2]'后没有符合条件的事件。

继续使用相同的比喻,请参考下面的例子。 再次,就像在连续的运算符中一样,符合条件的事件(下面示例中的## 1 C)起着重要作用。

除了序列末尾的'## 1 C'外,图6.19中的例子与前面的例子相同。'a|=>b[=2]'的行为与我们在上面看到的完全相同。 '## 1 c'告诉属性,在最后'b'之后,'c'必须出现一次,然后它可以在最后一个有效的'b'一个时钟之后的任何时间发生。 再次注意,即使我们有'## 1 c','c'不一定需要在最后一个'b'有效后的1个时钟发生。 它可以在最后一个'b'后的1个时钟后的任何一个时钟后发生 - 只要 - 当我们等待'c'时没有其他的'b'发生。混乱吗? 不是真的。 让我们看看图7.19中的仿真日志。 这将澄清事实。


                                             图6.19非连续重复运算符 - 例子

 在日志中,时刻5时a = 1;在时刻25,然后在45,b = 1。到目前为止一切都还好。我们正在按照属性的预期行事。然后在时刻75进入c = 1。这也符合'c'在最后b = 1之后的任何时间发生的属性要求。但请注意,在时刻75到达c = 1之前,'b'在时刻45的最后一次出现后没有进入'1'。该属性通过。让我们暂时离开这一点。

现在让我们看看日志的第二部分:在时刻95时a = 1;那么在105和125处b = 1;我们做得很好。现在我们等待c = 1发生在最后'b'之后的任何时候。 C = 1发生在时刻175.但在此之前属性失败!到底是怎么回事?注意在时刻145 b = 1。这是不允许的在这个属性的规则下。在最后一次出现'b'之后,但在出现任何其他b = 1之前,属性期望c = 1。如果在c = 1之前发生另一个b = 1(如时刻145),则所有投注均为关闭。属性不会等待c = 1的发生,只要它看到这个额外的b = 1就会失败。换句话说,(我称之为)资格事件''## 1 c''封装了属性并严格检查在c到来之前b发生了两次(b[=2])。

 6.6[=m:n]非连续重复范围


                                             图6.20非连续重复范围-基础

 图6.20中的属性类似于非连续(非范围)属性,但有一个范围。范围表示(在上面的例子中)'b'必须出现最少2次或最多5次,一个时钟之后'c'可以在任何时间出现,并且在最后出现b = 1和c = 1之间,出现'b'最多不超过5次,。

第一个仿真日志(左上)显示,在时刻5的a = 1之后,b在时刻15和45出现两次(最小次数),然后c = 1在时刻75.为什么属性不等待5次出现b = 1?这是因为在时刻45处的第二个b= 1之后,c = 1在时刻75到达,并且这个c = 1满足两个b = 1中的最小值以及随后的c = 1的属性要求。该属性通过并且不需要等待任何进一步的b = 1。换句话说,在最小需求(2)'b == 1'之后,属性开始寻找'c= 1'。因为它在两个'b = 1'之后确实找到'c = 1',所以属性在那里结束并且通过。

类似地,左下的仿真日志显示'b'出现5(最大)次,然后'c'发生,并且没有发生任何b。该属性通过。这是如何工作的。如上所述,在两个'b = 1'后,属性开始寻找'c == 1'。但在属性检测到'c == 1'之前,它会看到另一个'b == 1'。没关系,因为'b'最多可以发生五次。因此,在第三个'b == 1'之后,属性继续查找'c == 1'或'b == 1',直到达到最大值5'b == 1'。整个过程一直持续到五个'b'遇到。然后,属性只是等待'c'。在此阶段等待'c'时,如果发生第6个'b',则属性失败。该故障行为显示在图6.20右下角的仿真日志中。

 6.6.1应用:非连续重复运算符

 这是一个使用非连续运算符的实例。规格见图6.21。

 

                                            图6.21非连续重复范围-应用

 属性RdAckCheck将等待nBurstRead在时钟上升沿处于高。一旦发生这种情况,它将在ReadDone出现之前开始寻找8 RdAck。如果ReadDone在8 RdAck(而不是9)之后进入,该属性将通过。如果ReadDone在8 RdAck满足之前满足属性将失败。请注意,这将保证非突发协议完全遵守。

下面是一个有趣的例子,仅仅为了好玩......(图6.22)。


                                         图6.22非连续重复范围 - [=0:$]

 第一个案例很有趣。在时刻5'a'是1(前提是真的)触发右侧表达式(|=>)。在时刻15,c = 1,属性通过。但是没有发生'b'。 b [= 0]是一个空的序列,它指出'b'不应该出现。我们将在本书后面讨论空序列。时刻35-65的日志非常简单。 '35'时'a == 1';在时刻55处'b == 1'和在时刻65处'c == 1'。由于属性状态b [= 0:$]并且因为'b'确实发生了一次,并且后面是'c',所以该属性通过。

但让我们检查从时刻75开始的日志。在时刻75,a = 1,所以随后的序列被触发。在时刻85,b = 1和c = 1,属性通过!怎么样?再次注意,b [=0:$]范围的b[=0]部分表示'b'可能永远不会发生。该特性在时间75满足(即'b'不发生)并且1个时钟后'c'到达,因此该属性通过。一旦你对空序列有了更多的了解,你就会更好地理解这个例子。但问题在于,您需要小心使用运算符的最小和最大范围。结果可能不那么明显。

 空序列在章节14.6中讨论。

 6.7 [->m]非连续GoTo重复运算符


                                         图6.23非连续GoTo重复-基础

这就是所谓的不连续的goto运算符!非常类似于[= m]非连续运算符。注意符号差异。 goto运算符是[->2]。

在图6.23中,b[->2]的作用与b [=2]完全相同。那么,为什么要提出另一个具有相同行为的运算符呢?这是为了使符合条件的事件差异化。回想一下,符合条件的事件是在非连续运算符或'goto'非连续运算符之后出现的事件。我称之为符合条件,因为它是最终事件,它限定了序列匹配前的序列。

在图6.24中,我们有所谓的符合条件的事件'## 1 c'。该属性表示,在posedge clk上找到a = 1时,b必须是非连续的出现2次(a = 1后的1个时钟),'c'必须恰好在最后一次出现'b'后的1个时钟内出现。相反,在''b[=2] ## 1 c''的情况下,'c'可能在最后一次出现'b'后的任何时间发生。这是[= m]和[->m]之间的差异。

 

                                                          图6.24非连续重复-示例

 图6.24中的仿真日志显示了PASS和FAIL情况。 PASS场景非常清晰。在时刻5,a == 1,则出现两个不连续的'b',然后恰好在最后的'b = 1',1个时钟后'c = 1'出现。因此,该属性通过。 FAIL场景显示,在b == 1发生两次后,c == 1在b = 1的最后一次出现之后不会恰好在1个时钟后到达。这就是b[->2] ##1 c错误的原因。

 6.8 [=m:n] 和 [->m:n]之间的不同

图6.25中的仿真日志解释的相当清楚。 除LHS使用[=2:5]和RHS使用[->2:5]外,左侧和右侧属性相同。 LHS日志,即b [= 2:5]的那个日志检查通过,而b [->2:5]的那个日志失败,因为根据''b [->2:5] ## 1 c' 'c'必须在最后一次出现'b'后的1个时钟内到达。

现在这里出现一个非常重要的问题。 请注意,由于'## 1 c','c'预计会在最后一次出现b = 1后1个时钟进入。 但是,如果您在该属性中有“## 2 c”,该怎么办?

b [= m] ## 2 C:这意味着在'b'非连续出现'm'次之后,'c'可以在2个时钟后的任何时间发生。 如果'c = 1'在2个时钟之前到达,则该属性将失败。 b [->m] ## 2 c:这意味着在'b'非连续出现'm'次之后,'c'必须在2个时钟之后出现。 不多不少。

 

                                        图6.25[=m:n] 和 [->m:n]之间的不同

 6.8.1应用:GoTo重复 - 非连续重复运算符

 下面的应用表示,在'req'的上升沿,1个时钟后(由于非重复运算符)'ack'必须出现一次,并且它必须在其出现后的1个时钟后解除断言(变低)。如果'ack'断言后1个时钟未发现'ack'无效(低),则该属性将失败(图6.26)。


                                    图6.26 GoTo重复 - 非连续的运算符-应用

 6.9 sig1 ‘Throughout’seq1

 “贯穿”运算符使得在整个序列中测试某个条件(信号或表达式)变得更加容易。请注意,“贯穿”运算符的LHS只能是信号或表达式,不能是序列(或子序列)。 '贯穿'运算符的RHS可以是一个序列。那么,如果你还想要LHS是一个序列呢?这是通过“within”运算符完成的,在“贯穿”运算符之后进行讨论(图6.27)。


                                                图6.27sig1 贯穿 seq1

 让我们来看看图6.28中的应用,它将帮助我们理解整个运算符。

 6.9.1 应用:sig1贯穿seq1


                                             图6.28 sig1 贯穿 seq1-应用

 在图6.28中,属性pbrule1中的左侧表达式(|->)需要bMode(突发模式)下降。一旦这是真的,它需要执行checkbMode序列。

checkbMode确保bMode在整个data_transfer序列中保持低位。请注意,从某种意义上说,我们是通过checkbMode序列确保左侧表达式(!bMode)保持真实。如果bMode在data_transfer结束之前变高,则断言将失败。 data_transfer序列要求dack_和oe_被断言(低电平有效)并保持断言连续四个周期。在整个data_transfer序列中,突发模式(bMode)应该保持为低。


                                                图6.29 sig1 贯穿 seq1-应用

图6.29中有两个仿真日志。两者都是针对失败的情况!在这个例子中,失败案例比PASS案件更有趣!第一个模仿真日志(左侧)显示了在时刻20时的$下降(bMode)。在时刻40之后的两个时钟,oe_ = 0和dack_ = 0被检测到。到现在为止还挺好。 oe_和dack_保持他们的状态持续3个时钟,这也还ok。但是在第四个周期(时刻70),bMode变高。这是违规行为,因为在整个数据传输序列(持续4个时钟周期)中,bMode应该保持低电平,。

第二个仿真日志(右侧)也遵循与上述相同的序列,但是在连续三个时钟之后,oe_和dack_保持低位,dack_在160时刻变高。这是违规行为,因为data_transfer(oe_ = 0和dack_ = 0)应该连续四个周期保持低位。

 这也表明了其他一些重要问题:

  •  (1)整个运算符的双方必须满足其要求。换而言之,整个序列的LHS或RHS如果有一个失败,断言将失败。许多人认为,由于bMode正在检查以确保它保持低电平(在这种情况下),只有当bMode失败时断言才会失败。我们从两个失败日志中看到的情况并非如此。
  •  (2)重点:为了让读者更容易理解这种突发模式应用,我将其分解为两个不同的子序列。但是如果某人给了你时序图并要求你为它写断言呢?

 将任何复杂的断言需求分解为更小的块。这可能是我可以分享给读者的最好的建议。如果您将整个AC协议(时序图)视为一个整体序列,您确实会犯错,并花更多时间调试你写的断言,然后才是调试被测设计。

练习:您将如何使用连续重复[* m]运算符为此应用建模? 请试验以巩固您对throughout和[* m]运算符的理解。

 6.10 在seq2中的seq1


                                                                         图6.30 seq2中的seq1

 与'throughout'类似,'within'运算符可以看到一个序列是否包含在与另一个序列相同的长度内。请注意,'throughout'运算符只允许运算符左侧是信号或表达式。 'within'运算符允许在运算符的LHS和RHS上都有一个序列。如图6·30所示,带有'within'的属性在两个序列中持续时间较长者结束时才结束。

让我们用图6.31中的应用理解“within”操作符。

 6.10.1应用:seq2内的seq1

 

                                     图6.31 seq2应用程序中的seq1

 在图6.31中,我们再次处理突发模式的讨厌协议!当突发模式有效时,主机发送(smtrx)必须保持断言9个时钟,并且Target Ack(tack)必须保持断言7个时钟,并且'tack'序列发生在'smtrx'序列内。这是有道理的,因为从协议的角度来看,从机只有在主机启动请求之后才会响应,主机在从机完成后结束传输。

在图6.31中,断言bMode($fell(bMode))暗示“'smtrx'内'stack'是有效的'。现在,仔细查看smtrx中的蕴含属性@(posedgeclk)$ fell(bMode)| =>stack;

LHS和RHS序列在结果触发后开始执行。 'stack'会评估它是否为为真,并且'smtrx'开始自己的评估。同时,'within'运算符会持续确保'stack'包含在'smtrx'中。图6.31中的注释显示了该属性如何处理协议的不同部分。仿真日志如图6.32所示。

 6.10.2‘within’运算符成功案例

 

                                    图6.32within运算符-仿真日志-PASS的情况

 在图6.32的左侧,bMode在时刻0(未显示)为'1',在时刻10时为'0'。满足$fell(bMode)之后,右侧的表达式开始执行。 'stack'和'smtrx'序列都开始执行。如左侧仿真日志所示,根据需要,'mtrx'下降并保持低9个时钟。在'mtrx'下降后,'tack'下降,7个时钟保持低电平,'mtrx'变高时同时变高(即两个序列同时结束)。换句话说,“tack”包含在“mtrx”中。这符合“within”运算符的要求,并且属性通过。请注意,“within”运算符可以同时开始或结束序列。同样,右侧日志显示两个序列同时开始,'tack'包含在'mtrx'中,并且属性通过。

 现在让我们看看失败的案例。

 6.10.3 'within'运算符:失败案例

 

图6.33 within运算符-仿真日志-FAIL的情况

 仿真日志显示不同的失败情况。在顶端,'tack'确实包含在'mtrx'中,但'tack'并不保持在所需的7个时钟中,并且属性失败(图6.33)。

在中间日志中,'tack'包含在'mtrx'中,但是这次'mtrx'被少断言了1个时钟。

底部的日志显示了'tack'和'mtrx'都满足它们所需的clks个数的要求,但'tack'会在'mtrx'的下降沿前一个时钟开始,从而违反'within'语义。“within”两侧的序列可以同时开始或同时结束,但包含在较大序列中的序列不能较早开始或较晚结束。从这些仿真日志中注意到,当两个序列中较大的一个结束时,属性结束。在我们的案例中,属性不会在“stack”违规时立即结束。它等待'smtrx'结束,才对属性'pwin'的通过/失败做出判断。

 6.11 seq1与seq2

 

                                                    图6.34seq1和seq2-基础

 顾名思义,'与'运算符期望运算符的LHS和RHS的'与'评估为真。只要两个序列都符合要求,哪个序列首先结束并不重要。当两个序列中较长的一个结束时,属性评价结束。但请注意,两个序列必须同时开始(图6.34)。

 '与'操作符非常有用,当您想确保设计中的某些并发操作时,希望同时启动并且它们都能圆满完成。举例来说,在处理器领域,当读取L2高速缓存时,L2将启动标签匹配并同时读取,预期标签可能不匹配。如果匹配,则会中止读取。因此,一个序列是开始标记比较,而另一个序列是开始读取(结束于读取完成或中止)。Read序列的设计使其一旦出现标签匹配就立即中止。这样我们就确保了两个序列同时开始,并且两个序列都结束。让我们看看下面的例子来清楚地理解'与'操作符的语义。这些图片是自我解释的,并在图中标注(图6.35和6.36)。

 

                                            图 6.35与运算符-应用

 6.11.1 应用:'与'运算符


                                                 图6.36 与运算符-应用II

 在图6.37中,我们在一个属性中'与'两个表达式。换句话说,如前所述,'与'运算符允许在运算符的LHS和RHS上都有信号,表达式或序列。仿真日志用通过/失败指示进行解释。


                                                 图6.37 与表达式

 6.12 seq1'或'seq2

 '或'两个序列意味着当两个序列中的任何一个满足要求时,其属性将通过。请参考图6.38以及后面的示例以获得更好的理解。

关于'或'运算符需要注意的特征是只要LHS或RHS序列中的任何一个满足要求,其属性将要结束。这与'与'形成鲜明对比,只有在最长序列结束后才会对属性进行评估。


                                                 图6.38 seq1或seq2-基础

 另请注意,如果双方中的较短者失败,则序列将继续在较长的序列上寻找匹配。下面的例子说明了这一点。

 6.12.1应用:或运算符

 

                                                    图6.39 或运算符-应用

 一个简单的属性如图6.39所示。显示了不同的属性通过情况。在图的右上方,'ab'和'cde'序列同时开始。由于这是'或',只要'ab'完成,该属性完成并通过。换句话说,该属性不会等待'cde'完成了。

在左下角,我们看到'ab'序列失败。然而,由于这是一个'或'的属性继续寻找'cde'是真的。那么,'cde'确实是真的,属性通过(图6.40,6.41和6.42)。


                                                       图6.40 或运算符-应用II

 

                                                    图6.41 或运算符-应用I-III

 

                                                        图6.42 或表达式

 6.13 seq1 'intersect' seq2

 那么,对于“throughout”,“within”,“and”以及“or”运算符来说,谁又需要另外一个运算符来验证序列(如图6.43)是否匹配?

 

                                                图6.43 seq1与seq2相交

'throughout'或'within'或'and'或'or'不能确保运算符的LHS和RHS序列完全相同。但只要信号/表达或序列满足其要求,运算符就不会在意它们可以具有相同的长度。这就是'intersect'出现的地方。它确保两个序列确实同时开始并且同时结束并满足他们的要求。换句话说,它们相交。

正如你所看到的,'与'和'相交'之间的区别在于'相交'需要两个序列具有相同的长度,并且它们都在同一时间开始并且以同时结束,'与'可以有两个不同长度的序列。本章后面的时序图显示了这一点。但首先,一些简单的例子可以更好地理解“相交”。

 6.14应用:'intersect'运算符

 图6.44显示了'intersect'操作符的两种失败情况。

属性'isect'表示如果在posedge clk中对'z'采样为真,那么序列'abcde'应该被执行并且保持为真。我已将所需序列分解为两个子序列。序列'ab'要求'a'在posedge clk上为真,然后'b'在1到5 clks的任何时间为真。序列'cde'是一个固定的时域序列,它要求c在posedge clk上为真,然后d在2个时钟后成立,'e'在'd'成立2个时钟后成立。

图6.44中的时序图显示'ab'和'cde'都符合他们的要求,但属性失败,因为他们都不是同时结束(即使他们同时开始)。类似地,左下角的时序图显示'cde'和'ab'都符合要求,但不同时结束,因此断言失败。

在图6.45中,我们显示了一个具有相同属性的PASS情况(为了方便,在此重复)。 'ab'和'cde'都符合他们的要求,并在同一时间结束。因此断言通过。

现在让我们看看图6.46中的例子。这个在序列'ab'中不使用范围运算符(如上例所示)。很明显,如果没有范围运算符,那么这两个序列具有固定的长度并在不同的时间结束,相交属性将永远不会通过。因此,在使用'相交'运算符时使用带范围运算符的子序列是有意义的。


                                             图6.44 seq1'intersect'seq2-应用


                                             图6.45 seq1相交seq2  - 应用 II

 

                                                  图6.46相交使带范围运算符的子序列是有意义的

 6.14.1 应用:相交运算符(有趣的应用)

好吧,我承认这个属性可以写得更简单,就像

@ (posedge clk) $rose(Retry) |->Retry ##[1:4] $rose(dataRead);

 那么,为什么我们把它变得复杂?我只是想强调一下使用“相交”的有趣方式(图6.47)。

当$ rose(Retry)为真时,右侧表达式(|->)执行。随后使'true [* 1:4]和'(Retry ## [1:$] $ rose(dataRead)'之间'相交')。 '相交'的LHS表示在连续4个周期内它将为真。 RHS说Retry应该从现在开始直到永远,但直到$ rose(dataRead)。现在回想一下,'相交'需要LHS和RHS'长度'相同。

 

                                            图6.47相交运算符:有趣的应用

 如果$ rose(dataRead)没有在4个周期内到达,RHS将继续执行超过4个时钟。但是由于true [* 1:4]现在已经完成并且由于相交需要双方同时完成,所以断言将失败。

如果$ rose(dataRead)确实发生在4个时间范围内,则该属性将通过。为什么?请使用上面的讨论,你将能够得出一个答案。我将这个留给读者来得到答案。提示:'true是带范围的’。在“范围”内的任何时间都是正确的。

那么,这种属性的实际用途是什么?任何时候你想要在一段时间内发生一个大的序列,就很容易使用上述技术。一个大序列可能有许多时域和时间复杂性,但通过上述方法,您可以简单地将'true结构与'intersect'叠加,以获得期望的结果。

 6.14.2'相交'和'与':: 有什么区别?

 见图6.48。


                                     图6.48 与相对于相交 - 有什么区别

 6.15 first_match


 6.15.1 应用:first_match

 在图6.49中,属性'fms'表示在'bcORef'的第一次匹配后一个时钟,'a'应该rose。正如你注意到的,序列'bcORef'有许多匹配,因为范围运算符和'或'。只要发现'bcORef'的第一次匹配,该属性就会寻找$ rose(a)。在顶部的日志中,情况就是这样,并且属性通过。在底部日志中,$ rose(a)不会发生,并且属性失败。日志中间在图6.49内解释。


                                     图6.49first_match-应用

图6.50进一步解释'first_match'。注释解释发生了什么。


                                     图6.50 first_match-应用

图6.51进一步阐明了$ first_match。这是传统的PCI总线协议应用。如图所示,第一次frame_irdy_高(无效),总线进入空闲状态。请注意,一旦frame_和irdy_被取消断言,总线将长时间保持空闲状态。但我们想要第一次总线传输结束(由frame_irdy_ high)且总线进入空闲状态。我们不想评估任何进一步的busIdle条件。


                                                     图6.51first_match-应用

那么,如果你从上述属性中删除'first_match'会发生什么?属性将在frame_&&irdy_为高的每个时钟继续寻找state==busidle。这些将是完全臃余的检查。

请注意,在上述所有示例中,我们在前面使用了first_match()。为什么?因为根据定义,属性的后果(RHS)的行为与first_match完全相同。一旦找到第一个匹配项,就不会对结果进行评估(不使用first_match)。但是每当有表达的匹配时,先行条件就会继续触发。请注意以下'cover'属性。这进一步解释了first_match的使用。

abcProp: cover property (@ (posedge clk) a ##[1:4] b ##1 c);

让我们说,'a'在时刻10是真的,而'b'在时刻20是真实的,因此符合它的要求,然后在时刻30'c'是真实的。整个序列匹配并且将被视为'被覆盖' )。但是如果'b'在20,30,40时刻保持为真,并且'c'在30,40,50时保持为真,则覆盖报告将显示该序列的多个'覆盖率'。我们并不真正需要的东西。以下将解决这个问题。

abcProp: cover property (@ (posedge clk) first_match (a ##[1:4] ##1 b ##1 c));

在这种情况下,只要'a'为真并且'b'为真,## [1:4]中的第一次'c'在下一个时钟被评估为真,那么该属性被视为被覆盖。直到再次发现'a'时,才会对该序列进行下一次评估。

 简而言之,如果有多个匹配的序列,但您希望评估在您使用'first_match'的第一个匹配时停止,那么就用first_match。

 6.16 not<property expr>

 'not'运算符看起来非常温和。然而,它可能容易被误解,因为通常我们都是正向思考问题-不是吗?


                                                 图6.52 not 运算符-基础

 图6.52显示了'not'的用法。无论何时'cde'为真,该属性将由于'not'而失败并且如果'cde'不正确则通过。请参阅图6.53中的示例,然后再参考应用。

 6.16.1应用:not运算符

 首先,请参阅14.15章节‘空pass’然后了解以下应用。

图6.53显示了使用'not'运算符时工程师犯的一个典型错误。如果前面的a'## 1 b'不匹配,那么在这个例子中如果没有'not',那么这个属性空pass(空pass在14.5节中讨论),且没有真正发生。属性只是等待左侧表达式(|->)是真实的,以便后面的表达式能够开始执行。然而,由于左侧表达式在其前面有“not”,只要属性看到左侧表达式不匹配,它就会失败(不是空pass)。这对您的设计结果确实是有害的,因为这会导致许多错误的失败。

如图底部所示,只需删除蕴涵运算符''|->''即可, 并将其替换为## 0,这与重叠效果相同运算符。但是,由于没有蕴含运算符,所以没有空pass。


                                                 图6.53 not运算符-应用


                                                     图6.54 not运算符-应用

图6.54中的应用是一个非常经典的应用场景。该规范说,一旦请求req(高电平有效),我们必须在得到另一个请求之前得到一个ack。这种情况发生在许多设计中。

让我们来看看这个断言。属性strictOneAck表示当'req'被置位(高电平有效)时,ack [* 0:$]保持低电平直到$ rose(req)。如果这匹配则属性失败(因为'not'运算符)。

换句话说,我们正在检查以确认ack在下一个req之前一直保持低位,这意味着如果ack在req到达之前变为高(!ack [* 0:$] ## 1 $ rose(req))序列将会失败,而‘not’它会让它通过。这是正确的行为,因为我们确实需要在下一个请求之前进行ack。或者反过来看(如日志所示),如果'ack'保持低位,直到下一个'req'到达,那么序列(!ack [* 0:$] ## 1 $ rose(req))将会通过,它的'not'会是失败。这也是正确的,因为我们不想在下一个请求到达之前保持低位。我们希望'ack'在下一个'req'到达之前到达。

可能看起来有点奇怪,这个属性可以用很多不同的方式写出来,但这会让你很好地理解not逻辑如何有用。什么是写这个属性更简单的方法?

 6.17 if(expression)property_expr1 elseproperty_expr2

 

                                                              图6.55 if ... else

 'if' 'else'与过程语言陈述中的对应部分相似,并且显然非常有用。如图6.55所示,我们根据先前的情况做出决定。属性'if'表示在'a'为真时,'b'或'c'应该至少出现一次,在'a'一个时钟之后任何时间。如果这个先行词是真的,那么结果就会执行。因此,如果'b'为真,则'd'为真,'b'为假或'c'为真时'e'为真。

图6.55左下方的仿真日志显示,在时刻15,'a == 1'和1个时钟后'b'为真。因为'b'是真的,所以'd'在一个时钟后即时刻45是真实的。一切按需求工作并且属性通过。在右下角的仿真日志中,时刻55处的'a == 1'和'c'在85处变为真。这需要'e'在1个时钟后成为真,但它不是,所以属性失败。这只是一种使用if-else的方法,并且将antecedent 和 consequent相关联。

根据图6.55的类比,图6.56给出了一个实际应用。

 6.17.1应用:if then else


                                                     图6.56 if ... else-应用

 这个属性是自我解释。在TagCompare上,如果是命中,则启动MESI比较,否则启动Read Allocation周期。


猜你喜欢

转载自blog.csdn.net/zhajio/article/details/80078799