Menu Close

阻塞与非阻塞赋值语句深度探讨及使用技巧(2)

在上两节的内容中已经探讨了阻塞赋值语句与非阻塞赋值语句的原理,模型等内容以及注意事项等内容。本节内容根据以前介绍的内容结合设计中普遍存在的几个方面,从设计角度探讨问题发生的原因并给出解决问题的办法,目的通过以一点带面,举一反三的方式全面理解阻塞与非阻塞赋值语句在逻辑及时序电路设计中的应用,提高电路设计技巧。

一、全分支语句结构是好的设计习惯

上节内容提到在计数器设计中可以利用顺序语句的特点,调整语句的前后顺序可以实现相同的目的,但这样调整后阻塞与非阻塞赋值语句的结果表现不一致,需要调整。因此如果想保持设计风格尽量采用全分支结构。

下面的例子中探讨如何避免在组合逻辑设计中避免出现锁存器(latch)的情况。虽然在数字逻辑设计中锁存器也是标准器件如74LS373就是8锁存器器件。但FPGA器件中的标准组件只有查找表(LUT),触发器(D边沿触发器,DFF),选择器(MUX),如果要实现锁存器就要使用D触发器的来实现,此时相当于放弃了D触发器时钟功能。可能会存在如下问题:

  1. 该锁存器可能由于错误代码引起副产品,并不是设计期望的结果。除非有意实现锁存器用于特殊目的。
  2. 锁存器可能引起异步时序电路的综合,生成电路无法进行时序约束。
  3. 锁存器的值与同步时序电路交换数据时,要有二次采样,降低时序电路的效率。
  4. 锁存器对于毛刺等噪声信号比较敏感。

鉴于以上原因,在FPGA或IC的开发中尽量用同步时序电路设计而避免使用latch,做到时序约束与时序收敛都有可靠的保证。这是一般原则,当然的确需要latch时候依然可以做到。

例1 : Latch 电路的生成。



  module latch


  (


  input  a,b,


  input  load,


  output c,


  output d


  );


  always@(*)  begin


  		d<=0;


  if (load) begin


  c<= a & b;


  d<=a|b;


  end


  end


  endmodule 


  

这个电路的确生成锁存器电路,当load为1时,c=a&b;当load不为1时,c将保持原有值不变,即使a,b的值发生变化,c的值依然保持。但是比较c与d的值有何不同呢?c实现锁存效果,而d则是一般组合逻辑电路。综合后的模型如图1。

%title插图%num

图1

实际设计时我们应该需要清楚的知道,什么时候会生成锁存器电路什么时候会生成组合逻辑电路,这一点至关重要。那么是不是非阻塞电路造成的呢?我们修改程序,用阻塞赋值语句实现刚才的代码,部分程序如下:

always@(*) begin

d=0;

if (load) begin

c= a & b;

d=a|b;

end

end

通过分析可以看出,即使修改为阻塞赋值c依然是锁存器,那么这个原因就与不完全的分支结构有关,如果补全分支结构,程序的几种写法如下:

(1)阻塞赋值等效全分支结构

always@(*) begin

d=0;

c=0;

if (load) begin

c= a & b;

d=a|b;

end

end

(2)非阻塞赋值等效分支结构

always@(*) begin

d<=0;

c<=0

if (load) begin

c<= a & b;

d<=a|b;

end

end

  1. 阻塞赋值全分支结构

always@(*) begin

if (load) begin

c= a & b;

d=a|b;

end

else begin

d=0;

c=0;

end

end

  1. 非阻塞赋值全分支结构

always@(*) begin

if (load) begin

c<= a & b;

d<=a|b;

end

else begin

d<=0;

c<=0

end

end

通过以上四种方式都可以实现组合逻辑电路设计。当然上面讨论的前提是敏感表中的敏感量要全,否则依然会生成锁存器。例:

always@(load) begin

if (load) begin

c<= a & b;

d<=a|b;

end

else begin

d<=0;

c<=0

end

end

上例中虽然是完整的分支结构,但由于敏感量不全,最终也生成了锁存器模型。

二、 序列赋值模型与组合逻辑赋值模型

序列赋值建模(sequential assignment modeling)是指在always过程中的赋值中表现为变量的结果总是用表达式中变量的旧值计算后更新被赋值变量。组合逻辑模型是指当always过程进入后表达式中被赋值变量总是用表达式中当前获得的最新变量值计算,并将计算结果赋给变量。

 

虽然阻塞与非阻塞赋值语句都可以设计组合逻辑电路,但在具体表现形式上又有所不同,有的可以生成序列赋值模型,有的生成组合逻辑电路模型,看看下面的例子就可以清楚知道。

例:生成4变量的与或逻辑电路

  1. 用阻塞赋值语句实现

实体程序如下:



  	module block_logic


  	(


  		input 	 [3:0]  a,


  		output 	 reg    y


  	)


  	reg   tmp1,tmp2;


  	always@(a)  begin


  		tmp1=a[0]&a[1];


  		tmp2=a[2]&a[3];


  		y=tmp1|tmp2;


  	end


  	endmodule


                

仿真程序如下:

