源文件作者:Clifford E. Cummings (Sunburst Design, Inc.)
原标题:Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!
ATTN:所有括号内“外注”为理解方便或有疑问的地方,原文里并没有。所有翻译都是为遵循“共同进步”的理想但并没有去努力得到原作者的任何书面和其它方式许可,所以仅供大家参考。本人英文和设计水平都极有限,所以不能保证与原文的精确一致和正确(只能以“驽马十步”稍作安慰吧),惭愧之后还希望大家多指教!
在 Verilog 语言最难弄明白的结构中“非阻塞赋值”要算一个。甚至是一些很有经验的工程师也不完全明白“非阻塞赋值”在仿真器(符合 IEEE 标准的)里是怎样被设定执行的,以及什么时候该用“非阻塞赋值”。这篇文章将介绍怎样设定“非阻塞赋值”和“阻塞赋值”,给出了重要的使得编码可以被正确地综合的编码指导方针,和避免仿真竞争的编码风格细节。
1.0 介绍
众所周知的逻辑建模方针是:
* 在 always 块里用“阻塞赋值=”产生组合逻辑。
* 在 always 块里用“非阻塞赋值<=”产生时序逻辑。
但是为什么?(外注:在实现组合逻辑的 assign 结构中,当然采用阻塞赋值语句否则的话编译工具会提醒你进行修改的。)
普通的回答是:那只是关于仿真的,即使不遵照上面的规则也照样可以产生正确的综合结果。但问题是综合前的仿真结果也许会跟综合后的电路行为仿真不匹配。
要明白上述建模方针背后的原因,就必须明白“非阻塞赋值”和“阻塞赋值”它们的功能和时序安排(the functionality and scheduling of blocking and nonblocking assignments.)。这篇文章将详细描述有关问题。文章里将用到两个缩写形式:RHS(right-hand-side)和LHS
(left-hand-side)。前者指等式右边的表达式或者变量(RHS expression or RHS variable),后者指指等式左边的表达式或者变量(RHS expression or RHS variable)。
2.0 Verilog 仿真竞争条件
IEEE Verilog Standard [2] 定义: “保证性的赋值描述”和“非保证性的赋值”描述分别用“非阻塞赋值”和“阻塞赋值”。("Determinism", section 5.4.1;"Nondeterminism", section 5.4.2 & "Race conditions", section 5.5)
IEEE Verilog 标准允许在同一仿真时间里赋值竞争的产生。当赋值陈述有所不同时,会产生不同的结果。(译注:即可以认为:“非阻塞赋值”有更高的优先权对变量进行赋值或者是指陈述的次序不同会产生不同结果?)
为了避免含竞争的描述(race condition),明白Verilog“非阻塞赋值”和“阻塞赋值”
的时序安排是非常重要的。
3.0 阻塞赋值(blocking assignments)
阻塞赋值由等号“=”表示。“阻塞赋值”由它的赋值操作行为而得名:当没有其它的Verilog描述可以打断“阻塞赋值”时,操作将会估计RHS的值并完成赋值。“阻塞”即是说在当前的赋值完成前阻塞其它类型的赋值任务。一个例外是:对阻塞操作的RHS进行延时(delays)的阻塞赋值(在延时未完成前不会阻塞其它赋值任务),但是这被我们认为是不好的编码方式。
“阻塞赋值“可以看作一步进程(one-step process):
当没有其它可以打断赋值的描述时,估计等式右边(RHS)的指并赋予左边(LHS)。
在同一个always块里面,阻塞赋值结果将一直持续下去直到赋值结束。
阻塞赋值的一个问题是:当一个程序块(比如always块) 阻塞赋值描述里面的 “RHS变量” 同时是另外一个程序块(比如always块) 阻塞赋值描述里面的 “LHS变量”,并且两个等式的执行被安排在同一个仿真时间步里面执行(比如同一个时钟上升沿),那么竞争条件就产生了,这样的情况下其执行次序将是未知的。
为了举例说明这种情况,请看Verilog代码描述的例一:
module fbosc1 (y1, y2, clk, rst);
output y1, y2;
input clk, rst;
reg y1, y2;
always @(posedge clk or posedge rst)
if (rst) y1 = 0; // reset
else y1 = y2;
always @(posedge clk or posedge rst)
if (rst) y2 = 1; // preset
else y2 = y1;
endmodule
Example 1 - Feedback oscillator with blocking assignments
依据IEEE Verilog标准,这两个always块可以以任意的次序执行。如果在reset后第一个块先被执行,结果将是y1和y2都获得赋值1;如果在reset后第二个块先被执行,结果将是y1和y2都被赋值0。这个例子清楚地展示了一个Verilog竞争条件地产生。
4.0 非阻塞赋值(nonblocking assignments)
非阻塞赋值使用一个小于等于号“<=”。“非阻塞赋值”由它的赋值操作行为而得名:在一个时间步(time step)的开始估计RHS expression的值并在这个时间步(time step)结束时用等式右边的值更新取代LHS。在估算RHS expression和更新LHS expression的中间时间
段,其它的对LHS expression的非阻塞赋值可以被执行。即是说“非阻塞赋值”从估计RHS开始并不阻碍执行其它的Verilog描述。
“非阻塞赋值”可以看作二步进程(one-step process):
1. 在时间步开始估计RHS;
2. 在时间步结束时更新LHS;
“非阻塞赋值”为寄存器数据类型而设,所以只能被允许在程序块里面出现,比如initial块和always块。不允许持续性赋值(continuous assignments)。
为了举例说明,请看Verilog 编码例二:
module fbosc2 (y1, y2, clk, rst);
output y1, y2;
input clk, rst;
reg y1, y2;
always @(posedge clk or posedge rst)
if (rst) y1 <= 0; // reset
else y1 <= y2;
always @(posedge clk or posedge rst)
if (rst) y2 <= 1; // preset
else y2 <= y1;
endmodule
Example 2 - Feedback oscillator with nonblocking assignments
依据IEEE Verilog标准,这两个块可以以任意的次序执行。在reset后,不管哪一个块先被执行,在时间步的开始两个RHS expression同时被估值,在时间步结束LHS variables 同时更新赋值。在使用者看来,这两个非阻塞描述是并行发生的。
5.0 Verilog 编码指导仿真
在对“非阻塞赋值”和“阻塞赋值”作更深一步的举例和说明之前,现列举八条指导方针是有帮助的。这些仿真可以帮助正确地用Verilog对硬件建模和仿真。谨遵这些方针可以帮助Verilog设计者减少所遇到的90-100%的Verilog竞争。
#1: 当为时序逻辑建模,使用“非阻塞赋值”。
#2: 当为锁存器(latch)建模,使用“非阻塞赋值”。
#3: 当用always块为组合逻辑建模,使用“阻塞赋值”
#4: 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻塞赋值”。
#5: 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。
#6: 不要在两个或两个以上always块里面对同一个变量进行赋值。
#7: 使用$strobe以显示已被“非阻塞赋值”的值。
#8: 不要使用#0延迟的赋值。
关于这些指导方针的来源,这篇文章的余下部分将会给出。Verilog的新手们一定要记住并使用这些方针直到完全弄明白了它们根本的功能。遵循这些方针将会帮助你避免“Verilog痛苦”(“death by Verilog!”)。
6.0 层积事件列 ("stratified event queue")
仔细地考察一下Verilog的层积事件列(stratified event queue,见表一)可以帮助解释Verilog的层积事件列是如何发挥作用的。对于用于安排仿真事件顺序的不同Verilog事件列,“层积事件列”是一个迷人的和有想象力的名字。在IEEE Verilog标准里被描述成一种概念上的模范------用于鉴定各个供应商的仿真器能力,尽管它们各自对事件列的执行细节是它们各自所独有的。这些细节问题不是本章所要讨论的。
(外注:首先一个“事件轴”可以用来理解仿真事件:
)。
在IEEE 1364-1995 Verilog标准的5.3节,“层积事件列”被划分为四个迥然不同的列,它们分别归为当前仿真时间列和将来仿真时间列。
“激活事件列”(Active Events)是最多的被预备执行的Verilog事件,包括非阻塞赋值、连续赋值、$display命令、利用对实例(instance)和初原元件(primitive,可能这样翻不合适,但是知道Verilog的家伙都知道这个,就先将就着了!)更新的输出值估出
实例(instance)和初原元件(primitive)的输入值、估出“非阻塞赋值”的RHS expressions。注意“非阻塞赋值”的LHS不在“激活事件列”里更新值。
事件可以被加到任意的事件列里(由IEEE标准强制约束的)但是只可能从“激活事件列”里被移出。其它事件列里的事件最终总是要成为“激活事件”的(或者提升为“激活事件)。IEEE1364-1995 Verilog标准第5.4节列出了当其它事件列(event queues)被激活后的算法。
在当前仿真时间里,两个常见的事件列(event queues)是“非阻塞赋值更新”事件列和“monitor”事件列。具体描述如下:
“非阻塞赋值更新”事件列(The nonblocking assign updates event queue)即是“非阻塞赋值”的LHS expression被安排更新赋值 的那些事件。在一个仿真时间步(simulation time step)的开始,“RHS expression 的估值”与其它被激活事件 是以任意的次序进行的。
“monitor”事件列 是由那些被安排的“$strobe”和“$monitor”显示命令带来的。$strobe 和 $monitor 用于显示一个仿真时间步结束时变量更新后的值(这时该仿真时间步里所有的赋值分配都已经完成)。
IEEE1364-1995 Verilog标准第5.3节描述了“怠惰事件列”,即被赋为零延迟(#0)的事件。实际上“零延迟”是有缺陷的。一般设计者使用零延迟是想为在不同程序块(procedural blocks)被赋值的变量提供一个避免仿真竞争的环境。设计者希望一个赋值语句在另一个之后“一点儿”替代前面赋值。这是没必要的,只会增加(仿真器)分析事件列的难度。作者不知道有哪一种情况下必须要使用零延迟以至于不用零延时就不能用别的不同的、更有效的编码风格来达到所想的目的。所以不推荐使用零延迟。
建模方针8:不要使用零延迟。
上面图一的“层积事件列”将经常参考以用来解释下面的Verilog行为描述例子。
“事件列”也将作为证明5.0节列举的八条建模方针的参考。
7.0 自触发always块
一般来讲,一个always块不能够自触发。考虑下面例三的振荡器:
module osc1 (clk);
output clk;
reg clk;
initial #10 clk = 0;
always @(clk) #10 clk = ~clk;
endmodule
Example 3 - Non-self-triggering oscillator using blocking assignments
这个振荡器使用“阻塞赋值”,这样的话RHS估值和LHS赋值是不被打断地执行。在clk边沿触发能被安排执行之前,非阻塞赋值就已经必须安排执行。即在边沿事件之前,对clk的赋值已经完成。所以, 没有“触发事件”(@(clk))来触发always块里面的触发事件(to trigger the @(clk) trigger)。
与之形成对比的是,例4的振荡器使用“非阻塞赋值”:
module osc2 (clk);
output clk;
reg clk;
initial #10 clk = 0;
always @(clk) #10 clk <= ~clk;
endmodule
Example 4 - Self-triggering oscillator using nonblocking assignments
在第一个@(clk)触发之后,非阻塞赋值的RHS expression被估值,并且LHS值被送入“非阻塞赋值更新”事件列。在“非阻塞赋值更新事件列”被激活以前,仿真过程遇到@clk触发描述,所以always块又一次对clk信号变化敏感------然后在同一时间步的结束当LHS被更新时,@clk被又一次触发。所以osc2是可以自触发的(尽管不是我们有必要推荐的风格)。
(外注:这个另外添加的仿真波形可以帮助理解,原文里并没有。其中clk_reg表示寄存在内存的clk值。)
8.0 流水线建模
图二示意了一个简单的时序(sequential)流水线寄存器。
从例5到例8列举了一个工程师可能选用的4种使用阻塞赋值为它建模的方案。
module pipeb1 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q1 = d;
q2 = q1;
q3 = q2;
end
endmodule
Example 5 - Bad blocking-assignment sequential coding style #1
(外注:综合报告:WARNING: Signal is assigned but never used.
WARNING:Signal is assigned but never used.)
在例5里面,接连的“阻塞赋值”命令将使得输入D连续地覆盖所有寄存器输出(在下一个posedge clk到来时)。即在每一个clk边沿,输入值被无延迟地传到q3的输出。这很明显并没有建立一个流水线而只是为一个寄存器建模------实际综合结果将是上面的图3。
module pipeb2 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q3 = q2;
q2 = q1;注意次序的更改!
q1 = d;
end
endmodule
Example 6 - Bad blocking-assignment sequential coding style #2 - but it works!
上面的pipeb2里面,阻塞赋值被仔细地安排了次序以使得行为仿真正确。这种建模同样也可以得到正确的综合结果。
(外注:Found 3-bit shift register for signal .
Summary: inferred 8 Shift register(s). )
在下面的例3里,“阻塞赋值”被安排在不同的always块里面。这样Verilog标准允许以任意的次序 来仿真执行3个always块-------这也许会使得该流水线仿真结果产生错误,因为这产生了Verilog竞争条件。由不同的always块执行顺序会产生不同的结果。尽管这样,它的综合结果将是正确的! 这就意味着综合前仿真和综合后仿真不匹配。Pipeb4或者其它的类似always块同样也许会产生仿真与综合不匹配的结果------综合结果是对的,但是仿真结果也许不正确。(外注:pipeb4只是又颠倒了一下次序,对实际仿真次序却不产生决定作用.)
module pipeb3 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q1=d;
always @(posedge clk) q2=q1;
always @(posedge clk) q3=q2;
endmodule
Example 7 - Bad blocking-assignment sequential coding style #3
module pipeb4 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q2=q1;
always @(posedge clk) q3=q2;
always @(posedge clk) q1=d;
endmodule
Example 8 - Bad blocking-assignment sequential coding style #4
假如每一个上面的例子都改用“非阻塞赋值”那么将会都能得到正确的仿真结果,并综合出想要的流水线逻辑。
module pipen1 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q1 <= d;
q2 <= q1;
q3 <= q2;
end
endmodule
Example 9 - Good nonblocking-assignment sequential coding style #1
module pipen2 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q3 <= q2;
q2 <= q1;
q1 <= d;
end
endmodule
Example 10 - Good nonblocking-assignment sequential coding style #2
module pipen3 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q1<=d;
always @(posedge clk) q2<=q1;
always @(posedge clk) q3<=q2;
endmodule
Example 11 - Good nonblocking-assignment sequential coding style #3
module pipen4 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q2<=q1;
always @(posedge clk) q3<=q2;
always @(posedge clk) q1<=d;
endmodule
Example 12 - Good nonblocking-assignment sequential coding style #4
从上面的流水线编码风格例子可以看出:
仅一个“阻塞赋值”的描述可以保证仿真正确。 三个“阻塞赋值”的描述可以得到正确综合结果。 四个“非阻塞赋值”描述都可以保证仿真结果正确。 四个“非阻塞赋值”描述都可以得到正确综合结果。(原文这一条是“阻塞赋值”大概是有误?)
虽然,如果限制在一个always块里面,并小心地组织好一个always块里面阻塞赋值的次序(外注:一个always块里面的几个“阻塞赋值”是按照陈述的次序串行仿真执行的,综合执行次序也是?)同样可能会正确地为流水线建模;但是另一方面,我们可以很容易地使用
“非阻塞赋值”来为上面的流水线建模------它们既可以正确仿真也可以正确综合。9.0 阻塞赋值 & 简单例子
有许多将Verilog和Verilog综合的书,它们举了很多成功地利用“阻塞赋值”为一些简单的时序电路建模的小例子。例13是一个在大多数Verilog书本里用来为一个触发器(flip-flop)建模的例子(这是简单而有缺陷的阻塞赋值建模,但是它确实可以工作):
module dffb (q, d, clk, rst);
output q;
input d, clk, rst;
reg q;
always @(posedge clk)
if (rst) q = 1'b0;
else q = d;
endmodule
Example 13 - Simple flawed blocking-assignment D-flipflop model - but it works!
如果工程师们想把所有的模块(module)都集中到一个always里面描述,“阻塞赋值”可以用来正确地为所需要的逻辑建模、仿真和综合。但是不幸的是这个原因导致了喜欢在其它情况下也使用“阻塞赋值”的习惯,并且更复杂的时序always块将会产生竞争条件------在前面已经详细阐述过。
module dffx (q, d, clk, rst);
output q;
input d, clk, rst;
reg q;
always @(posedge clk)
if (rst) q <= 1'b0;
else q <= d;
endmodule
Example 14 - Preferred D-flipflop coding style with nonblocking assignments
应该努力养成使用“非阻塞赋值”为 所有的 时序逻辑建模的习惯------象上面的例14一样------即使是为了对付任何一个简单的模块。
下面考虑一下一个稍微复杂的时序逻辑,一个线性反馈移位寄存器(Linear Feedback shift-Register)或称之为LFSR。
10.0 为时序反馈建模 (Sequential feedback modeling)
一个LFSR是一种带反馈环路(feedback loop)的时序逻辑。反馈环路(feedback loop)为工程师们带来了一个难题使得他们试图使用细心组织次序的“阻塞赋值”来为它正确建模,如下面的例子:
module lfsrb1 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
wire n1;
assign n1 = q1 ^ q3;
always @(posedge clk or negedge pre_n)
if (!pre_n) begin
q3 = 1'b1;
q2 = 1'b1;
q1 = 1'b1;
end
else begin
q3 = q2;
q2 = n1;
q1 = q3;
end
endmodule
Example 15 - Non-functional LFSR with blocking assignments
(外注:综合报告―――>
Register equivalent to has been removed
Found 1-bit register for signal .
Found 1-bit xor2 for signal .
Found 1-bit register for signal .)
Summary:
inferred 2 D-type flip-flop(s).
没有办法通过调整描述次序的方法来正确建模除非引入一个临时的变量(
外注:例如引入“wire n2”――>
module xxxxx (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
wire n1,n2;
assign n1 = q1 ^ q3;
assign n2 = q3;
always @(posedge clk or negedge pre_n)
if (!pre_n) begin
q3 = 1'b1;
q2 = 1'b1;
q1 = 1'b1;
end
else begin
q3 = q2;
q2 = n1;
q1 = n2;
end
endmodule
这样可以得到正确的综合结果:
Found 1-bit register for signal .
Found 1-bit xor2 for signal .
Found 1-bit register for signal .
Found 1-bit register for signal .
Summary:
inferred 3 D-type flip-flop(s).)。
可以通过把所有赋值弄到一个等式的方式(one-line equations)来避免使用临时变量,例如下面的例16所示。但是现在编码显得更难于理解尤其当涉及的表达式更大更长时,编写代码和调试都变得比较困难,因此不鼓励使用这种风格。
module lfsrb2 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
always @(posedge clk or negedge pre_n)
if (!pre_n) {q3,q2,q1} = 3'b111;
else {q3,q2,q1} = {q2,(q1^q3),q3};
endmodule
Example 16 - Functional but cryptic LFSR with blocking assignments
如果把例15和例16的阻塞赋值(blocking assignment)都替换为非阻塞赋值(nonblocking assignment),如下面例17和18所示,那么所有的仿真都将如我们对一个LFSR所期望的那样。
module lfsrn1 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
wire n1;
assign n1 = q1 ^ q3;
always @(posedge clk or negedge pre_n)
if (!pre_n) begin
q3 <= 1'b1;
q2 <= 1'b1;
q1 <= 1'b1;
end
else begin
q3 <= q2;
q2 <= n1;
q1 <= q3;
end
endmodule
Example 17 - Functional LFSR with nonblocking assignments
module lfsrn2 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
always @(posedge clk or negedge pre_n)
if (!pre_n) {q3,q2,q1} <= 3'b111;
else {q3,q2,q1} <= {q2,(q1^q3),q3};
endmodule
Example 18 - Functional but cryptic LFSR with nonblocking assignments
根据8.0段例子pipeline和10.0段例子LFSR,我们推荐对所有时序逻辑建模时使用非阻塞赋值(nonblocking assignment)。相似的分析也将显示出对latch建模时使用非阻塞赋值(nonblocking assignment)是最安全的。
#1: 当为时序逻辑建模,使用“非阻塞赋值”。
#2: 当为锁存器(latch)建模,使用“非阻塞赋值”。
11.0 组合逻辑―使用阻塞赋值(blocking assignment)
用Verilog可以有很多种方法为组合逻辑建模,但是当使用always块来为组合逻辑建模时,应该使用阻塞赋值(blocking assignment)。
如果在某个always块里面只有一个赋值(表达),那么使用阻塞或者非阻塞赋值都可以正确工作。但是如果您对养成好的编码习惯有兴趣的话,还是要“总是用阻塞赋值对组合逻辑建模”。
一些设计师建议非阻塞赋值不应该只为编写时序逻辑,它也可以用来编写组合逻辑。当然对于简单的组合逻辑always块这是可以的,但是对于在一个always块里面含有多个赋值陈述,例如例19含有and-or的陈述,使用了不含延迟(delay)的非阻塞赋值会造成仿真不正确,
或者要使仿真正确您需要另外的添加敏感事件列表(sensitivity list entries),和“多登入路径”(multiple passes)来贯穿always 块以使得仿真正确。接下来的问题是从仿真需要多长时间来看,这是低效率的(外注:即降低仿真的performance)。
例19的y输出建立在3个依次执行的陈述上(外注:tmp1 <= a & b; tmp2 <= c & d; y <= tmp1 | tmp2;)。由于非阻塞赋值的LHS变量值更新是在对RHS表达式估值之后,所以tmp1和tmp2的值仍然是该always块上一个登入口的值而不是在这一个仿真时间步(simulation time step)结束时被更新的值。因此y的值将受旧的tmp1和tmp2影响,而不是这次扫描过的always块内被更新的值。
module ao4 (y, a, b, c, d);
output y;
input a, b, c, d;
reg y, tmp1, tmp2;
always @(a or b or c or d) begin
tmp1 <= a & b;
tmp2 <= c & d;
y <= tmp1 | tmp2;
end
endmodule
Example 19 - Bad combinational logic coding style using nonblocking assignments
例20与例19是一样的,不同之处在于tmp1和tmp2被添加到事件列表中去了。如第7段(section 7.0)中所述,在“非阻塞赋值更新事件队列”中当非阻塞赋值更新LHS变量时,always块将会“自触发”并使用最新的tmp1和tmp2来更新y输出。现在y输出值正确了因为增加使用了两条“登入路径”(two passes)贯穿整个always块。使用更多的“登入路径”来贯穿always块等于降低仿真器的性能,因此如果可以有合理的一些代码变化可以取代这种用法的话,就尽量避免这种用法。
module ao5 (y, a, b, c, d);
output y;
input a, b, c, d;
reg y, tmp1, tmp2;
always @(a or b or c or d or tmp1 or tmp2) begin
tmp1 <= a & b;
tmp2 <= c & d;
y <= tmp1 | tmp2;
end
endmodule
Example 20 - Inefficient multi-pass combinational logic coding style with nonblocking assignments
发展一个好的习惯可以避免使用“多登入路径”(multiple passes)贯穿always块,即使用阻塞赋值为组合逻辑建模。
module ao2 (y, a, b, c, d);
output y;
input a, b, c, d;
reg y, tmp1, tmp2;
always @(a or b or c or d) begin
tmp1 = a & b;
tmp2 = c & d;
y = tmp1 | tmp2;
end
endmodule
Example 21 - Efficient combinational logic coding style using blocking assignments
例21与例19一样,不同之处只在于用阻塞赋值替代了非阻塞赋值。这保证了在一个“登入路径”贯穿always后y输出的正确(guarantee that the y-output assumes the correct value after only one pass through the always block?)。因此有下面的编码方针:
#3: 当用always块为组合逻辑建模,使用“阻塞赋值”
12.0 时序-组合混合逻辑建模:使用非阻塞赋值
很多时候为了方便我们把时序和一些简单的组合逻辑放在一起。当我们把时序和组合编码放在一个always块的时候,像编写时序逻辑一样使用非阻塞赋值为这种混合逻辑建模,如下面的例22:
module nbex2 (q, a, b, clk, rst_n);
output q;
input clk, rst_n;
input a, b;
reg q;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= a ^ b;
endmodule
Example 22 - Combinational and sequential logic in a single always block
与例22相同的逻辑也可以使用两个分立的always块------一个是纯粹的时序逻辑(使用非阻塞赋值),另一个是纯粹的组合逻辑(使用阻塞赋值)------建模,例如下面的例23:
module nbex1 (q, a, b, clk, rst_n);
output q;
input clk, rst_n;
input a, b;
reg q, y;
always @(a or b)
y = a ^ b;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= y;
endmodule
Example 23 - Combinational and sequential logic separated into two always blocks
#4: 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻塞赋值”。
13.0 其它混合“阻塞”与“非阻塞”赋值建模方针
Verilog允许在一个always块里面自由混合“阻塞”与“非阻塞”赋值。一般情况下在同一个always块里面混合“阻塞”与“非阻塞”赋值是“衰婆”风格(poor coding style,呵呵,借用电影《钢琴教师》里的翻译“衰婆”,刚好poor发音与“婆”有些相近。不过可能引
起大家一阵反胃,女士们一阵痛恨------向导演,可不要向我!),尽管Verilog允许这样做。
下面的例24的仿真和综合都将是正确的,因为“阻塞”与“非阻塞”赋值不是针对同一个变量来的。尽管这可以“正常工作”,但是作者不推荐这种风格。
module ba_nba2 (q, a, b, clk, rst_n);
output q;
input a, b, rst_n;
input clk;
reg q;
always @(posedge clk or negedge rst_n) begin: ff
reg tmp;
if (!rst_n) q <= 1'b0;
else begin
tmp = a & b;
q <= tmp;
end
end
endmodule
Example 24 - Blocking and nonblocking assignment in the same always block - generally a bad idea!
下面的例25在大多数情况下仿真是正确的,但是新思(Synopsys)工具会报告语法错误因为针对同一个既进行了“阻塞赋值”又进行了“非阻塞赋值”。这样的编码必须进行修改才可以综合。(Error:Cannot mix blocking and non blocking assignments on signal .)
module ba_nba6 (q, a, b, clk, rst_n);
output q;
input a, b, rst_n;
input clk;
reg q, tmp;
always @(posedge clk or negedge rst_n)
if (!rst_n) q = 1'b0; // blocking assignment to "q"
else begin
tmp = a & b;
q <= tmp; // nonblocking assignment to "q"
end
endmodule
Example 25 - Synthesis syntax error - blocking and nonblocking assignment to the same variable
为了养成好的编写习惯,作者推荐始终坚持:
#5: 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。
14.0 对同一变量多处赋值(Multiple assignments to the same variable)
对同一变量在二个以上(包括二个)always块里面进行赋值就是一种Verilog竞争生成环境------即使使用非阻塞赋值。
在下面例26里,二个always块对q进行赋值,同时使用非阻塞赋值。因为这些always块可以以同一次序安排执行,仿真输出呈竞争条件。
module badcode1 (q, d1, d2, clk, rst_n);
output q;
input d1, d2, clk, rst_n;
reg q;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= d1;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= d2;
endmodule
Example 26 - Race condition coding style using nonblocking assignments
当新思(Synopsys)工具读这段编码时,会产生如下警告:
Warning: In design 'badcode1', there is 1 multiple-driver net with unknown wired-logic type.
当忽略这个警告并编译上面的例子时,推断结果是二个触发器的输出将作为一个and门的输入。在这个例子里综合前(pre-synthesis)仿真结果与综合后(post-synthesis)仿真结果不匹配。
#6: 不要在两个或两个以上always块里面对同一个变量进行赋值。
15.0 常见的“非阻塞”神话(外注:指与事实不符或严格说不正确的见解、想法)
15.1 非阻塞赋值和$display
神话:“对‘非阻塞赋值’使用$display命令不起作用。”
事实:非阻塞赋值在$display命令之后才被更新赋值。
module display_cmds;
reg a;
initial $monitor("\$monitor: a = %b", a);
initial begin
$strobe ("\$strobe : a = %b", a);
a = 0;
a <= 1;
$display ("\$display: a = %b", a);
#1 $finish;
end
endmodule
下面的仿真输出结果显示出$display命令在“激活事件列”(the active event queue)里被执行的情形:在“非阻塞赋值更新”这个事件被执行之前。
$display: a = 0
$monitor: a = 1
$strobe : a = 1
15.2 赋“零延迟”
神话:“‘零延迟’#0 使得该赋值事件在时间步结束时发生”
事实:零延迟’#0 使得赋值事件处于“非激活事件列”
module nb_schedule1;
reg a, b;
initial begin
a = 0;
b = 1;
a <= b;
b <= a;
$monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b);
$display ("%0dns: \$display: a=%b b=%b", $stime, a, b);
$strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b);
#0 $display ("%0dns: #0 : a=%b b=%b", $stime, a, b);
#1 $monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b);
$display ("%0dns: \$display: a=%b b=%b", $stime, a, b);
$strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b);
$display ("%0dns: #0 : a=%b b=%b", $stime, a, b);
#1 $finish;
end
endmodule
下面的仿真输出结果显示出$display命令在“非激活事件列”(the inactive event queue)里被执行的情形:在“非阻塞赋值更新”这个事件被执行之前。
0ns: $display: a=0 b=1
0ns: #0 : a=0 b=1
0ns: $monitor: a=1 b=0
0ns: $strobe : a=1 b=0
1ns: $display: a=1 b=0
1ns: #0 : a=1 b=0
1ns: $monitor: a=1 b=0
1ns: $strobe : a=1 b=0
#7: 使用$strobe以显示已被“非阻塞赋值”的值。
15.3 对同一变量多处进行“非阻塞赋值”
神话:“‘在同一个always块里对同一变量多处进行非阻塞赋值’ 没有被明确定义。”
事实:Verilog标准定义了以上操作。最后一个非阻塞赋值操作将赢得最后结果。引用IEEE1364-1995 Verilog Standard [2], pg. 47, section 5.4.1 – Determinism 如下:
“非阻塞赋值由它们被陈述的次序决定被执行的情况,考虑下面的例子:
initial begin
a <= 0;
a <= 1;
end
When this block is executed, there will be two events added to the nonblocking assign update queue. The previous rule requires that they be entered on the queue in source order; this rule requires that they be taken from the queue and performed in source order as well. Hence, at the end of time-step 1, the variable a will be assigned 0 and then 1."
换句话说:“最后一个非阻塞赋值操作将赢得优先权。”
指导方针和结论(概要):
#1: 当为时序逻辑建模,使用“非阻塞赋值”。
#2: 当为锁存器(latch)建模,使用“非阻塞赋值”。
#3: 当用always块为组合逻辑建模,使用“阻塞赋值”
#4: 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻塞赋值”。
#5: 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。
#6: 不要在两个或两个以上always块里面对同一个变量进行赋值。
#7: 使用$strobe以显示已被“非阻塞赋值”的值。
#8: 不要使用#0延迟的赋值。
谨遵这些方针可以帮助Verilog设计者减少所遇到的90-100%的Verilog竞争。
16.0 最后注意:“nonblocking”的拼写
“nonblocking”经常被拼错为“non-blocking”。作者认为这是“微软化”的拼写方式。工程师们在“non”和“blocking”之间插入一个“-”是为了满足微软的拼写检查不致报错。在IEEE 1364-1995里正确的拼写应该是:nonblocking。(外注:呵呵,你已经看到了,在这
个文档里面所有的nonblocking都被下划了红色波浪线。)
参考文献:
[1] IEEE P1364.1 Draft Standard For Verilog Register Transfer Level Synthesis
[2] IEEE Standard Hardware Description Language Based on the Verilog Hardware
Description Language, IEEE Computer Society, IEEE Std 1364-1995
[3] Clifford Cummings, "Correct Methods For Adding Delays To Verilog Behavioral
Models," International HDL Conference 1999 Proceedings, pp. 23-29, April 1999.
(外注:以上参考文献原文的。译文中第4页“事件轴”引自中科院计算所张亮编著、人民邮电出版社2000年10月出版的《数字电路设计与Verilog HDL》;一些综合报告来自Xilinx公司的综合软件XST。)