Systemverilog断言介绍(三)

3.2.3 LOCATION IN PROCEDURAL BLOCKS

直接断言是在其执行所在位置的变量所持有的任何值上操作。这可能与信号在过程结束时所决定的值不同。以下是一个示例,展示了一个放置不当的直接断言的过程:

always_comb begin
    gnt = 4’b0;
    no_conflict: assert final ($onehot0(gnt));
    if (|req)
        gnt = f_compute_grant (req);
end

在这个例子中,断言no_conflict被编写用来检查是否始终最多只有一个授权。但是,因为它被放置在f_compute_grant调用之前,即使f_compute_grant函数中有错误,它实际上永远不会检测到错误:在达到断言行时,gnt的值始终是在前一行分配的0。以下版本更合理:

always_comb begin
    gnt = 4’b0;
if (|req)
    gnt = f_compute_grant (req);
    no_conflict: assert final ($onehot0(gnt));
end

3.2.4 BOOLEAN BUILDING BLOCKS

在我们结束对立即断言的讨论之前,我们应该检查SystemVerilog语言提供的各种布尔建模块。这些模块不仅仅用于断言,而且是一种有用的简写方式,可以帮助我们更清晰、更简洁地描述常见情况下的断言。它们特别有用的是,它们可以同时应用于立即断言和并发断言。一些常见的Boolean Building Blocks例子:

Systemverilog断言介绍(三)_第1张图片

3.2.5 CONCURRENT ASSERTION BASICS AND CLOCKING

编写的大多数FPV目的的断言将是并发断言。这些断言支持对时钟和重置的清晰规定,并能够检查值随时间变化的行为。下面是一个并发断言的示例,用于检查在复位外的任何上升沿时钟clk上,有一个可接受的非NOP操作码:

safe_opcode: assert property (
    @(posedge clk)
    disable iff (rst)
    (opcode inside {FORCE0,FORCE1,FORCE2,FORCE3,ACCESS_OFF,ACCESS_ON}))
    else $error("Illegal opcode.");

从这个例子中,可以看到并发断言的语法与即时断言有几个明显的区别:• 使用关键字assert property声明断言语句。• 包含一个可选的时钟规范,指定相关的边沿。• 使用关键字disable iff包含一个可选的复位规范。

从功能上讲,该断言与类似的即时断言之间的关键区别在于,该断言仅在给定的时钟边沿进行检查,并且在其复位(disable iff)条件下会被忽略。由于并发断言可以定义自己的时钟和复位,所以通常最合理的做法是在过程化代码之外使用并发断言,与always块分开。你也可以将它们包含在过程化代码中,但我们建议避免这种用法,因为它可能会在断言和过程之间引入复杂的时序关系。

3.2.6 SAMPLING AND ASSERTION CLOCKING

并发断言对其参数的采样值进行操作。基本上,在任何模拟时间步长中,每个变量的采样值是该变量在上一个模拟时间步长结束时获得的值。下面示例一个信号sig1,其值在不同的时间发生变化,并且有一个并发断言:

sig1_off: assert property (@(posedge clk1) !sig1);

Systemverilog断言介绍(三)_第2张图片

乍一看,报告的断言值可能看起来很奇怪,部分人可能会认为只要sig1为false,断言就应该简单地为true。但实际上,断言结果仅在时钟的每个上升沿计算,它们始终检查在前一个时间步骤结束之前、就在该上升沿之前采样的值。所以看到的结果表明:

  • 在第4个阶段,sig1刚刚变为0,但在clk1上升沿之前它是1,所以断言使用了1的值并且失败了。

  • 在第6个阶段,sig1刚刚变为1,但在clk1上升沿之前它是0,所以断言使用了0的值并且通过了。

  • 在第8个阶段,sig1在时钟边沿之前和之后都是1,所以断言使用了1的值并且失败了。

  • 在第10个阶段,sig1在上升时钟边沿之前很长时间变为0,所以断言使用了0的值并且通过了。

