Menu Close

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

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

上节内容介绍了阻塞赋值语句及非阻塞赋值语句的描述、特点及区别,并用实例逐步深入探讨的方式逐一介绍给大家。本文将介绍在建模及实际设计应用中应注意的问题以及在处理某些问题时的使用技巧,特别是结合顺序语句以及在边沿驱动下always过程使用中应注意的问题。由于上节中的例子都是在testbench中进行的,为了考虑综合和仿真的普遍性,本节中的内容都是采用实体程序与仿真程序分开设计的方式,将顺序性,阻塞性,非阻塞性放在可综合实体中实现,仿真程序仅提供激励信号和格式显示。

一、 阻塞与非阻塞赋值语句在边沿驱动过程使用

下面结合60进制计数器为例子进行介绍:

  1. 非阻塞赋值程序如下:

实体程序

`timescale 1 ns /1 ps

module non_block_counter

(

input rst,

input clk,

output reg [3:0] out_a,

output reg [3:0] out_b

);

always@(posedge clk or posedge rst)

if(rst) begin

out_a<=0;

out_b<=0;

end

else begin

if(out_a==9) begin

out_a<=0;

if(out_b==5)

out_b<=0;

else

out_b<=out_b+1;

end

else

out_a<=out_a+1;

end

endmodule

  1. 仿真程序(tb.v)


  `timescale 1 ns  /1 ps


  module tb_block 


  (


  );


  parameter       PERIOD=10;


  reg		 clk;


  initial   begin


  clk=0;


  forever 


  #5 clk=~clk;


  end


  reg		 rst;


  wire    [3:0]   out_a ,out_b;


    initial begin


  		rst=0;


  		#5


  		rst=1'b1;


  		#30


  		rst=0;


     end


  always@(posedge clk or posedge rst)


  if(rst) begin


  	$display(“time=%d, out_a=%h, out_b=%h”,$time, out_a,out_b);


  end


  else begin


  	$display(“time=%d, out_a=%h, out_b=%h”,$time, out_a,out_b);


  end


  		


  non_block_counter  non_block_counter_inst


  (


  .rst		(rst),


  .clk		(clk),


  .out_a		(out_a),


  .out_b		(out_b)


  );


  endmodule


  

仿真结果如图1,2,3所示

                      图1                                                                                                                                                              图2

图3

图4

从图1,2,3,4的对比可以看出$display函数打印的总是计数器上次的值,也就是上升沿之前的值。这就是上面内容提到的delta延时。

2. 利用非阻塞语句实现触发器模型

在介绍计数器的时候,我们曾经探讨过计数器可以实现触发器模型,当时采用D触发器作为参考模型实现的。当将reg型变量抽象成D触发器模型,该变量就有三个属性,分别为D,CLK, Q。可以用X.D,X.CLK,X.Q表示, 如out_a的三个属性为out_a.D,out_a.CLK, out_a.Q.

为什么非阻塞语句比较切合D触发器模型呢?因为它们的行为比较吻合,吻合点列举如下:

(1)都由reg型变量表示(触发器是寄存器的一种)。

(2)都采用了带有边沿的always过程。

(3)在边沿的时间点上,非阻塞赋值语句由于delta延时,被赋值的变量只有always过程结束后才被更新(如图1,2),而触发器的Q的值也是在上升沿结束后才被更新,理想的D触发器被认为上升沿的时间足够短,这与delta延迟的概念一致。因此非阻塞赋值语句的左边的值被认为(或等效为)触发器的Q端,而赋值语句的右边表达式被认为(或等效为)D端(或与触发器的D端相连),如图5。

(4)在引用变量值时,非阻塞赋值语句引用上次的值(上升沿之前的值,如图1,2, $display函数显示的内容),而在数字同步时序电路设计中对于D触发器值的引用也是引用D触发器Q端的值,例如:在if(out_a==9)中我们就可以用D触发器的模型来分析,if(out_a==9)的含义就是if(out_a.Q==9)。而“out_a<=out_a+1;”在赋值语句的左右两边都有out_a,那么如何区分D和Q呢?按照带边沿的always顺序过程非阻塞的理解,赋值左边的应为out_a.D,右边赋值表达式中的out_a应为out_a.Q。

3. 阻塞赋值

将上面的实体程序改成阻塞赋值的情况,然后分析和观察仿真结果。

(1)实体程序

