Menu Close

Verilog 中阻塞与非阻塞赋值语句的区别

Verilog 中阻塞与非阻塞赋值语句的区别

在Verilog HDL语法中有阻塞赋值语句与非阻塞赋值语句之分,这两种赋值语句在Verilog语言程序设计中应用都比较广泛。这两者在用法上既有类似之处也有区别。下面就这两种赋值语句的定义,区别于联系以及使用中应注意的事项进行详细探讨,同时也注意在探讨过程中对以介绍知识点的回顾。

一、定义描述

  1. 阻塞赋值语句:

阻塞赋值语句用”=” 表示,按照字面理解可以认为该语句在一个过程阻止Verilog后续语句的执行,直到该语句赋值完成。通俗的讲就是该赋值语句在顺序过程中在满足延时条件后立即生效。

例:

reg [3:0] a , b ,c;

initial begin

a=0;

b=0;

c=0;

#10

a=1;

b=a;

c=a+b;

a=2;

c=c+a;

#10;

end

下面我们来分析这个程序的执行结果:根据阻塞赋值语句的描述,变量赋值立刻生效;同时根据顺序语句的特点,程序执行具有顺序性,即语句的执行遵循从前到后,从上到下的特点,每条语句执行后的结果如下,

a=1; //a==1

b=a; //b==1

c=a+b; //c==2

a=2; //a==2

c=c+a; //c==4; 因为在运算前c=2,a=2, 因此 c=2+2=4

可以看出c有两次赋值,结果也不同,分别为2和4, 那么最终结果为多少?我们直到在顺序赋值中,如果多次对同一个变量赋值,该变量的最终结果一定是最后一次生效的赋值。即4是c的最终值。图1是上面语句的仿真结果,从图形中可以看出,中间结果c==2,在图形中并没显示出来,因为从时间的角度看,它们处在同一个时间点,只有最后的结果4,在图形中显示。但中间结果确实参与了运算。

图1

  1. 观察中间过程

如果想观察变量的中间结果可以使用$display函数显示, 可以修改程序如下:



  `timescale 1 ns  /1 ps

  module tb 

  (


  );


  reg   [3:0]   a , b ,c;

    initial begin

  		a=0;

  		b=0;

  		c=0;

  		#10

  		
  		a=1;

  		b=a;

  		c=a+b;

  		$display(" c=  %d " ,c);

  		a=2;

  		c=c+a;

  		$display(" c= %d " ,c);

  		#10;

     end


  endmodule


  

显示波形与图1一致,但在modelsim的transcript窗口中可以清晰看到c的变化过程。如图2,

图2

2. 分析顺序语句的最终结果

从上面的例子中已经可以看出,变量c的最终结果,修改程序如下,从$display和波形图对比可以更好的理解顺序语句的执行结果。



  	`timescale 1 ns  /1 ps


  module tb 


  (


  );


  reg   [3:0]   a , b ,c;

    initial begin

  		a=0;

  		b=0;

  		c=0;

  		#10
		

  		a=1;

  		b=a;

  		c=a+b;


  		$display(" c=  %d " ,c);

  		a=2;

  		c=c+a;

  		$display(" c= %d " ,c);

  		c=a+b;    //a==2, b==1; 因此 c==3

  		$display(" c= %d " ,c);

  		#10;

     end


  endmodule


  

通过已学习过的知识,可以分析出c的最终结果应该为3。 波形图和和$display显示如图3和图4,

图3

图4

3. 非阻塞赋值语句

顾名思义,非阻塞赋值语句就是在赋值时并不阻塞在整个顺过程中其它语句的评估与执行,这一点与阻塞赋值语句不同,在执行结果上也有很大的差别。非阻塞赋值语句用”<=”表示。将上面的例子进行修改如下:

`timescale 1 ns /1 ps

module tb_non_block

(

);

reg [3:0] a , b ,c;

initial begin

a<=0;

b<=0;

c<=0;

#10

a<=1;

b<=a;

c<=a+b;

$display(” c= %d ” ,c);

a<=2;

c<=c+a;

$display(” c= %d ” ,c);

c<=a+b;

b<=6;

$display(” c= %d ” ,c);

#10;

end

endmodule

先尝试分析上面的程序,看看a,b,c三个变量的最终结果。

a<=1; //a==1吗?不一定,由于非阻塞,还要看后面赋值语句是否对a进行修改

b<=a; //b==1吗? 不一定

c<=a+b; //c==2吗?不一定

$display(” c= %d ” ,c); //该如何显示?

a<=2; //a==2吗?,对,后面没有对a修改的赋值语句

c<=c+a; //c等于多少?

$display(” c= %d ” ,c); //该如何显示?

c<=a+b; //c==3吗? 不一定,看后面有没有对a,b的修改

b<=6; //b==6吗? 对,这是对b的最后一次修改。

$display(” c= %d ” ,c); //应该显示 c==8吗?似乎正确

#10;

通过分析,最终a==2,b==6, 那么c==8应该是正确的,然而实际结果c==0; 这个结果出乎意料之外,让人大跌眼镜。下面看看仿真结果。如图5和图6所示。从仿真结果看,a,b的变量值的确与我们分析的一致,但c的值不是8而是0,原因出在哪里?

原因是所有这些赋值语句都是在同一时间点对a, b ,c赋值,在评估所有的赋值语句结束时,a和b最终获得了2和6,然而c在这个时间点上只抓取到a==0,b==0的值进行计算,因此c=0; 如果后续时间点没有给c重新赋值,c的值将维持不变。

图5

图6

修改一下程序可以验证上面的结果。延时10单位重新显示c的值,看看结果。代码与仿真结果如下:

`timescale 1 ns /1 ps

module tb_non_block

(

);

reg [3:0] a , b ,c;

initial begin

a<=0;

b<=0;

c<=0;

#10

a<=1;

b<=a;

c<=a+b;

$display(” c= %d ” ,c);

a<=2;

c<=c+a;

$display(” c= %d ” ,c);

c<=a+b;

b<=6;

$display(” c= %d ” ,c);

#10;

$display(” c= %d ” ,c);

#10;

end

endmodule

图7

图8

从图7,8的显示结果可以看出,C一旦得到了值0,在后续的时间点没有给c继续赋值的话,c==0将保持不变。

继续修改程序,将c重新赋值,观察仿真结果。


  `timescale 1 ns  /1 ps

  module tb_non_block 


  (


  );


  reg   [3:0]   a , b ,c;

    initial begin

  		a<=0;

  		b<=0;

  		c<=0;

  		#10
  		
  		a<=1;

  		b<=a;

  		c<=a+b;

  		$display(" c= %d " ,c);

  		a<=2;

  		c<=c+a;

  		$display(" c= %d " ,c);

  		c<=a+b;

  		b<=6;

  		$display(" c= %d " ,c);

  		#10;

     		$display(" c= %d " ,c);

    		#10;

  		c<=a+b;

     		$display(" c= %d " ,c);

  		#10;

     end


  endmodule

仿真结果如下:

图 9

图10

从图9可以看出,c的值的确在延迟一段时间后变成8了,但紧随其后的$display为什么还显示0呢?这就引入了另一个概念,赋值语句delta延迟的概念。

4. Delta延迟:

Delta延迟的概念解释如下,赋值语句的左边要得到右边表达式的值,一般会有一个delta延迟,这个delta是多大呢?从数学的角度讲,delta可以认为无穷小;从仿真的角度看可以认为仿真精度的最小刻度。如下面的语句,赋值语句“c<=a+b;”和显示$display都在同一时刻评估,赋值语句要等delta时间后才会生效,$display是抓取当前值进行显示,而C当前值值由于并没有更新(delta之后才更新),依然是0,因此$display显示c的值为0就不奇怪了。图9能够显示c==8,那是因为人的眼睛无法分辨delta延迟的原因造成的。

#10;

c<=a+b; //c延迟delta后为8

$display(” c= %d ” ,c);//c的当前值依然为零,

#10;

继续修改程序看看$display的结果。修改后的程序如下:

`timescale 1 ns /1 ps

module tb_non_block ( );

reg [3:0] a , b ,c;

initial begin

a<=0;

b<=0;

c<=0;

#10

a<=1;

b<=a;

c<=a+b;

$display(” c= %d ” ,c);

a<=2;

c<=c+a;

$display(” c= %d ” ,c);

c<=a+b;

