Clifford E. Cummings 论文详解(二)Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!

1.0 前言

两个众所周知的 Verilog 逻辑建模编码准则是:

  • 在组合逻辑的 always 块中使用阻塞赋值
  • 在时序逻辑的 always 块中使用非阻塞赋值

但是为什么呢?一般来说,答案与仿真有关。忽略上述准则仍然可以推断出正确的综合逻辑,但综合前仿真可能与综合电路的行为不匹配。

要理解上述准则背后的原因,需要充分了解 Verilog 阻塞和非阻塞赋值的功能和调度。本文将详细介绍阻塞和非阻塞赋值的功能和调度。

本文将使用以下缩写:
RHS:等式右边的表达式或变量可以缩写为 RHS 方程,RHS 表达式或 RHS 变量。
LHS:等式左侧的表达式或变量可以缩写为 LHS 方程、LHS 表达式或 LHS 变量。

2.0 Verilog 竞争条件

IEEE Verilog 标准定义了:哪些语句具有确定的执行顺序(“Determinism”,第5.4.1节),哪些语句没有确定的执行顺序(“Nondeterminism”,第5.4.2节和“Race conditions”,第5.5节)。

当两个或多个语句计划在相同的仿真时间步执行时,如果按照 IEEE Verilog 标准的允许,改变语句的执行顺序,则会产生不同的结果,就会出现 Verilog 竞争条件。

为了避免竞争条件,理解 Verilog 阻塞和非阻塞赋值的调度是很重要的。

3.0 阻塞赋值

阻塞赋值操作符是一个等号(“=”)。阻塞赋值之所以得名,是因为阻塞赋值必须计算 RHS 参数并在不受任何其他 Verilog 语句中断的情况下完成赋值。赋值被称为“阻塞”其他赋值,直到当前赋值完成。唯一的例外是在阻塞操作符的 RHS 上有时间延迟的阻塞赋值,这被认为是一种糟糕的编码风格。

阻塞赋值的执行可以看作是一个一步的过程:

  1. 在不被任何其他 Verilog 语句中断的情况下,计算 RHS(右侧方程)并更新阻塞赋值的 LHS(左侧表达式)。

一个阻塞赋值“阻塞”了在同一个 always 中尾随赋值的发生,直到当前赋值完成。

当一个程序块中的一个赋值的 RHS 变量也是另一个程序块中的另一个赋值的 LHS 变量,并且两个方程被安排在相同的仿真时间步中执行时,例如在相同的时钟边缘上,就会出现阻塞赋值的问题。如果阻塞赋值没有正确排序,就会出现竞争条件。当阻塞赋值被安排在同一时间步执行时,执行顺序是未知的。

为了说明这一点,请看 Example 1 中的 Verilog 代码。
在这里插入图片描述
根据 IEEE Verilog 标准,这两个 always 块可以按任意顺序调度。如果第一个 always 块在复位后首先执行,则 y1 和 y2 的值都将为 1。如果第二个 always 块在复位后首先执行,则 y1 和 y2 的值都为 0。这清楚地表示了 Verilog 竞争条件。

4.0 非阻塞赋值

非阻塞赋值操作符与小于等于操作符(“<=”)相同。非阻塞赋值之所以得名,是因为该赋值在时间步开始时计算非阻塞语句的 RHS 表达式,并将 LHS 更新安排在时间步结束时进行。在求值 RHS 表达式和更新 LHS 表达式之间,可以求值和更新其他 Verilog 语句,也可以求值其他 Verilog 非阻塞赋值的 RHS 表达式并安排 LHS 更新。非阻塞赋值不会阻止其他 Verilog 语句被求值。

非阻塞赋值的执行可以看作是一个两步的过程:

  1. 在时间步的开始处计算非阻塞语句的 RHS。
  2. 在时间步结束时更新非阻塞语句的 LHS。

非阻塞赋值仅用于寄存器数据类型,因此只允许在过程块(例如 initial 块和 always 块)内部进行。在连续赋值中不允许非阻塞赋值。