只要所有并发断言中使用的数值的采样原则保持一致,其逻辑定义的任何失败都将按预期行为执行,只要失败的数值在相关时钟边沿之前可见。许多EDA工具会在其输出中对这种固有的偏差进行补偿,因此使用的特定工具可能会或可能不会以这种方式在信号转变后报告失败。但如果它们符合语言的规则,它们将始终使用这些采样值来计算断言结果。如果某些EDA工具中并发断言失败时间的报告似乎比导致失败的数值晚了一个周期,不要担心。这是SVA采样定义的结果。

如果将并发断言放置在过程性代码中,或者包含使用未进行时钟控制的元素(例如软件调用),并创建可以将采样值和非采样值合并在一起的情况时,这种采样行为可能会导致麻烦。这也是建议将并发断言放置在过程性代码之外的另一个原因。

3.2.7 SAMPLED VALUE FUNCTIONS

作为构建并发断言的有用构建模块,SVA还提供了一组采样值函数,这些内置函数与并发断言以相同的方式操作采样值。例如,假设我们想确保每当没有授权时,上一个周期也没有请求。我们可以这样写断言:

no_grant_ok: assert property (@(posedge clk) (|gnt) || !$past(|req));

这个断言检查了期望的条件。请求(req)和授权(gnt)的值都是在时钟(clk)的上升沿之前采样的,并且$past函数检查其参数的值比之前一个周期。这些函数都继承了它们使用的断言语句、序列或属性的时钟,尽管它们也可以传递明确的时钟参数。一些常见的函数如下:

Systemverilog断言介绍(三)_第3张图片

在使用这些函数时需要注意的一点是X或Z值的问题。许多现代仿真器支持包含这些值的四值运行,而且某些FV工具已经添加了这个功能。从X到1的变化被视为$rose的上升沿,从X到0的变化被视为$fell的下降沿。如果您的验证环境允许这些值,并且您不希望从X/Z到计数为上升沿或下降沿,您可能应该使用序列符号,而不是使用简写函数。

3.2.8 CONCURRENT ASSERTION CLOCK EDGES

并发断言中使用的时钟是一个重要的考虑因素。这个时钟用一个时钟说明符来表示,例如@(posedge clk1)。时钟表达式决定了哪些值会被采样,并且如果使用错误的时钟可能会导致意外的行为。常见的错误是省略边沿,使用类似@(clk1)这样的表达式:这会使断言在正负时钟边沿上起作用,从而实际上是检查每个相位而不是每个周期。对于基于锁存器的设计,这可能有时是真正的用户意图,但更常见的是确实需要posedge。如果将一个慢时钟传递给断言,它可能会忽略只持续一个较快时钟相位的短期值。如果使用采样值函数(如$past),或者使用序列延迟,选择不同的时钟实际上可以改变计数的周期数。

在下图中,我们可以看到使用相同逻辑表达式!sig1,但传入不同时钟时断言的不同行为。

Systemverilog断言介绍(三)_第4张图片

check_posedge: assert property (@(posedge clk1) !sig1);
check_anyedge: assert property (@(clk1) !sig1);
check_posedge4: assert property (@(posedge clk4) !sig1);

特别是,我们看到:• 在第4相位,check_posedge和check_anyedge断言已经检测到了sig1的零值,但是check_posedge4断言却没有,因为我们还没有clk4的正边沿。• 在第7相位,check_anyedge断言已经检测到了sig1的单相脉冲,但其他两个断言都没有,因为这个脉冲没有持续到任何正边沿。• 在第14相位,我们看到所有三个断言都通过了,因为sig1在两个时钟的正边沿之前有一个值为0。

因此需要仔细思考断言所需的适当时钟,并确保在适用的情况下包含posedge或negedge修饰符。不然可能会导致断言出现异常的行为。

