阻塞赋值和非阻塞赋值.docx
《阻塞赋值和非阻塞赋值.docx》由会员分享,可在线阅读,更多相关《阻塞赋值和非阻塞赋值.docx(28页珍藏版)》请在冰豆网上搜索。
阻塞赋值和非阻塞赋值
深入理解阻塞和非阻塞赋值的不同
阻塞和非阻塞赋值的语言结构是Verilog语言中最难理解概念之一。
甚至有些很有经验的Verilog设计工程师也不能完全正确地理解:
何时使用非阻塞赋值何时使用阻塞赋值才能设计出符合要求的电路。
他们也不完全明白在电路结构的设计中,即可综合风格的Verilog模块的设计中,究竟为什么还要用非阻塞赋值,以及符合IEEE标准的Verilog仿真器究竟如何来处理非阻塞赋值的仿真。
本小节的目的是尽可能地把阻塞和非阻塞赋值的含义详细地解释清楚,并明确地提出可综合的Verilog模块编程在使用赋值操作时应注意的要点,按照这些要点来编写代码就可以避免在Verilog仿真时出现冒险和竞争的现象。
我们在前面曾提到过下面两个要点:
•在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构。
•在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构。
为什么一定要这样做呢?
回答是,这是因为要使综合前仿真和综合后仿真一致的缘故。
如果不按照上面两个要点来编写Verilog代码,也有可能综合出正确的逻辑,但前后仿真的结果就会不一致。
为了更好地理解上述要点,我们需要对Verilog语言中的阻塞赋值和非阻塞赋值的功能和执行时间上的差别有深入的了解。
为了解释问题方便下面定义两个缩写字:
RHS–方程式右手方向的表达式或变量可分别缩写为:
RHS表达式或RHS变量。
LHS–方程式左手方向的表达式或变量可分别缩写为:
LHS表达式或LHS变量。
IEEEVerilog标准定义了有些语句有确定的执行时间,有些语句没有确定的执行时间。
若有两条或两条以上语句准备在同一时刻执行,但由于语句的排列次序不同(而这种排列次序的不同是IEEEVerilog标准所允许的),却产生了不同的输出结果。
这就是造成Verilog模块冒险和竞争现象的原因。
为了避免产生竞争,理解阻塞和非阻塞赋值在执行时间上的差别是至关重要的。
阻塞赋值
阻塞赋值操作符用等号(即=)表示。
为什么称这种赋值为阻塞赋值呢?
这是因为在赋值时先计算等号右手方向(RHS)部分的值,这时赋值语句不允许任何别的Verilog语句的干扰,直到现行的赋值完成时刻,即把RHS赋值给LHS的时刻,它才允许别的赋值语句的执行。
一般可综合的阻塞赋值操作在RHS不能设定有延迟,(即使是零延迟也不允许)。
从理论上讲,它与后面的赋值语句只有概念上的先后,而无实质上的延迟。
若在RHS加上延迟,则在延迟期间会阻止赋值语句的执行,延迟后才执行赋值,这种赋值语句是不可综合的,在需要综合的模块设计中不可使用这种风格的代码。
阻塞赋值的执行可以认为是只有一个步骤的操作:
计算RHS并更新LHS,此时不能允许有来自任何其他Verilog语句的干扰。
所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上(即使不设定延迟)是在前一句赋值语句结束后再开始赋值的。
如果在一个过程块中阻塞赋值的RHS变量正好是另一个过程块中阻塞赋值的LHS变量,这两个过程块又用同一个时钟沿触发,这时阻塞赋值操作会出现问题,即如果阻塞赋值的次序安排不好,就会出现竞争。
若这两个阻塞赋值操作用同一个时钟沿触发,则执行的次序是无法确定的。
下面的例子可以说明这个问题:
[例1].用阻塞赋值的反馈振荡器
modulefbosc1(y1,y2,clk,rst);
outputy1,y2;
inputclk,rst;
regy1,y2;
always@(posedgeclkorposedgerst)
if(rst)y1=0;//reset
elsey1=y2;
always@(posedgeclkorposedgerst)
if(rst)y2=1;//preset
elsey2=y1;
endmodule
按照IEEEVerilog的标准,上例中两个always块是并行执行的,与前后次序无关。
如果前一个always块的复位信号先到0时刻,则y1和y2都会取1,而如果后一个always块的复位信号先到0时刻,则y1和y2都会取0。
这清楚地说明这个Verilog模块是不稳定的会产生冒险和竞争的情况。
非阻塞赋值
非阻塞赋值操作符用小于等于号(即<=)表示。
为什么称这种赋值为非阻塞赋值?
这是因为在赋值操作时刻开始时计算非阻塞赋值符的RHS表达式,赋值操作时刻结束时更新LHS。
在计算非阻塞赋值的RHS表达式和更新LHS期间,其他的Verilog语句,包括其他的Verilog非阻塞赋值语句都能同时计算RHS表达式和更新LHS。
非阻塞赋值允许其他的Verilog语句同时进行操作。
非阻塞赋值的操作可以看作为两个步骤的过程:
在赋值时刻开始时,计算非阻塞赋值RHS表达式。
在赋值时刻结束时,更新非阻塞赋值LHS表达式。
非阻塞赋值操作只能用于对寄存器类型变量进行赋值,因此只能用在"initial"块和"always"块等过程块中。
非阻塞赋值不允许用于连续赋值。
下面的例子可以说明这个问题:
[例2].用非阻塞赋值的反馈振荡器
modulefbosc2(y1,y2,clk,rst);
outputy1,y2;
inputclk,rst;
regy1,y2;
always@(posedgeclkorposedgerst)
if(rst)y1<=0;//reset
elsey1<=y2;
always@(posedgeclkorposedgerst)
if(rst)y2<=1;//preset
elsey2<=y1;
endmodule
同样,按照IEEEVerilog的标准,上例中两个always块是并行执行的,与前后次序无关。
无论哪一个always块的复位信号先到,两个always块中的非阻塞赋值都在赋值开始时刻计算RHS表达式,而在结束时刻才更新LHS表达式。
所以这两个always块在复位信号到来后,在always块结束时y1为0而y2为1是确定的。
从用户的角度看这两个非阻塞赋值正好是并行执行的。
Verilog模块编程要点:
下面我们还将对阻塞和非阻塞赋值做进一步解释并将举更多的例子来说明这个问题。
在此之前,掌握可综合风格的Verilog模块编程的八个原则会有很大的帮助。
在编写时牢记这八个要点可以为绝大多数的Verilog用户解决在综合后仿真中出现的90-100%的冒险竞争问题。
1)时序电路建模时,用非阻塞赋值。
2)锁存器电路建模时,用非阻塞赋值。
3)用always块建立组合逻辑模型时,用阻塞赋值。
4)在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。
5)在同一个always块中不要既用非阻塞赋值又用阻塞赋值。
6)不要在一个以上的always块中为同一个变量赋值。
7)用$strobe系统任务来显示用非阻塞赋值的变量值
8)在赋值时不要使用#0延迟
我们在后面还要对为什么要记住这些要点再做进一步的解释。
Verilog的新用户在彻底搞明白这两种赋值功能差别之前,一定要牢记这几条要点。
照着要点来编写Verilog模块程序,就可省去很多麻烦。
Verilog的层次化事件队列
详细地了解Verilog的层次化事件队列有助于我们理解Verilog的阻塞和非阻塞赋值的功能。
所谓层次化事件队列指的是用于调度仿真事件的不同的Verilog事件队列。
在IEEEVerilog标准中,层次化事件队列被看作是一个概念模型。
设计仿真工具的厂商如何来实现事件队列,由于关系到仿真器的效率,被视为技术诀窍,不能公开发表。
本节也不作详细介绍。
在IEEE1364-1995Verilog标准的5.3节中定义了:
层次化事件队列在逻辑上分为用于当前仿真时间的4个不同的队列,和用于下一段仿真时间的若干个附加队列。
1)动态事件队列(下列事件执行的次序可以随意安排)
▪阻塞赋值
▪计算非阻塞赋值语句右边的表达式
▪连续赋值
▪
▪执行$display命令
▪
▪计算原语的输入和输出的变化
2)停止运行的事件队列
▪#0延时阻塞赋值
3)非阻塞事件队列
▪更新非阻塞赋值语句LHS(左边变量)的值
4)监控事件队列
•执行$monitor命令
•执行$strobe命令
5)其他指定的PLI命令队列
▪(其他PLI命令)
以上五个队列就是Verilog的“层次化事件队列”
大多数Verilog事件是由动态事件队列调度的,这些事件包括阻塞赋值、连续赋值、$display命令、实例和原语的输入变化以及他们的输出更新、非阻塞赋值语句RHS的计算等。
而非阻塞赋值语句LHS的更新却不由动态事件队列调度。
在IEEE标准允许的范围内被加入到这些队列中的事件只能从动态事件队列中清除。
而排列在其他队列中的事件要等到被“激活”后,即被排入动态事件队列中后,才能真正开始等待执行。
IEEE1364-1995Verilog标准的5.4节介绍了一个描述其他事件队列何时被“激活”的算法。
在当前仿真时间中,另外两个比较常用的队列是非阻塞赋值更新事件队列和监控事件队列。
细节见后。
非阻塞赋值LHS变量的更新是按排在非阻塞赋值更新事件队列中。
而RHS表达式的计算是在某个仿真时刻随机地开始的,与上述其他动态事件是一样的。
$strobe和$monitor显示命令是排列在监控事件队列中。
在仿真的每一步结束时刻,当该仿真步骤内所有的赋值都完成以后,$strobe和$monitor显示出所有要求显示的变量值的变化。
在Verilog标准5.3节中描述的第四个事件队列是停止运行事件队列,所有#0延时的赋值都排列在该队列中。
采用#0延时赋值是因为有些对Verilog理解不够深入的设计人员希望在两个不同的程序块中给同一个变量赋值,他们企图在同一个仿真时刻,通过稍加延时的赋值来消除Verilog可能产生的竞争冒险。
这样做实际上会产生问题。
因为给Verilog模型附加完全不必要的#0延时赋值,使得定时事件的分析变得很复杂。
我们认为采用#0延时赋值根本没有必要,完全可用其他的方式来代替,因此不推荐使用。
在下面的一些例子中,常常用上面介绍的层次化事件队列来解释Verilog代码的行为。
时件队列的概念也常常用来说明为什么要坚持上面提到的8项原则。
自触发always块
一般而言,Verilog的always块不能触发自己,见下面的例子:
[例3]使用阻塞赋值的非自触发振荡器
moduleosc1(clk);
outputclk;
regclk;
initial#10clk=0;
always@(clk)#10clk=~clk;
endmodule
上例描述的时钟振荡器使用了阻塞赋值。
阻塞赋值时,计算RHS表达式并更新LHS的值,此时不允许其他语句的干扰。
阻塞赋值必须在@(clk)边沿触发到来时刻之前完成。
当触发事件到来时,阻塞赋值已经完成了,因此没有来自always块内部的触发事件来触发@(clk),是一个非自触发振荡器。
而例4中的振荡器使用的是非阻塞赋值,它是一个自触发振荡器。
[例4]采用非阻塞赋值的自触发振荡器
moduleosc2(clk);
outputclk;
regclk;
initial#10clk=0;
always@(clk)#10clk<=~clk;
endmodule
@(clk)的第一次触发之后,非阻塞赋值的RHS表达式便计算出来,把值赋给LHS的事件被安排在更新事件队列中。
在非阻塞赋值更新事件队列被激活之前,又遇到了@(clk)触发语句,并且always块再次对clk的值变化产生反应。
当非阻塞LHS的值在同一时刻被更新时,@(clk)再一次触发。
该例是自触发式,在编写仿真测试模块时不推荐使用这种写法的时钟信号源。
移位寄存器模型
下图表示是一个简单的移位寄存器方框图。
从例5至例8介绍了四种用阻塞赋值实现图2移位寄存器电路的方式,有些是不正确。
[例5]不正确地使用的阻塞赋值来描述移位寄存器。
(方式#1)
modulepipeb1(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)
begin
q1=d;
q2=q1;
q3=q2;
end
endmodule
在上面的模块中,按顺序进行的阻塞赋值将使得在下一个时钟上升沿时刻,所有的寄存器输出值都等于输入值d。
在每个时钟上升沿,输入值d将无延时地直接输出到q3。
显然,上面的模块实际上被综合成只有一个寄存器的电路(见图3),这并不是当初想要设计的移位寄存器电路。
[例6]用阻塞赋值来描述移位寄存器也是可行的,但这种风格并不好。
(方式#2)
modulepipeb2(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)
begin
q3=q2;
q2=q1;
q1=d;
end
endmodule
在上面[例6]的模块中,阻塞赋值的次序是经过仔细安排的,以使仿真的结果与移位寄存器相一致。
虽然该模块可被综合成图2所示的移位寄存器,但我们不建议使用这种风格的模块来描述时序逻辑。
[例7]不好的用阻塞赋值来描述移位时序逻辑的风格(方式#3)
modulepipeb3(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)q1=d;
always@(posedgeclk)q2=q1;
always@(posedgeclk)q3=q2;
endmodule
在[例7]中,阻塞赋值分别被放在不同的always块里。
仿真时,这些块的先后顺序是随机的,因此可能会出现错误的结果。
这是Verilog中的竞争冒险。
按不同的顺序执行这些块将导致不同的结果。
但是,这些代码的综合结果却是正确的流水线寄存器。
也就是说,前仿真和后仿真的结果可能会不一致。
[例8]不好的用阻塞赋值来描述移位时序逻辑的风格(方式#4)
modulepipeb4(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)q2=q1;
always@(posedgeclk)q3=q2;
always@(posedgeclk)q1=d;
endmodule
若在[例8]中仅把always块的次序的作些变动,也可以被综合成正确的移位寄存器逻辑,但仿真结果可能不正确。
如果用非阻塞赋值语句改写以上这四个阻塞赋值的例子,每一个例子都可以正确仿真,并且综合为设计者期望的移位寄存器逻辑。
[例9]正确的用非阻塞赋值来描述时序逻辑的设计风格#1
modulepipen1(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)begin
q1<=d;
q2<=q1;
q3<=q2;
end
endmodule
[例10]正确的用非阻塞赋值来描述时序逻辑的设计风格#2
modulepipen2(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)
begin
q3<=q2;
q2<=q1;
q1<=d;
end
endmodule
[例11]正确的用非阻塞赋值来描述时序逻辑的设计风格#3
modulepipen3(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)q1<=d;
always@(posedgeclk)q2<=q1;
always@(posedgeclk)q3<=q2;
endmodule
[例12]正确的用非阻塞赋值来描述时序逻辑的设计风格#4
modulepipen4(q3,d,clk);
output[7:
0]q3;
input[7:
0]d;
inputclk;
reg[7:
0]q3,q2,q1;
always@(posedgeclk)q2<=q1;
always@(posedgeclk)q3<=q2;
always@(posedgeclk)q1<=d;
endmodule
以上移位寄存器时序逻辑电路设计的例子表明:
•四种阻塞赋值设计方式中有一种可以保证仿真正确
•四种阻塞赋值设计方式中有三种可以保证综合正确
•四种非阻塞赋值设计方式全部可以保证仿真正确
•四种非阻塞赋值设计方式全部可以保证综合正确
虽然在一个always块中正确的安排赋值顺序,用阻塞赋值也可以实现移位寄存器时序流水线逻辑。
但是,用非阻塞赋值实现同一时序逻辑要相对简单,而且,非阻塞赋值可以保证仿真和综合的结果都是一致和正确的。
因此我们建议大家在编写Verilog时序逻辑时要用非阻塞赋值的方式。
阻塞赋值及一些简单的例子
许多关于Verilog和Verilog仿真的书籍都有一些使用阻塞赋值而且成功的简单例子。
例13就是一个在许多书上都出现过的关于触发器的例子。
[例13]moduledffb(q,d,clk,rst);
outputq;
inputd,clk,rst;
regq;
always@(posedgeclk)
if(rst)q=1'b0;
elseq=d;
endmodule
虽然可行也很简单,但我们不建议这种用阻塞赋值来描述D触发器模型的风格。
如果要把所有的模块写到一个always块里,是可以采用阻塞赋值得到正确的建模、仿真并综合成期望的逻辑。
但是,这种想法将导致使用阻塞赋值的习惯,而在较为复杂的多个always块的情况下可能会导致竞争冒险。
[例14]使用非阻塞赋值来描述D触发器是建议使用的风格
moduledffx(q,d,clk,rst);
outputq;
inputd,clk,rst;
regq;
always@(posedgeclk)
if(rst)q<=1'b0;
elseq<=d;
endmodule
养成在描述时序逻辑的多个always块(甚至在单个always块)中使用非阻塞赋值的习惯比较好,见例14所示。
现在来看一个稍复杂的时序逻辑——线性反馈移位寄存器或LFSR。
时序反馈移位寄存器建模
线性反馈移位寄存器(LinearFeedbackShift-Register简称LFSR)是带反馈回路的时序逻辑。
反馈回路给习惯于用顺序阻塞赋值描述时序逻辑的设计人员带来了麻烦。
见15所示。
[例15]用阻塞赋值实现的线性反馈移位寄存器,实际上并不具有LFSR的功能
modulelfsrb1(q3,clk,pre_n);
outputq3;
inputclk,pre_n;
regq3,q2,q1;
wiren1;
assignn1=q1^q3;
always@(posedgeclkornegedgepre_n)
if(!
pre_n)
begin
q3=1'b1;
q2=1'b1;
q1=1'b1;
end
else
begin
q3=q2;
q2=n1;
q1=q3;
end
endmodule
除非使用中间暂存变量,否则用例15所示的赋值是不可能实现反馈逻辑的。
有的人可能会想到将这些赋值语句组成单行等式(如例16所示),来避免使用中间变量。
如果逻辑再复杂一些,单行等式是难以编写和调试的。
这种方法不推荐使用。
[例16]用阻塞赋值描述的线性反馈移位寄存器,其功能正确,但模型的含义较难理解。
modulelfsrb2(q3,clk,pre_n);
outputq3;
inputclk,pre_n;
regq3,q2,q1;
always@(posedgeclkornegedgepre_n)
if(!
pre_n){q3,q2,q1}=3'b111;
else{q3,q2,q1}={q2,(q1^q3),q3};
endmodule
如果将例15和例16中的阻塞赋值用非阻塞赋值代替,如例17和例18所示,仿真结果都和LFSR的功能相一致。
[例17]用非阻塞语句描述的LFSR,可综合其功能正确。
modulelfsrn1(q3,clk,pre_n);
outputq3;
inputclk,pre_n;
regq3,q2,q1;
wiren1;
assignn1=q1^q3;
always@(posedgeclkornegedgepre_n)
if(!
pre_n)begin
q3<=1'b1;
q2<=1'b1;
q1<=1'b1;
end
elsebegin
q3<=q2;
q2<=n1;
q1<=q3;
end
endmodule
[例18]用非阻塞语句描述的LFSR,可综合其功能正确。
modulelfsrn2(q3,clk,pre_n);
outputq3;
inputclk,pre_n;
regq3,q2,q1;
always@(posedgeclkornegedgepre_n)
if(!
pre_n)