为了说明这一点,请看 Example 12 中的 Verilog 代码。
在这里插入图片描述
同样,根据 IEEE Verilog 标准,这两个 always 块可以按照任何顺序进行调度。无论复位后哪个 always 块首先执行,两个非阻塞的 RHS 表达式都将在时间步的开始时计算,然后在同一时间步的结束时更新两个非阻塞的 LHS 变量。从用户的角度来看,这两个非阻塞语句的执行是并行的。

5.0 Verilog 编码指南

在给出阻塞和非阻塞赋值的进一步解释和示例之前,有必要概述八条指导原则,这些指导原则有助于准确地仿真使用 Verilog 建模的硬件。遵守这些导原则也将消除大多数 Verilog 设计师遇到的 90-100% 的 Verilog 竞争条件。

准则 #1:在对时序逻辑建模时,使用非阻塞赋值。
准则 #2:在对锁存器建模时,使用非阻塞赋值。
准则 #3:在使用 always 块对组合逻辑建模时,使用阻塞赋值。
准则 #4:在同一个 always 块中对时序逻辑和组合逻辑建模时,使用非阻塞赋值。
准则 #5:不要在同一个 always 块中混合使用阻塞和非阻塞赋值。
准则 #6:不要在多个 always 块中对同一个变量赋值。
准则 #7:使用 $strobe 来显示使用非阻塞赋值方式赋值的值。
准则 #8:不要使用 #0 延迟来进行赋值。

本文的其余部分给出了这些指导原则的原因。Verilog 的新设计师被鼓励记住和使用这些指导方针,直到完全理解他们的基本功能。遵循这些指导方针将有助于避免“死于 Verilog!”

6.0 Verilog “分层事件队列”

检查 Verilog 的“分层事件队列”(见图 1)有助于解释 Verilog 阻塞和非阻塞赋值是如何起作用的。“分层事件队列”是用于调度仿真事件的不同 Verilog 事件队列的一个奇特名称。

IEEE Verilog 标准中描述的“分层事件队列”是一个概念模型。每个供应商实现事件队列的具体方式是专有的,这有助于确定每个供应商的仿真器的效率,本文不详细介绍。

按照 IEEE 1364-1995 Verilog 标准 5.3 节的定义,“分层事件队列”在逻辑上被划分为四个不同的队列,用于当前仿真时间,以及用于未来仿真时间的附加队列。
在这里插入图片描述
活动事件队列是安排大多数 Verilog 事件的地方,包括阻塞赋值、连续赋值、$display 命令、计算实例和原语输入,然后更新原语和实例输出、以及计算非阻塞 RHS 表达式。非阻塞赋值的 LHS 不会在活动事件队列中更新。

事件可以添加到任何事件队列中(在 IEEE 标准规定的限制范围内),但只能从活动事件队列中删除。在其他事件队列上调度的事件最终将被“激活”,或提升到活动事件队列。IEEE 1364-1995 Verilog 标准的 5.4 节列出了一个描述其他事件队列何时被“激活”的算法。

当前仿真时间中另外两个常用的事件队列是非阻塞赋值更新事件队列和监视事件队列,它们将在下面进行描述。

非阻塞赋值更新事件队列是调度对非阻塞赋值的 LHS 表达式的更新的地方。RHS 表达式在仿真时间步的开始与上面描述的其他活动事件一起以随机顺序计算。

监视事件队列是调度 $ strobe 和 $ monitor 显示命令值的地方。$ strobe 和 $ monitor 在仿真时间步结束时,在该仿真时间步的所有其他赋值完成之后,显示所有请求变量的更新值。

Verilog 标准第 5.3 节中描述的第四个事件队列是非活动事件队列,其中调度 #0 延迟的任务。#0 延迟赋值的做法通常是一种有缺陷的做法,设计者试图在两个独立的过程块中对同一个变量进行赋值,试图通过将其中一个赋值在相同的仿真时间步中稍晚地进行来击败 Verilog 竞争条件。向 Verilog 模型添加 #0 延迟赋值会不可避免地使计划事件的分析复杂化。作者知道没有任何条件需要进行 #0 延迟赋值,而这些赋值不能轻易地被另一种更有效的编码风格所取代,因此不鼓励这种做法。