`timescale 1 ns /1 ps

module non_block_counter

(

input rst,

input clk,

output reg [3:0] out_a,

output reg [3:0] out_b

);

always@(posedge clk or posedge rst)

if(rst) begin

out_a=0;

out_b=0;

end

else begin

if(out_a==9) begin

out_a=0;

if(out_b==5)

out_b=0;

else

out_b=out_b+1;

end

else

out_a=out_a+1;

end

endmodule

  1. 仿真程序(tb.v)


  `timescale 1 ns  /1 ps


  module tb_block 


  (


  );


  parameter       PERIOD=10;


  reg		 clk;


  initial   begin


  clk=0;


  forever 


  #5 clk=~clk;


  end


  reg		 rst;


  wire    [3:0]   out_a ,out_b;


    initial begin


  		rst=0;


  		#5


  		rst=1'b1;


  		#30


  		rst=0;


     end


  always@(posedge clk or posedge rst)


  if(rst) begin

  	$display(“time=%d, out_a=%h, out_b=%h”,$time, out_a,out_b);

  end

  else begin

  	$display(“time=%d, out_a=%h, out_b=%h”,$time, out_a,out_b);

  end



  block_counter  block_counter_inst


  (


  .rst		(rst),


  .clk		(clk),


  .out_a		(out_a),


  .out_b		(out_b)


  );


  endmodule

  

仿真结果如下:

图5                                                                                                                                                              图6

从上面的程序的逻辑和时序分析看,阻塞赋值语句应该非阻塞语句有差别,如在time=40时应该输出”out_b=0,out_a=1”,但仿真结果图5,6却与图1,2的结果完全一致。这与预期的结果不一致。观察程序可以出,$display 函数与变量赋值不在同一个always过程中,这对于非阻塞赋值没有区别,但对于阻塞赋值语句是不同,由于$display 函数在不同的always过程中,很难确定$display函数与各个赋值语句的先后顺序,因此上面的仿真结果并不可信。修改程序如下:

(1)将$display 函数放实体程序的赋值语句后面,注意这种改动会引起程序的不可综合。



  `timescale 1 ns  /1 ps


  module block_counter


  (


  input		   rst,


  input  	                  clk,


  output reg [3:0]      out_a,


  output reg [3:0]      out_b


  );


  always@(posedge clk or posedge rst)


  if(rst) begin


  	out_a=0;


  	out_b=0;


  	$display("time=%d, out_b=%h, out_a=%h",$time, out_b,out_a);


  end


  else begin


  	if(out_a==9) begin


  		out_a=0;


  		if(out_b==5)


  		out_b=0;


  		else


  		out_b=out_b+1'b1;


  		$display("time=%d, out_b=%h, out_a=%h",$time, out_b,out_a);


  	end


  	else begin


  	 out_a=out_a+1'b1;

  	 $display("time=%d, out_b=%h, out_a=%h",$time, out_b,out_a);

  	 end


  end

  endmodule

  
  1. 注释仿真程序的$display函数,


   `timescale 1 ns  /1 ps


  module tb


  (


  );


  parameter       PERIOD=10;


  reg				 clk;


  initial   begin


  clk=0;

  #5

  forever  begin


  	#5 clk=~clk;


  	if($time&gt;630)


  	$stop;


  end


  end


  reg				 rst;


  wire    [3:0]   out_a,out_b;


    initial begin


  		rst=0;


  		#5


  		rst=1'b1;


  		#30


  		rst=0;


     end

  //always@(posedge clk or posedge rst)


  //if(rst) begin


  //	$display("time=%d, out_b=%h, out_a=%h",$time, out_b,out_a);


  //end


  //else begin


  //	$display("time=%d, out_b=%h, out_a=%h",$time, out_b,out_a);


  //end


  //	


  block_counter  block_counter_inst


  (


  .rst			(rst),


  .clk			(clk),


  .out_a		(out_a),


  .out_b		(out_b)


  );


  endmodule


  

将修改后的程序重新仿真,结果如图7:

图7

从图7的仿真结果可以看出,和上面的分析结果一致。即在time=40时输出”out_b=0,out_a=1”。

从上面的结果看,如果虽然整个循环结果相同,但每次的$display函数输出结果提前了10ns,虽然对于计数器来说没有区别,但对于其它应用程序,可能会带来严重错误。例如,如果存储器的地址和数据在生成时,地址如果用阻塞语句而数据用非阻塞语句则会出现地址数据不一致的情况。因此使用时要注意。

4. 时钟边沿驱动下的阻塞赋值语句模型

我们知道在时钟边沿驱动下非阻塞赋值语句可以用D触发器进行建模,阻塞赋值语句是否也可以呢?下面我们将上例做简单修改,测试阻塞赋值语句在另一个边沿驱动的always过程中非阻塞赋值语句的捕获情况,修改的程序如下,

(1)实体程序

