注册 登录
cpubbs论坛 返回首页

cpubbs的个人空间 http://www.cpubbs.com/bbs/?19 [收藏] [复制] [分享] [RSS]

日志

[文档].艾米电子 - 阻塞赋值与非阻塞赋值,Verilog

热度 2已有 610 次阅读2011-6-10 14:09

 
说明

翻译自:FPGA Prototyping By Verilog Examples: Xilinx Spartan-3 Version的第7章第一节

 

内容 阻塞赋值VS非阻塞赋值

有两种赋值语句被用在always块内:阻塞赋值与非阻塞赋值。关于阻塞与非阻塞复制有3条简单的准则:

  • 将电路分为两部分:寄存器电路和组合电路
  • 在寄存器电路中使用非阻塞赋值
  • 在组合电路中使用阻塞赋值

 

1 概览

阻塞赋值 基本语法如下:

1 [var] = [expression];

当该条语句被执行时,右手边的表达式将被赋给左手边的变量,期间不允许其他语句的干扰。因此,就阻塞了其他语句,直到该条语句执行完毕为止。阻塞赋值的行为与C语言中的变量赋值类似。

 

非阻塞赋值 基本语法如下:

1 [var] <= [expression];

非阻塞赋值的行为非常令人难以琢磨。当always块被激活(在time step的开始),右手边的表达式被赋初值。当运行到always块的结尾(即time step的结尾),运算所得的值被赋给左手边的变量。

以x变量执行非阻塞赋值为例。因为Verilog模型的实际流程比较复杂,我们将非阻塞赋值的行为翻译成一下几个步骤:

  • 在alway块的开始,x值传递给x_entry;
  • 右手边的变量x的值被x_entry取代;
  • 左手边变量x的值被x_exit取代;
  • 在always块的结束,x_exit的值传递给x。

 

在下面的代码片段内,上述四个步骤被呈现在代码的注释中。

1 always@*
2 begin               // x_entry = x
3   y <= x & ...      // y       = x_entry & ...
4   x <= ...          // x_exit  = ...
5 end                 // x       = x_exit

 

范例 为了了解阻塞赋值和非阻塞赋值的区别,我们用三输入的的电路来做讨论。

代码1 使用阻塞赋值的电路

01 module and_blocking
02 (
03   input      a,
04   input      b,
05   input      c,
06   output reg y
07 );  
08   
09 always@*
10 begin
11   y = a;
12   y = y & b;
13   y = y & c;
14 end
15   
16 endmodule

阻塞赋值的欣慰类似于C语言中的顺序赋值。y最终得到的值为a & b & c。注意,此代码仅用于示范,使用顺序语义学来描述电路是比较差劲的行为。

 

下面给出的代码,其中的阻塞赋值被替换为非阻塞赋值。代码注释详细说明了y的赋值动作。

代码2 使用非阻塞赋值的电路

01 module and_nonblocking
02 (
03   input      a,
04   input      b,
05   input      c,
06   output reg y
07 );  
08   
09 always@*
10 begin               // y_entry = y
11   y <= a;           // y_exit  = a
12   y <= y & b;       // y_exit  = y_entry & b
13   y <= y & c;       // y_exit  = y_entry & c
14 end                 // y       = y_exit
15   
16 endmodule

注意always块内的前2条语句将不会产生任何效果。上述always块等价与:

1 always@*        
2   y <= y & c;

 


路过

雷人

握手

鲜花

鸡蛋

发表评论 评论 (3 个评论)

回复 cpubbs 2011-6-10 14:09
2 组合电路
上一个小节的范例属于极端的情况。除了缺省值,大部分的组合电路并不会多次赋值同一变量。阻塞赋值和非阻塞赋值都可以用于描述同一电路。然而,它们有一些微妙的区别。下面的范例用于解释这些不同。让我们以一位同或(异或非)电路为例。我们将详细列出敏感列表中的变量。

代码3 使用阻塞赋值的一位同或电路

view sourceprint?
01 module eq1_blocking  

02 (  

03   input      i0,  

04   input      i1,  

05   output reg eq  

06 );   

07   

08 reg p0, p1;  

09   

10 always@(i1, i2)     // 只有i0和i1在敏感列表  

11 begin               // 语句的顺序非常重要  

12   p0 = ~i0 & i1;  

13   p1 = i0 & i1;  

14   eq = p0 | p1;  

15 end

16   

