Verilog非阻塞赋值的仿真/综合问题 (Nonblocking Assignments in Verilog Synthesis)上

摘要:
阻塞分配=:非阻塞分配&lt。在分配结构中实现组合逻辑?当然,使用块赋值语句。否则,编译器将提醒您修改:非块赋值,保证赋值描述。第5.5节)IEEE Verilog标准允许在同一模拟时间生成分配竞争;分配优先级较高或语句顺序不同的变量将产生不同的结果。“非阻塞赋值”操作将估计RHS的值并完成赋值
http://hi.baidu.com/hieda/blog/item/4a7f238220e256a60cf4d2c2.html

源文件作者: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都获得赋值0;如果在reset后第二个块先被执行,结果将是y1和y2都被赋值1。这个例子清楚地展示了一个Verilog竞争条件地产生。

4.0    非阻塞赋值(nonblocking assignments)
          非阻塞赋值使用一个小于等于号“<=”。“非阻塞赋值”由它的赋值操作行为而得名:在一个时间步(time step)的开始估计RHS expression的值并在这个时间步(time step)结束时用等式右边的值更新取代LHS。在估算RHS expression和更新LHS expression的中间时间段,其它的对LHS expression的非阻塞赋值可以被执行。即是说“非阻塞赋值”从估计RHS开始并不阻碍执行其它的Verilog描述。


“非阻塞赋值”可以看作二步进程(two-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标准里被描述成一种概念上的模范------用于鉴定各个供应商的仿真器能力,尽管它们各自对事件列的执行细节是它们各自所独有的。这些细节问题不是本章所要讨论的。
                      (外注:首先一个“事件轴”可以用来理解仿真事件:
Verilog非阻塞赋值的仿真/综合问题 (Nonblocking Assignments in Verilog Synthesis)上第1张  
         )。

        在IEEE 1364-1995 Verilog标准的5.3节,“层积事件列”被划分为四个迥然不同的列,它们分别归为当前仿真时间列和将来仿真时间列。
Verilog非阻塞赋值的仿真/综合问题 (Nonblocking Assignments in Verilog Synthesis)上第2张
                      
          “ 激活事件列”(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是可以自触发的(尽管不是我们有必要推荐的风格)。
Verilog非阻塞赋值的仿真/综合问题 (Nonblocking Assignments in Verilog Synthesis)上第3张
(外注:这个另外添加的仿真波形可以帮助理解,原文里并没有。其中clk_reg表示寄存在内存的clk值。)

8.0 流水线建模
         图二示意了一个简单的时序(sequential)流水线寄存器。
Verilog非阻塞赋值的仿真/综合问题 (Nonblocking Assignments in Verilog Synthesis)上第4张  
         从例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

Verilog非阻塞赋值的仿真/综合问题 (Nonblocking Assignments in Verilog Synthesis)上第5张  
(外注:综合报告:WARNING: Signal <q1> is assigned but never used.
                                WARNING:Signal <q2> 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 <q3>.
                 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块里面的几个“阻塞赋值”是按照陈述的次序串行仿真执行的,综合执行次序也是?)同样可能会正确地为流水线建模;但是另一方面,我们可以很容易地使用
“非阻塞赋值”来为上面的流水线建模------它们既可以正确仿真也可以正确综合。

To be continued :   Verilog非阻塞赋值的仿真/综合问题 -下- (Nonblocking Assignments in Verilog Synthesis)

免责声明:文章转载自《Verilog非阻塞赋值的仿真/综合问题 (Nonblocking Assignments in Verilog Synthesis)上》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Vim文本编辑器中常用的一些命令spring boot + vue实现图片上传及展示下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[VC++]一些常用数据类型的使用

我们先定义一些常见类型变量借以说明 int i = 100; long l = 2001; float f=300.2; double d=12345.119; char username[]="bone"; char temp[200]; char *buf; CString str; _variant_t v1; _bstr_t v2; 一、其它数据类...

WMI简介和Event驻留

  WMI (Windows Management Instrumentation,Windows管理规范) 从Windows 2000开始被包含于操作系统后,就一直是Windows操作系统的一部分。这项技术对于系统管理员来说具有巨大价值,因为它提供了提取所有类型信息、配置组件和基于系统数个组件的状态采取行动等方式。由于这种灵活性,且被早早地被包含于操作系...

CSS3之渐变

渐变语法:使用background-image属性进行设置   可以取值: linear-gradient:线性渐变         radial-gradient:径向渐变           repeating-linear-gradient:重复线性渐变         repeating-radial-gradient:重复径向渐变 线性渐变   ...

实战二(上):程序出错该返回啥?NULL、异常、错误码、空对象?

我们可以把函数的运行结果分为两类。一类是预期的结果,也就是函数在正常情况下输出的结果。一类是非预期的结果,也就是函数在异常(或叫出错)情况下输出的结果。比如,在上一节课中,获取本机名的函数,在正常情况下,函数返回字符串格式的本机名;在异常情况下,获取本机名失败,函数返回 UnknownHostException 异常对象。 在正常情况下,函数返回数据的类...

84、智能指针的原理、常用的智能指针及实现

原理 智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源 常用的智能指针 (1) shared_ptr 实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引...

[转]Delphi : keydown与keypress的区别,组合键

Shift 是一个集合变量。 type TShiftState = set of (ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble); 也就是说TShiftstate类型有ssShift, ssAlt, ssCtrl, ssLeft(鼠标左键), ssRight(鼠标右键), ssMi...