3.2.9 CONCURRENT ASSERTION RESET (DISABLE) CONDITIONS

除了时钟之外,并发断言还允许使用disable iff来指定复位条件。这里的主要目标是在复位期间关闭断言检查:在大多数现代设计中,可以预期在复位时设计中存在一些任意的“垃圾”值,可能不希望处理由于这些任意值而导致的无效断言失败的噪声。因此,只要其禁用条件为真,断言被认为是轻而易举通过的。

断言复位的一个复杂性在于它们是过去几个部分所述的采样行为的一个例外:断言复位是异步的,只要满足其禁用条件,就会立即关闭断言。这导致的后果是不应该尝试在禁用子句中插入杂散逻辑:因为它的时序与其余部分不同,很可能会得到令人困惑和意外的结果。例如,考虑以下两个断言,都试图说明在复位之外,如果至少有一个授权,你应该有一个请求:

bad_assert: assert property (@(posedge clk)
   disable iff
        (real_rst || ($countones(gnt) == 0))
   ($countones(req) > 0));
good_assert: assert property (@(posedge clk)
   disable iff (real_rst)
   (($countones(req) > 0) ||
        ($countones(gnt) == 0)));

乍一看,这两个似乎在逻辑上是等价的:两者都是断言,表示你必须要么没有任何授权,要么必须至少有一个请求当前处于高电平。但是因为bad_assert将gnt信号放在禁用条件中,而该条件不会被采样,实际上它会在req值之前一个周期查看gnt值,从而导致在下图所示的波形中漏掉一个故障发生。

Systemverilog断言介绍(三)_第5张图片

在上图中,第6个阶段明显存在一个没有请求的授权问题。断言good_assert会在第8个阶段报告这个故障,使用的是在最近与clk1上升沿相关的采样值。

除第6个阶段外,断言bad_assert在每个阶段都被禁用。在第8个阶段上升沿之前的采样值显示为|req false,但是由于禁用子句,当前的gnt值(使用而不是采样值)禁用了对这个断言的检查。

在SVA断言的重置(仅在禁用的情况下)中,只使用实际的重置:全局关闭模型的主要部分长时间的信号。在重置项中使用杂项逻辑可能会由于异步行为而导致混乱的结果。

3.2.10 SETTING DEFAULT CLOCK AND RESET

good_assert: assert property @(posedge clk)
    disable iff (rst)
    (|req || !(|gnt)) else $error(“Bad gnt.”);
safe_opcode: assert property (
    @(posedge clk)
    disable iff (rst)
    (opcode inside {FORCE0,FORCE1,FORCE2,FORCE3,ACCESS_OFF,ACCESS_ON}))
    else $error(“Illegal opcode.”);

SVA提供了一种在模块中为并发断言设置全局时钟和重置的方法,因此它们可以被写一次并覆盖多个断言。可以通过default clocking语句声明默认时钟,并通过default disable iff语句声明默认复位。如果声明了这些默认值,它们将适用于当前模块中的所有断言语句,除了那些明确包含不同时钟或重置的语句。因此,下面的代码等同于上面的两个断言:

default clocking @(posedge clk); endclocking
default disable iff (rst);
good_assert: assert property
    (|req || !(|gnt)) else $error(“Bad gnt.”);
safe_opcode: assert property (
    (opcode inside {FORCE0,FORCE1,FORCE2,FORCE3,ACCESS_OFF,ACCESS_ON}))
    else $error(“Illegal opcode.”);

默认时钟要求有一个endclocking,而默认禁用iff则不需要。在通常情况下,如果有许多使用相同时钟和复位的断言,则在每个包含断言的模块中声明这些默认值可以显著提高断言代码的可读性,并防止由于对时钟和复位的误用而产生的错误。



原创 Junxiao Zhang 芯片验证笔记 

你可能感兴趣的:(Systemverilog,fpga开发,systemverilog断言)