17 endmodule
注意到敏感列表仅包括i0和i1,。当其中之一变化时,always块被激活,p0、p1和ep被顺序运算,ep在第一个time step的结尾被更新。语句的顺序非常重要。假设性我们移动最后面的语句到最前面。

view sourceprint?
1 always@(i1, i2)      

2 begin               

3   eq = p0 | p1;  

4   p0 = ~i0 & i1;  

5   p1 = i0 & i1;  

6 end
在第一条语句中,由于p0和p1还没有被指定新的值,因此先前被激活的值将会被用到。而先前的值将意味着锁存器的存在,故此代码是不正确的。



下面将使用非阻塞赋值替换阻塞赋值。

代码4 使用非阻塞赋值的一位同或电路

view sourceprint?01 module eq1_nonblocking  

02 (  

03   input      i0,  

04   input      i1,  

05   output reg eq  

06 );   

07   

08 reg p0, p1;  

09   

10 always@(i1, i2, p0, p1)       // p0、p1也在敏感列表中   

11                               // 语句的顺序不重要        

12 begin                         // p0_entry = p0; p1_entry = p1               

13   p0 <= ~i0 & i1;             // p0_exit = ~i0 & ~i1  

14   p1 <= i0 & i1;              // p1_exit = i0 & i1  

15   eq <= p0 | p1;              // eq_exit = p0_entry | p1_entry  

16 end                           // eq = eq_exit; p0 = p0_exit; p1 = p1_exit                           

17   

18 endmodule
注意p0和p1也包括在敏感列表中。当i0或i1变化时,always块被激活;在第一个time step的结尾,p0和p1被赋以新值。既然ep取决于p0和p1(p0_entry和p1_entry)的旧值,那么其值在第一个time step的结尾保持不变。当当前的time step执行完毕,always块重新被激活,因为p0和p1发生了变化(这便于为何p0和p1也要置于敏感列表之中的原因)。注意语句的顺序不影响结果。
回复 cpubbs 2011-6-10 14:10
3 存储单元
使用非阻塞赋值来引用存储器。例如,D触发器:

view sourceprint?
1 always@(posede clk)  

2   q <= d;
当然也可以用阻塞赋值来引用D触发器,如下:

view sourceprint?
1 always@(posede clk)  

2   q = d;
虽然在单个D-FF情况下面,上面的代码工作正常,但是当多个寄存器互相动作的时候,这里就出现许多微妙的问题。

考虑两个寄存器在每个时钟周期交换数据。使用阻塞赋值,代码为:

view sourceprint?
1 always@(posede clk)  

2   a = b;  

3      

4 always@(posede clk)  

5   b = a;
在clk的上升沿,两个always块都被激活,并行操作。这两个操作应该在同一time step结束。根据Verilog的标准,两个always块的执行可以以任何顺序列入。若第一个always块先执行,则由于阻塞赋值的缘故a立即得到b的值。当第二个always块执行的时候,b得到a的刷新值,及b的原始值,因此b保持不变。类似的,若第二个always块先执行,a得到的也是其初始值。这就是Verilog中的竞争冒险(race condition)。从Verilog的角度看,两种结果都是有效的。

下面我们修改代码中的阻塞赋值为非阻塞赋值。

view sourceprint?1 always@(posede clk)  

2 begin        // b_entry = b  

3   a <= b;    // a_exit  = b_entry  

4 end          // a       = a_exit  

5      

6 always@(posede clk)  

7 begin        // a_entry = a  

8   b <= a;    // b_exit  = a_entry  

9 end          // b       = b_exit
通过注释我们看到,因为输入(entry)值都被用于赋值,所以无论执行的顺序,a和b都得到正确的值。

因此为了避免竞争冒险,我们使用非阻塞赋值来引用D-FF和触发器。
回复 cpubbs 2011-6-10 14:10
4 混用阻塞和非阻塞赋值的时序电路
在同一个always块内,有可能混用阻塞赋值和非阻塞赋值。下面我们使用简单的例程来解释不同组合的行为,以加强对赋值的理解。



图4.1 通过混合赋值来引用电路



考虑图4.1(b)所示的原理图。当时钟上升沿来临之时,a、b与运算所得的值被存入D-FF。基于前面的讲解,我们可以将存储和组合电路分配到两段代码中。如代码4.1所示。

代码4.1 两段实现

view sourceprint?
01 module ab_dff_2seg  

02 (  

03   input clk,  

04   input a,  

05   input b,  

06   output reg q  

07 );  

08   