准则 #8:不要使用 #0 延迟来进行赋值。

图 1 中的“分层事件队列”将经常被引用来解释本文后面展示的 Verilog 代码示例的行为。还将引用事件队列来证明第 5.0 节中给出的 8 个编码准则的正确性。

7.0 自触发 always 块

通常,Verilog always 块不能自己触发。考虑Example 3 中的振荡器例子。这个振荡器使用阻塞赋值。阻塞赋值评估它们的 RHS 表达并不间断地更新它们的 LHS 值。阻塞赋值必须在 @(clk) 边缘触发事件被调度之前完成。到调度触发事件时,阻塞时钟分配已经完成;因此,在 always 块中没有触发事件来触发 @(clk) 触发器。
在这里插入图片描述
相反,Example 4 中的振荡器使用非阻塞赋值。在第一个 @(clk) 触发之后,计算非阻塞赋值的 RHS 表达式,并将 LHS 值调度到非阻塞赋值更新事件队列中。在非阻塞赋值事件队列被“激活”之前,会遇到 @(clk) 触发器语句,并且 always 块再次对 clk 信号的变化变得敏感。当稍后在同一时间步中更新非阻塞 LHS 值时,@(clk) 将再次触发。osc2 的例子是自触发的(不一定是推荐的编码风格)。
在这里插入图片描述

8.0 流水线建模

图 2 显示了一个简单时序流水线寄存器的框图。Example 5 - Example 8 展示了工程师可能选择的四种不同的方法,即使用阻塞赋值对流水线进行建模。
在这里插入图片描述
在 Example 5 代码的 pipeb1 中,顺序排列的阻塞赋值将导致输入值 d 被放置在下一个 posedge clk 的每个寄存器的输出上。在每个时钟边缘,输入值直接传递到 q3 输出,没有延迟。这显然不是一个流水线寄存器的模型,实际上将综合成一个单一的寄存器!(参见图 3)。
在这里插入图片描述
在 pipeb2 示例中,阻塞赋值已被仔细排序,以使仿真正确地表现为流水线寄存器。该模型综合成图 2 中所示的流水线寄存器。
在这里插入图片描述
在 pipeb3 示例中,阻塞赋值被分割成单独的 always 块。Verilog 允许以任何顺序仿真 always 块,这可能导致该流水线仿真出错。这是 Verilog 的竞争条件!以不同的顺序执行 always 块会产生不同的结果。然而,Verilog 代码将综合到正确的流水线寄存器。这意味着在综合前和综合后的仿真之间可能存在不匹配。pipeb4 示例或相同 always 块语句的任何其他排序也将综合为正确的寄存器逻辑,但可能无法正确仿真。
在这里插入图片描述
如果用非阻塞赋值重写四个阻塞赋值示例中的每一个,则每个示例都将正确地仿真并综合成所需的流水线逻辑。
在这里插入图片描述
在这里插入图片描述
通过对本节所示的流水线编码风格进行检查:

  • 4种阻塞赋值编码风格中有1种保证正确仿真
  • 4种阻塞赋值编码风格中有3种保证正确综合
  • 4种非阻塞赋值编码风格中有4种保证正确仿真
  • 4种非阻塞赋值编码风格中有4种保证正确综合

虽然,如果限制在一个具有仔细排序赋值的 always 块中,可以使用阻塞赋值对流水线逻辑进行编码。但是,使用非阻塞赋值很容易编写相同的流水线逻辑。事实上,非阻塞赋值编码风格更适用于综合和仿真。

9.0 阻塞赋值 & 简单示例