`timescale 1 ns /1 ps

module tb_non_block

(

);

reg [3:0] a;

wire y;

integer i;

initial begin

a=0;

#10

for(i=0;i<16;i=i+1)

#10 a=a+1;

#10;

end

block_logic block_logic_inst

(

.a (a),

.y (y)

);

endmodule

仿真结果如图2:

%title插图%num

图2

从图2的仿真结果可以看出,阻塞仿真语句的确是完美实现组合逻辑功能。而且从敏感表的枚举可以看出,敏感表中只要给出相对于该过程的输入量即可(always@(a) )。同样的逻辑,用非阻塞语句实现结果是否一致呢?下面我们来测试用非阻塞赋值语句实现的程序。

2. 用非阻塞语句实现与或逻辑

module non_block_logic

(

input [3:0] a,

output reg y

);

reg tmp1, tmp2;

always@(a) begin

tmp1 <= a[0] & a[1];

tmp2 <= a[2] & a[3];

y <= tmp1 | tmp2;

end

endmodule

仿真程序如下:



   `timescale 1 ns  /1 ps


  module tb_block 


  (


  );


  reg  [3:0] a;


  wire       y;


  integer    i; 


    initial begin


  		a=0;


  		#10

  		for(i=0;i<16;i=i+1)


  		#10  a=a+1;


  		#10;


  		


     end


  	


  block_logic  block_logic_inst


  (


   .a	(a),


   .y	(y)


  );


  endmodule


  

仿真结果如图3:

%title插图%num

图3

从图3的显示结果来看,y的结果与组合逻辑电路结果并不一致。这验证了我们上节结果的内容,即非阻塞赋值语句实现的结果只有always过程退出时才会生效,因此体现了一个序列的过程。仔细分析程序会得出如下结论,每一次序列后,新值变成旧值,如表1所示。

表1

a[3:0] tmp1_old tmp1_after tmp2 _old tmp2_after y_old y_after
0010 0 0 0 0 0 0
0011 0 1 0 0 0 1
0100 1 0 0 0 1 0
0101 0 0 0 0 0 0
0110 0 0 0 0 0 0
0111 0 1 0 0 0 1
1000 1 0 0 0 1 0

表1中仅列出了部分的序列状态,在同时用_old和_after表示在进入过程中的前后值,分析语句y=tmp1|tmp2,我们知道非阻塞赋值语句并不阻碍其它赋值语句的执行。如果整个always过程都是非阻塞赋值语句,则对所有的赋值语句可以看作是并发。tmp1,tmp2的值只有退出always过程是才得到,因此用tmp1_after,tmp2_after表示,y此时只能得到tmp1_after|tmp2_after的值。又因为y不在敏感表中,y值的变化不会引起always过程的再次进入,所以y没有办法再次更新,因此出现图3所示的结果。而阻塞语句在退出always过程前都已赋值完毕,因此y总能得到最新值,所以出现图2中的结果。

修改上例非阻塞赋值语句中的敏感列表,程序如下:
module non_block_logic

(

input [3:0] a,

output reg y

);

reg tmp1, tmp2;

always@(a,y) begin

tmp1 <= a[0] & a[1];

tmp2 <= a[2] & a[3];

y <= tmp1 | tmp2;

end

endmodule

或者

module non_block_logic

(

input [3:0] a,

output reg y

);

reg tmp1, tmp2;

always@(*) begin //此处用*代表所有的敏感量

tmp1 <= a[0] & a[1];

tmp2 <= a[2] & a[3];

y <= tmp1 | tmp2;

end

endmodule

经过如上修改,都可以得到图2所示结果。这主要是由于y的变化引起always过程的再次进入,将所有的赋值语句重新执行一遍后的结果。但即使从波形行已经看不出差别,但实际上阻塞赋值语句与非阻塞赋值语句在细节上还有一些细微的差别,如用$display 函数打印,我们会发现非阻塞赋值语句会打印两次或多次的结果,这个问题在上两节内容中已经做过探讨,这里不再累述。但是有一点需要特别引起注意,就是组合逻辑设计的结果又被时序电路使用在阻塞非阻塞方面的差别。

 

总结如下:

  1. 全分支结构可以生成组合逻辑电路或序列赋值,非全分支结构可能会生成锁存器
  2. 全的敏感表可以生成组合逻辑电路,非全的敏感表可能会生成锁存器
  3. 不全的敏感表非阻塞赋值可以生成序列赋值模型,全的敏感表生成组合逻辑电路
  4. 非阻塞赋值语句在组合逻辑设计中具有较强的复杂性,一般在组合逻辑设计中推荐使用阻塞赋值语句。

更多的内容在后续课程中将会继续探讨,敬请关注。

 

Posted in FPGA, FPGA, Quartus II, Verilog, Vivado, 开发工具, 教材与教案, 文章, 编程语言

6 Comments

  1. wangff

    文中提到的“查找表(LUT)”概念,不理解,希望老师可以介绍一下,如果方便的话,可以将“查找表”的电路图附在文章中。

  2. wangff

    本篇文章一方面深入探讨了可能引起锁存器产生的真实原因,通过对阻塞赋值、非阻塞赋值、全分支结构、非全分支结构等四方面的对比验证,得出在“全敏感量”的前提下使用全分支结构可以避免产生锁存器模型的结论;另一方面,介绍了序列赋值模型(使用旧值参与计算)与组合逻辑赋值模型(使用最新变量值参与计算)概念,通过程序验证得出“不全的敏感表非阻塞赋值可以生成序列赋值模型,全的敏感表生成组合逻辑电路”的结论,并建议“在组合逻辑设计中使用阻塞赋值语句”。

    在本文中提到的“时序约束与时序收敛”概念,请老师详细介绍一下,这个两个概念的具体含义?

发表评论

相关链接