09 reg q_next;  

10   

11 // D-FF  

12 always@(posedge clk)  

13   q <= q_next;  

14   

15 // 组合电路  

16 always@*  

17   q_next = a & b;  

18   

19 endmodule


我们可以变换一下,将两段组合在一起,使用单个always块来描述电路。下面通过六次尝试,来描述阻塞和非阻塞赋值的不同组合的区别。如代码4.2所示。

代码4.2 混合赋值例程

view sourceprint?
01 module ab_dff_mix  

02 (  

03   input clk,  

04   input a,  

05   input b,  

06   output reg q0,  

07   output reg q1,  

08   output reg q2,  

09   output reg q3,  

10   output reg q4,  

11   output reg q5  

12 );  

13   

14 reg ab0, ab1, ab2, ab3, ab4, ab5;  

15   

16 // 尝试0  

17 always@(posedge clk)  

18 begin

19   ab0 = a & b;  

20   q0 <= ab0;  

21 end

22   

23 // 尝试1  

24 always@(posedge clk)  

25 begin                   // ab1_entry = ab1; q1_entry = q1  

26   ab1 <= a & b;         // ab1_exit = a & b  

27   q1  <= ab1;           // q1_exit = ab1_entry  

28 end                     // ab1 = ab1_exit; q1 = q1_exit  

29   

30 // 尝试2  

31 always@(posedge clk)  

32 begin

33   ab2 = a & b;  

34   q2  = ab2;  

35 end

36   

37 // 尝试3(调换尝试1的顺序)  

38 always@(posedge clk)  

39 begin

40   q0 <= ab0;  

41   ab0 = a & b;  

42 end

43   

44 // 尝试4(调换尝试2的顺序)  

45 always@(posedge clk)  

46 begin                   // ab4_entry = ab4; q4_entry = q4  

47   q4  <= ab4;           // q4_exit = ab4_entry  

48   ab4 <= a & b;         // ab4_exit = a&b  

49 end                     // ab4 = ab4_exit; q4 = q4_exite  

50   

51 // 尝试5(调换尝试3的顺序)  

52 always@(posedge clk)  

53 begin

54   q5  = ab5;  

55   ab5 = a & b;  

56 end

57   

58 endmodule
在尝试0中,起初赋值给ab0和q0将引用两个寄存器,一个用于存储寄存器ab0,另一个用于存储寄存器q0。因为ab0在块赋值时被立即更新,所以q0得到了a&b的值。对应的原理图如图4.1(a)所示。由于ab0在always块外没有被使用,因此寄存器ab0的输出就不是必需存在的,即相应的寄存器可以被移除。这样,结果电路就如图4.1(b)所示,也就是所需的电路。
在尝试1中,对ab1使用了非阻塞赋值,对应的阐述写到了注释里面。注意q1得到的是ab1_entry而非ab1_exit。而ab1_entry是先前存储的ab值,即对应一个寄存器的输出。相应的原理图如图4.1(c)所示。一个不确定的输入缓存被引用,同时a&b的值延迟一个时钟周期后才被存储到q1中。

在尝试2中,ab2和q2都是用了阻塞赋值。该代码所引用的电路,与尝试1等同,如图4.1(a)和(b)所示。由于使用阻塞赋值来引用D-FF,有可能产生竞争冒险,因此不推荐使用这种类型的代码。

出于演示的目的,让我们来测试一下调换尝试0、1和2的赋值顺序会发生什么。其结果代码如尝试4、5和6所示。在尝试3中,ab3未更新便被使用,因此q3得到的是先前激活块所产生的值。所引用的电路如4.1(c)所示。而尝试4,交换语句的顺序不影响综合的效果,因此等同于尝试1。尝试5中,由于ab5未更新值便被使用,因此q5得到的寄存器a&b的值,等同于尝试3。

简而言之,只有尝试0描述的电路正确且可靠。在尝试0中,我们可以将ab0移除,合并代码如下:

view sourceprint?
1 // 尝试0  

2 always@(posedge clk)  

3 begin

4   q0 <= a & b;  

5 end


推荐阅读
1 SNUG.Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!

参考
1 Pong P. Chu.FPGA Prototyping By Verilog Examples: Xilinx Spartan-3 Version.Wiley

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

Archiver|手机版|cpubbs论坛. ( 粤ICP备09171248号 )

GMT+8, 2026-9-27 23:16 , Processed in 0.016485 second(s), 8 queries , Gzip On, File On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

返回顶部