有许多 Verilog 和 Verilog 综合书籍展示了使用阻塞赋值成功编码的简单顺序示例。Example 13 显示了在大多数 Verilog 教科书中出现的触发器模型。
在这里插入图片描述
如果工程师愿意将所有模块限制为单个 always 块,则可以使用阻塞赋值来正确建模,仿真和综合所需的逻辑。不幸的是,这种习惯导致了在其他更复杂的时序 always 块中放置阻塞赋值,这些 always 块将表现出本文已经详细介绍的竞争条件。
在这里插入图片描述
最好养成习惯,使用非阻塞赋值编码所有时序 always 块,甚至是简单的单块模块,如 Example 14 所示。

现在考虑一个更复杂的时序逻辑,线性反馈移位寄存器或 LFSR。

10.0 时序反馈建模

线性反馈移位寄存器(LFSR)是一个带有反馈回路的时序逻辑。反馈回路给试图用 Example 15 中所示的正确排序的阻塞赋值来编码这段时序逻辑的工程师带来了一个问题。
在这里插入图片描述
除非使用临时变量,否则无法对 Example 15 中的赋值进行排序来模拟反馈循环。

可以将所有赋值分组到单行方程中,以避免使用临时变量,如 Example 16 所示,但是代码现在更加含义模糊了。对于较大的示例,单行方程可能变得非常难以编码和调试。并不鼓励单行方程的编码风格。
在这里插入图片描述
如果将 Example 15 和 Example 16 中的阻塞赋值替换为 Example 17 和 Example 18 中所示的非阻塞赋值,则所有仿真都将按照 LFSR 的预期运行。
在这里插入图片描述
在这里插入图片描述
基于第 8.0 节的流水线示例和第 10.0 节的 LFSR 示例,建议使用非阻塞赋值对所有时序逻辑建模。类似的分析表明,对锁存器建模使用非阻塞赋值也是最安全的。

准则 #1:在对时序逻辑建模时,使用非阻塞赋值。
准则 #2:在对锁存器建模时,使用非阻塞赋值。

11.0 组合逻辑 - 使用阻塞赋值

使用 Verilog 编码组合逻辑的方法有很多,但是当使用 always 块编码组合逻辑时,应该使用阻塞赋值。

如果在 always 块中只进行了一个赋值,使用阻塞或非阻塞赋值都可以工作;但是为了养成良好的编码习惯,应该总是使用阻塞赋值来编码组合逻辑。

一些 Verilog 设计者建议,非阻塞赋值不仅应该用于编码时序逻辑,也应该用于编码组合逻辑。对于编写简单的组合逻辑 always 块,这是可行的,但是如果 always 块中包含多个赋值,例如 Example 19 中所示的 and-or 代码,使用没有延迟的非阻塞赋值要么会仿真错误,要么需要额外的灵敏度列表条目和多次遍历 always 块才能正确仿真。从仿真时间的角度来看,后者是低效的。

Example 19 中显示的代码从三个顺序执行的语句构建 y 输出。由于非阻塞赋值在更新 LHS 变量之前对 RHS 表达式求值,因此 tmp1 和 tmp2 的值是这两个变量在进入该 always 块时的初始值,而不是在仿真时间步结束时更新的值。y 输出将反映 tmp1 和 tmp2 的旧值,而不是在 always 块的当前传递中计算的值。
在这里插入图片描述
Example 20 中所示的代码与 Example 19 中所示的代码相同,只是将 tmp1 和 tmp2 添加到灵敏度列表中。如 7.0 节所述,当非阻塞赋值更新事件队列中的 LHS 变量时,always 块将自动触发并使用新计算的 tmp1 和 tmp2 值更新 y 输出。经过两次遍历 always 块后,y 输出值现在将是正确的。多次遍历 always 块等同于降低仿真性能,如果存在合理的替代方案,则应避免。
在这里插入图片描述
一个不需要多次遍历 always 块的更好的习惯是,只在编写用于建模组合逻辑的 always 块中使用阻塞赋值。
在这里插入图片描述
Example 21 中的代码与 Example 19 中的代码相同,除了非阻塞赋值已被阻塞赋值所取代,这将保证 y 输出仅在一次通过 always 块后就会生成正确的值;因此有以下指导方针:

准则 #3:在使用 always 块对组合逻辑建模时,使用阻塞赋值。