b<=6;

$display(” c= %d ” ,c);

#10;

$display(” c= %d ” ,c);

#10

c<=a+b;

$display(” c= %d ” ,c);

#1

$display(” c= %d ” ,c);

#10;

end endmodule

仿真结果如下:

图11

图12

从修改的程序程序上看,仅仅延迟一个单位的刻度,$display函数就能正确显示c的新值8,可见delta延迟的概念的确存在。

5. always过程中如何体现delta过程的。

学过以上概念后,针对非阻塞赋值语句,我们对always过程再探讨,看看有没有新的认识。修改程序如下,

`timescale 1 ns /1 ps

module tb_non_block_always

( );

reg [3:0] a , b ,c;

initial begin

a<=0;

b<=0;

c<=0;

end

always@(*) begin

a<=2;

c<=a+b;

b<=6;

$display(” c= %d ” ,c);

end

endmodule

首先分析一下程序,我们知道always过程由敏感量组成的敏感表做为过程的入口,当任意一个敏感量发生变化时,always过程执行一遍。那么程序第一次如何进入的呢?首先当程序第一次运行时a,b,c的值都默认为’xxxx’,此时执行initial过程,变量a,b,c初始化为0。由x到0的变化,引起always过程的第一次进入。运行完成后a==2,b==6,c==0,引起第二次进入;第二次进入后,a==2,b==6,c==8,此时$display函数显示c==0, 由于c的变化引起第三次进入,变量a,b,c的结果仍然是a==2,b==6,c==8,但此时$display会打印出c=8。之后a,b,c的值保持不变,always过程也就不再进入。仿真结果如下:

图13

如果如下改成多次赋值的程序,

`timescale 1 ns /1 ps

module tb_non_block_always

(

);

reg [3:0] a , b ,c;

initial begin

a<=0;

b<=0;

c<=0;

end

always@(*) begin

#10

a<=1;

b<=a;

c<=a+b;

$display(” c= %d ” ,c);

a<=2;

c<=c+a;

$display(” c= %d ” ,c);

c<=a+b;

b<=6;

$display(” c= %d ” ,c);

end

endmodule

同样三次之后的结果为a==2,b==6, c==8, 但由于中间变量有转换过程,而且变量的结果在退出always过程后才会得到,因此程序会循环进入,这一点在使用时应引起注意。

如果将上面的程序该为阻塞赋值语句可以看出重大差别。程序如下:



  `timescale 1 ns  /1 ps


  module tb_non_block_always 


  (


  );


  reg   [3:0]   a , b ,c;


    initial begin

  		a<=0;

  		b<=0;

  		c<=0;

  	end

  	


  	always@(*)  begin

  	
  		#10	

  		a=1;

  		b=a;

  	        c=a+b;

  		$display(" c= %d " ,c);

  		a=2;

  		c=c+a;

  		$display(" c= %d " ,c);

  		c=a+b;

  		b=6;

  		$display(" c= %d " ,c);

  		#10;	

     end

  endmodule

  [/code]

仿真结果如下:

图14

图15

从图15可以看出,阻塞赋值语句可以立即得到结果,并能用$display函数显示。而且在退出always过程前得到所有值,因此always过程只进一次,没有像非阻塞赋值语句那样循环进入的机会。

总结:

  1. 非阻塞赋值语句有delta延时,被赋值变量在顺序过程退出后才获得赋值结果,而阻塞赋值语句没有delta延迟,赋值结果立马生效。
  2. 由于delta延时,因此对于always过程会多次进入,对于同一变量多次赋值会引起always语句的循环进入;而阻塞赋值语句由于变量赋值立马生效,如果没有外部引起变量变化,always过程不会多次进入。
  3. 在同一个时间点上评估,非阻塞赋值语句由于有delta延迟,此时引用变量的值,是上次进入顺序过程的值。而阻塞赋值语句紧跟其后的引用可以立即使用已被更新的值。
  4. 在边沿过程中使用差别,在后续课程中会详细介绍。

 

Posted in FPGA, FPGA 教材教案, Quartus II, Quartus II, Verilog, Verilog, 开发语言, 教材与教案, 文章

发表评论

您的电子邮箱地址不会被公开。

Leave the field below empty!

相关链接