`timescale 1 ns /1 ps

module block_counter

(

input rst,

input clk,

output reg [3:0] out_a,

output reg [3:0] out_b,

output reg [3:0] out_c,

output reg [3:0] out_d

);

always@(posedge clk or posedge rst)

if(rst) begin

out_a=0;

out_b=0;

$display(“time=%d, out_b=%h, out_a=%h”,$time, out_b,out_a);

end

else begin

if(out_a==9) begin

out_a=0;

if(out_b==5)

out_b=0;

else

out_b=out_b+1’b1;

$display(“time=%d, out_b=%h, out_a=%h”,$time, out_b,out_a);

end

else begin

out_a=out_a+1’b1;

$display(“time=%d, out_b=%h, out_a=%h”,$time, out_b,out_a);

end

end

always@(posedge clk or posedge rst)

if(rst) begin

out_c<=0;

out_d<=0;

end

else begin

out_c<=out_a;

out_d<=out_b;

end

endmodule

(2)仿真程序,

`timescale 1 ns /1 ps

module tb

(

);

parameter PERIOD=10;

reg clk;

initial begin

clk=0;

#5

forever begin

#5 clk=~clk;

if($time>630)

$stop;

end

end

reg rst;

wire [3:0] out_a,out_b;

wire [3:0] out_c,out_d;

initial begin

rst=0;

#5

rst=1’b1;

#30

rst=0;

end

block_counter block_counter_inst

(

.rst (rst),

.clk (clk),

.out_a (out_a),

.out_b (out_b),

.out_c (out_c),

.out_d (out_d)

);

endmodule

仿真结果如图8,

图8

图8中可以看到,在40ns时,out_a 的变化不仅能立马更新,而且out_c也能立即得到out_a的值。我们知道在功能仿真时时钟的上升沿是理想的,其数学模型为阶跃函数。相比非阻塞模型,阻塞模型的值相当于左极限,而非阻塞模型更像右极限。因此阻塞模型不符合标准边沿D触发器的模型。但是在边沿撤销后又能保持数据不变,因此又的确是触发器的模型。由于阻塞赋值语句不能用单一的边沿D触发器模型来分析,在同步时序电路设计中带来复杂性。因此建议在同步时序电路设计中尽量采用非阻塞式赋值语句。下面针对阻塞赋值语句常见的问题进行分析并给出解决办法。

二、  阻塞语句不完整的分支语句处理

在图1,2中可以看出,对于完整if分支语句(if…else…),阻塞与非阻塞在分支及边界条件处理方面差别不大,都能实现要求的循环计数。但不完整的分支语句结果不同,如将上面的60进制计数器加以修改得到另外一种形式的计数器(仅复制部分程序)。

非阻塞源程序:

if(out_a==9) begin

out_a<=0;

if(out_b==5)

out_b<=0;

else

out_b<=out_b+1;

end

else

out_a<=out_a+1;

修改后的程序:

 

out_a<=out_a+1;

if(out_a==9) begin

out_a<=0;

out_b<=out_b+1;

if(out_b==5)

out_b<=0;

end

可以看出第二段更简洁,充分顺序语句的特点,调换语句的前后顺序,实现第一段的功能,由于该部分内容经常被使用,大家都熟悉这两种写法,这里省略仿真程序及过程。

阻塞源程序:

if(out_a==9) begin

out_a=0;

if(out_b==5)

out_b=0;

else

out_b=out_b+1;

end

else

out_a=out_a+1;

修改后的程序:

 

out_a=out_a+1;

if(out_a==9) begin

out_a=0;

out_b=out_b+1;

if(out_b==5)

out_b=0;

end

阻塞程序修改后,经过仔细分析,发现第二段不能完全替代第一段,主要发生在边界的地方。由于阻塞赋值语句立马生效,当out_a==8时,赋值语句out_a=out_a+1;立马生效得到out_a==9,而if(out_a==9)条件成立又把out_a修改成0;因此计数器将会丢失out_a==9的机会,out_b也会出现同样的情况。下面看看第二段的仿真结果,如图9所示,

图9

从图9的结果可以看出在边界处的赋值与上面的分析结果一致。将程序修改如下可以恢复源程序的功能。

out_a=out_a+1;

if(out_a==10) begin

out_a=0;

out_b=out_b+1;

if(out_b==6)

out_b=0;

end

程序修改后的仿真结果如图10,

图10

从图10可以看出,经过修改后,计数器恢复正常。关于阻塞与非阻塞赋值语句

还有许多值得探讨的问题,我们将在下节内容进行深入探讨。

 

 

 

Posted in FPGA, FPGA 教材教案, Quartus II, Quartus II, 开发工具, 开发工具, 教材与教案, 文章

发表评论

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

Leave the field below empty!

相关链接