12.0 混合时序和组合逻辑 - 使用非阻塞赋值

有时将简单的组合逻辑方程与时序逻辑方程归为一类是很方便的。当将组合代码和时序代码组合成单个 always 块时,将 always 块编码为具有非阻塞赋值的时序 always 块,如 Example 22 所示。
在这里插入图片描述
Example 22 中实现的相同逻辑也可以实现为两个独立的 always 块,一个是用阻塞赋值编码的纯组合逻辑,另一个是用非阻塞赋值编码的纯时序逻辑,如 Example 23 所示。
在这里插入图片描述
准则 #4:在同一个 always 块中对时序逻辑和组合逻辑建模时,使用非阻塞赋值。

13.0 其他混合阻塞和非阻塞赋值准则

Verilog 允许阻塞和非阻塞赋值在 always 块中自由混合。一般来说,在同一个 always 块中混合阻塞和非阻塞赋值是一种糟糕的编码风格,即使 Verilog 允许这样做。

Example 24 中的代码将正确地仿真和综合,因为阻塞赋值与非阻塞赋值不是对同一个变量进行的。虽然这可以工作,但作者不鼓励使用这种编码风格。
在这里插入图片描述
Example 25 中的代码在大多数情况下很可能会正确地仿真,但是综合工具会报告语法错误,因为阻塞赋值与非阻塞赋值被赋给了相同的变量。这个代码必须经过修改才能综合。
在这里插入图片描述
为了养成良好的编码习惯,作者鼓励遵循以下准则:

准则 #5:不要在同一个 always 块中混合使用阻塞和非阻塞赋值。

14.0 对同一变量的多次赋值

从多个 always 块中对同一个变量进行多次赋值是 Verilog 的竞争条件,即使使用非阻塞赋值也是如此。

在 Example 26 中,两个 always 块对 q 输出进行赋值,它们都使用非阻塞赋值。由于这些 always 块可以按顺序调度,因此仿真输出是一个竞争条件。
在这里插入图片描述
当综合工具读取这种类型的编码示例时,会发出以下警告消息:
在这里插入图片描述
当忽略警告并编译示 Example 26 中的代码时,将生成两个输出为双输入和门的触发器。在这个例子中,综合前仿真甚至不能与综合后仿真相匹配。

准则 #6:不要在多个 always 块中对同一个变量赋值。

15.0 常见的非阻塞误解

15.1 非阻塞赋值和 $display

  • 误解:对非阻塞赋值使用 $display 命令会不起作用
  • 事实:非阻塞赋值会在所有 $display 命令后更新

在这里插入图片描述
上述仿真的输出如下所示,在执行非阻塞赋值更新事件之前,在活动事件队列中先执行了 $display 命令。
在这里插入图片描述

15.2 #0 延迟赋值

  • 误解:#0 强制赋值到时间步的末尾
  • 事实:#0 强制赋值到“非活动事件队列”

在这里插入图片描述
上述仿真的输出如下所示,在执行非阻塞赋值更新事件之前,在非活动事件队列中执行了 #0 延迟命令。
在这里插入图片描述

准则 #7:使用 $strobe 来显示使用非阻塞赋值方式赋值的值。

15.3 对同一变量的多个非阻塞赋值

  • 误解:对同一个 always 块中的同一个变量进行多个非阻塞赋值是未定义的。
  • 事实:Verilog 标准定义了对同一个 always 块中的同一个变量进行多个非阻塞赋值。对同一变量的最后一次非阻塞赋值有效!引用自 IEEE 1364-1995 Verilog Standard [2], pg. 47, section 5.4.1 - Determinism

非阻塞赋值应按照语句执行的顺序执行。考虑下面的例子:
在这里插入图片描述
当执行此块时,将有两个事件添加到非阻塞赋值更新队列中。前一条规则要求它们按原顺序进入队列;该规则要求将它们从队列中取出,并按照原顺序执行。因此,在时间步 1 结束时,变量 a 将被先赋值为 0,然后赋值为 1。

参考资料

https://www.researchgate.net/profile/Clifford-Cummings

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值