为了正常的体验网站,请在浏览器设置里面开启Javascript功能!
首页 > 阻塞赋值和非阻塞赋值

阻塞赋值和非阻塞赋值

2013-11-11 50页 doc 116KB 59阅读

用户头像

is_969843

暂无简介

举报
阻塞赋值和非阻塞赋值深入理解阻塞和非阻塞赋值的不同 阻塞和非阻塞赋值的语言结构是Verilog 语言中最难理解概念之一。甚至有些很有经验的Verilog 设计工程师也不能完全正确地理解:何时使用非阻塞赋值何时使用阻塞赋值才能设计出符合要求的电路。他们也不完全明白在电路结构的设计中,即可综合风格的Verilog模块的设计中,究竟为什么还要用非阻塞赋值,以及符合IEEE 标准的Verilog 仿真器究竟如何来处理非阻塞赋值的仿真。本小节的目的是尽可能地把阻塞和非阻塞赋值的含义详细地解释清楚,并明确地提出可综合的Verilog模块编程在使用赋值操作时...
阻塞赋值和非阻塞赋值
深入理解阻塞和非阻塞赋值的不同 阻塞和非阻塞赋值的语言结构是Verilog 语言中最难理解概念之一。甚至有些很有经验的Verilog 工程师也不能完全正确地理解:何时使用非阻塞赋值何时使用阻塞赋值才能设计出符合要求的电路。他们也不完全明白在电路结构的设计中,即可综合风格的Verilog模块的设计中,究竟为什么还要用非阻塞赋值,以及符合IEEE 的Verilog 仿真器究竟如何来处理非阻塞赋值的仿真。本小节的目的是尽可能地把阻塞和非阻塞赋值的含义详细地解释清楚,并明确地提出可综合的Verilog模块编程在使用赋值操作时应注意的要点,按照这些要点来编写代码就可以避免在Verilog 仿真时出现冒险和竞争的现象。我们在前面曾提到过下面两个要点: · 在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构。 · 在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构。 为什么一定要这样做呢?回答是,这是因为要使综合前仿真和综合后仿真一致的缘故。如果不按照上面两个要点来编写Verilog代码,也有可能综合出正确的逻辑,但前后仿真的结果就会不一致。 为了更好地理解上述要点,我们需要对Verilog 语言中的阻塞赋值和非阻塞赋值的功能和执行时间上的差别有深入的了解。为了解释问题方便下面定义两个缩写字: RHS – 方程式右手方向的达式或变量可分别缩写为: RHS表达式或RHS变量。 LHS – 方程式左手方向的表达式或变量可分别缩写为: LHS表达式或LHS变量。 IEEE Verilog标准定义了有些语句有确定的执行时间,有些语句没有确定的执行时间。若有两条或两条以上语句准备在同一时刻执行,但由于语句的排列次序不同(而这种排列次序的不同是IEEE Verilog标准所允许的), 却产生了不同的输出结果。这就是造成Verilog模块冒险和竞争现象的原因。为了避免产生竞争,理解阻塞和非阻塞赋值在执行时间上的差别是至关重要的。 阻塞赋值 阻塞赋值操作符用等号(即 = )表示。为什么称这种赋值为阻塞赋值呢?这是因为在赋值时先计算等号右手方向(RHS)部分的值,这时赋值语句不允许任何别的Verilog语句的干扰,直到现行的赋值完成时刻,即把RHS赋值给 LHS的时刻,它才允许别的赋值语句的执行。一般可综合的阻塞赋值操作在RHS不能设定有延迟,(即使是零延迟也不允许)。从理论上讲,它与后面的赋值语句只有概念上的先后,而无实质上的延迟。 若在RHS 加上延迟,则在延迟期间会阻止赋值语句的执行, 延迟后才执行赋值,这种赋值语句是不可综合的,在需要综合的模块设计中不可使用这种风格的代码。 阻塞赋值的执行可以认为是只有一个步骤的操作: 计算RHS并更新LHS,此时不能允许有来自任何其他Verilog语句的干扰。 所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上(即使不设定延迟)是在前一句赋值语句结束后再开始赋值的。 如果在一个过程块中阻塞赋值的RHS变量正好是另一个过程块中阻塞赋值的LHS变量,这两个过程块又用同一个时钟沿触发,这时阻塞赋值操作会出现问题,即如果阻塞赋值的次序安排不好,就会出现竞争。若这两个阻塞赋值操作用同一个时钟沿触发,则执行的次序是无法确定的。下面的例子可以说明这个问题: [例1]. 用阻塞赋值的反馈振荡器 module fbosc1 (y1, y2, clk, rst); output y1, y2; input clk, rst; reg y1, y2; always @(posedge clk or posedge rst) if (rst) y1 = 0; // reset else y1 = y2; always @(posedge clk or posedge rst) if (rst) y2 = 1; // preset else y2 = y1; endmodule 按照IEEE Verilog 的标准,上例中两个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]. 用非阻塞赋值的反馈振荡器 module fbosc2 (y1, y2, clk, rst); output y1, y2; input clk, rst; reg y1, y2; always @(posedge clk or posedge rst) if (rst) y1 <= 0; // reset else y1 <= y2; always @(posedge clk or posedge rst) if (rst) y2 <= 1; // preset else y2 <= y1; endmodule 同样,按照IEEE Verilog 的标准,上例中两个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事件队列。在IEEE Verilog标准中,层次化事件队列被看作是一个概念模型。设计仿真工具的厂商如何来实现事件队列,由于关系到仿真器的效率,被视为技术诀窍,不能公开发表。本节也不作详细介绍。 在IEEE 1364-1995 Verilog标准的5.3节中定义了: 层次化事件队列在逻辑上分为用于当前仿真时间的4个不同的队列, 和用于下一段仿真时间的若干个附加队列。 1) 动态事件队列(下列事件执行的次序可以随意安排) · 阻塞赋值 · 计算非阻塞赋值语句右边的表达式 · 连续赋值 · 执行$display命令 · 计算原语的输入和输出的变化 2) 停止运行的事件队列 · #0 延时阻塞赋值 3) 非阻塞事件队列 · 更新非阻塞赋值语句LHS(左边变量)的值 4) 监控事件队列 · 执行$monitor 命令 · 执行$strobe 命令 5) 其他指定的PLI命令队列 · (其他 PLI 命令) 以上五个队列就是Verilog 的“层次化事件队列” 大多数Verilog事件是由动态事件队列调度的,这些事件包括阻塞赋值、连续赋值、$display命令、实例和原语的输入变化以及他们的输出更新、非阻塞赋值语句RHS的计算等。而非阻塞赋值语句LHS的更新却不由动态事件队列调度。 在IEEE标准允许的范围内被加入到这些队列中的事件只能从动态事件队列中清除。而排列在其他队列中的事件要等到被“激活”后,即被排入动态事件队列中后,才能真正开始等待执行。IEEE 1364-1995 Verilog 标准的5.4节介绍了一个描述其他事件队列何时被“激活”的算法。 在当前仿真时间中,另外两个比较常用的队列是非阻塞赋值更新事件队列和监控事件队列。细节见后。 非阻塞赋值LHS变量的更新是按排在非阻塞赋值更新事件队列中。而RHS表达式的计算是在某个仿真时刻随机地开始的,与上述其他动态事件是一样的。 $strobe 和 $monitor显示命令是排列在监控事件队列中。在仿真的每一步结束时刻,当该仿真步骤内所有的赋值都完成以后,$strobe 和 $monitor显示出所有要求显示的变量值的变化。 在Verilog标准5.3节中描述的第四个事件队列是停止运行事件队列,所有#0延时的赋值都排列在该队列中。采用#0延时赋值是因为有些对Verilog理解不够深入的设计人员希望在两个不同的程序块中给同一个变量赋值,他们企图在同一个仿真时刻,通过稍加延时的赋值来消除Verilog可能产生的竞争冒险。这样做实际上会产生问题。因为给Verilog模型附加完全不必要的#0延时赋值,使得定时事件的分析变得很复杂。我们认为采用#0延时赋值根本没有必要,完全可用其他的方式来代替,因此不推荐使用。 在下面的一些例子中,常常用上面介绍的层次化事件队列来解释Verilog代码的行为。时件队列的概念也常常用来说明为什么要坚持上面提到的8项原则。 自触发always块 一般而言,Verilog的always块不能触发自己,见下面的例子: [例3] 使用阻塞赋值的非自触发振荡器 module osc1 (clk); output clk; reg clk; initial #10 clk = 0; always @(clk) #10 clk = ~clk; endmodule 上例描述的时钟振荡器使用了阻塞赋值。阻塞赋值时,计算RHS表达式并更新LHS的值,此时不允许其他语句的干扰。阻塞赋值必须在@(clk)边沿触发到来时刻之前完成。当触发事件到来时,阻塞赋值已经完成了,因此没有来自always块内部的触发事件来触发@(clk),是一个非自触发振荡器。 而例4中的振荡器使用的是非阻塞赋值,它是一个自触发振荡器。 [例4] 采用非阻塞赋值的自触发振荡器 module osc2 (clk); output clk; reg clk; initial #10 clk = 0; always @(clk) #10 clk <= ~clk; endmodule @(clk)的第一次触发之后,非阻塞赋值的RHS表达式便计算出来,把值赋给LHS的事件被安排在更新事件队列中。在非阻塞赋值更新事件队列被激活之前,又遇到了@(clk)触发语句,并且always块再次对clk的值变化产生反应。当非阻塞LHS的值在同一时刻被更新时, @(clk)再一次触发。该例是自触发式,在编写仿真测试模块时不推荐使用这种写法的时钟信号源。 移位寄存器模型 下图表示是一个简单的移位寄存器方框图。 从例5至例8介绍了四种用阻塞赋值实现图2移位寄存器电路的方式,有些是不正确。 [例5] 不正确地使用的阻塞赋值来描述移位寄存器。(方式 #1) module pipeb1 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) begin q1 = d; q2 = q1; q3 = q2; end endmodule 在上面的模块中,按顺序进行的阻塞赋值将使得在下一个时钟上升沿时刻,所有的寄存器输出值都等于输入值d。在每个时钟上升沿,输入值d将无延时地直接输出到q3。 显然,上面的模块实际上被综合成只有一个寄存器的电路(见图3),这并不是当初想要设计的移位寄存器电路。 [例6] 用阻塞赋值来描述移位寄存器也是可行的,但这种风格并不好。(方式 #2 ) module pipeb2 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) begin q3 = q2; q2 = q1; q1 = d; end endmodule 在上面[例6]的模块中,阻塞赋值的次序是经过仔细安排的,以使仿真的结果与移位寄存器相一致。虽然该模块可被综合成图2所示的移位寄存器,但我们不建议使用这种风格的模块来描述时序逻辑。 [例7] 不好的用阻塞赋值来描述移位时序逻辑的风格(方式 #3) module pipeb3 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) q1 = d; always @(posedge clk) q2 = q1; always @(posedge clk) q3 = q2; endmodule 在[例7]中,阻塞赋值分别被放在不同的always块里。仿真时,这些块的先后顺序是随机的,因此可能会出现错误的结果。这是Verilog中的竞争冒险。按不同的顺序执行这些块将导致不同的结果。但是,这些代码的综合结果却是正确的流水线寄存器。也就是说,前仿真和后仿真的结果可能会不一致。 [例8] 不好的用阻塞赋值来描述移位时序逻辑的风格(方式 #4) module pipeb4 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) q2 = q1; always @(posedge clk) q3 = q2; always @(posedge clk) q1 = d; endmodule 若在[例8]中仅把always块的次序的作些变动,也可以被综合成正确的移位寄存器逻辑,但仿真结果可能不正确。 如果用非阻塞赋值语句改写以上这四个阻塞赋值的例子,每一个例子都可以正确仿真,并且综合为设计者期望的移位寄存器逻辑。 [例9] 正确的用非阻塞赋值来描述时序逻辑的设计风格 #1 module pipen1 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) begin q1 <= d; q2 <= q1; q3 <= q2; end endmodule [例10] 正确的用非阻塞赋值来描述时序逻辑的设计风格 #2 module pipen2 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) begin q3 <= q2; q2 <= q1; q1 <= d; end endmodule [例11] 正确的用非阻塞赋值来描述时序逻辑的设计风格 #3 module pipen3 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) q1 <= d; always @(posedge clk) q2 <= q1; always @(posedge clk) q3 <= q2; endmodule [例12] 正确的用非阻塞赋值来描述时序逻辑的设计风格 #4 module pipen4 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always @(posedge clk) q2 <= q1; always @(posedge clk) q3 <= q2; always @(posedge clk) q1 <= d; endmodule 以上移位寄存器时序逻辑电路设计的例子表明: · 四种阻塞赋值设计方式中有一种可以保证仿真正确 · 四种阻塞赋值设计方式中有三种可以保证综合正确 · 四种非阻塞赋值设计方式全部可以保证仿真正确 · 四种非阻塞赋值设计方式全部可以保证综合正确 虽然在一个always块中正确的安排赋值顺序,用阻塞赋值也可以实现移位寄存器时序流水线逻辑。但是,用非阻塞赋值实现同一时序逻辑要相对简单,而且,非阻塞赋值可以保证仿真和综合的结果都是一致和正确的。因此我们建议大家在编写Verilog时序逻辑时要用非阻塞赋值的方式。 阻塞赋值及一些简单的例子 许多关于Verilog和Verilog仿真的书籍都有一些使用阻塞赋值而且成功的简单例子。例13就是一个在许多书上都出现过的关于触发器的例子。 [例13] module dffb (q, d, clk, rst); output q; input d, clk, rst; reg q; always @(posedge clk) if (rst) q = 1'b0; else q = d; endmodule 虽然可行也很简单,但我们不建议这种用阻塞赋值来描述D触发器模型的风格。 如果要把所有的模块写到一个always块里,是可以采用阻塞赋值得到正确的建模、仿真并综合成期望的逻辑。但是,这种想法将导致使用阻塞赋值的习惯,而在较为复杂的多个always块的情况下可能会导致竞争冒险。 [例14] 使用非阻塞赋值来描述D触发器是建议使用的风格 module dffx (q, d, clk, rst); output q; input d, clk, rst; reg q; always @(posedge clk) if (rst) q <= 1'b0; else q <= d; endmodule 养成在描述时序逻辑的多个always块(甚至在单个always块)中使用非阻塞赋值的习惯比较好,见例14所示。 现在来看一个稍复杂的时序逻辑——线性反馈移位寄存器或LFSR。 时序反馈移位寄存器建模 线性反馈移位寄存器(Linear Feedback Shift-Register 简称LFSR)是带反馈回路的时序逻辑。反馈回路给习惯于用顺序阻塞赋值描述时序逻辑的设计人员带来了麻烦。见15所示。 [例15] 用阻塞赋值实现的线性反馈移位寄存器,实际上并不具有LFSR的功能 module lfsrb1 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; wire n1; assign n1 = q1 ^ q3; always @(posedge clk or negedge pre_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] 用阻塞赋值描述的线性反馈移位寄存器,其功能正确,但模型的含义较难理解。 module lfsrb2 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always @(posedge clk or negedge pre_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,可综合其功能正确。 module lfsrn1 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; wire n1; assign n1 = q1 ^ q3; always @(posedge clk or negedge pre_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 [例18] 用非阻塞语句描述的LFSR,可综合其功能正确。 module lfsrn2 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always @(posedge clk or negedge pre_n) if (!pre_n) {q3,q2,q1} <= 3'b111; else {q3,q2,q1} <= {q2,(q1^q3),q3}; endmodule 从上面介绍的移位寄存器的例子以及LFSR的例子,建议使用非阻塞赋值实现时序逻辑。而用非阻塞赋值语句实现锁存器也是最为安全的。 原则1 :时序电路建模时,用非阻塞赋值。 原则2 :锁存器电路建模时,用非阻塞赋值。 组合逻辑建模时应使用阻塞赋值: 在Verilog中可以用多种方法来描述组合逻辑,但是当用always块来描述组合逻辑时,应该用阻塞赋值。 如果always块中只有一条赋值语句,使用阻塞赋值或非阻塞赋值语句都可以,但是为了养成良好的编程习惯,应该尽量使用阻塞赋值语句来描述组合逻辑。 有些设计人员提倡非阻塞赋值语句不仅可以用于时序逻辑,也可以用于组合逻辑的描述。对于简单的组合alwasys块是可以这样的,但是当always块中有多个赋值语句时,如例19所示的四输入与或门逻辑,使用没有延时的非阻塞赋值可能导致仿真结果不正确。有时需要在always块的入口附加敏感事件,才能使仿真正确,因而从仿真的时间效率角度看也不合算。 [例19] 使用非阻塞赋值语句来描述组合逻辑——不建议使用这种风格。 module ao4 (y, a, b, c, d); output y; input a, b, c, d; reg y, tmp1, tmp2; always @(a or b or c or d) begin tmp1 <= a & b; tmp2 <= c & d; y <= tmp1 | tmp2; end endmodule 例19中,输出y的值由三个时序语句计算得到。由于非阻塞赋值语句在LHS更新前,计算RHS的值,因此tmp1和tmp2仍是应进入该always块时的值,而不是在该步仿真结束时将更新的数值。输出y反映的是刚进入always块时的tmp1和tmp2的值,而不是在always块中经计算后得到的值。 [例20]使用非阻塞赋值来描述多层组合逻辑,虽可行,但效率不高。 module ao5 (y, a, b, c, d); output y; input a, b, c, d; reg y, tmp1, tmp2;5 always @(a or b or c or d or tmp1 or tmp2) begin tmp1 <= a & b; tmp2 <= c & d; y <= tmp1 | tmp2; end endmodule 例20和例19的唯一区别在于,tmp1和temp2加入了敏感列表中。如前所描述,当非阻塞赋值的LHS数值更新时,always块将自触发并用最新计算的tmp1和tmp2的值计算更新输出y的值。将tmp1和tmp2加入到敏感列表中后,现在输出y的值是正确的。但是,一个always块中有多次参数传递降低了仿真器的性能,只有在没有其他合理方法的情况下才考虑这样做。 只需要在always块中使用阻塞赋值语句就可以实现组合逻辑,这样做既简单仿真又快是好的Verilog代码风格,建议大家使用。 [例21] 使用阻塞赋值实现组合逻辑是推荐使用的编码风格。 module ao2 (y, a, b, c, d); output y; input a, b, c, d; reg y, tmp1, tmp2; always @(a or b or c or d) begin tmp1 = a & b; tmp2 = c & d; y = tmp1 | tmp2; end endmodule 例21和例19的唯一区别是,用阻塞赋值替代了非阻塞赋值。这样做可以保证仿真时经一次数据传递输出y的值便是正确的,仿真效率高。因此有以下原则; 原则3 :用always块描述组合逻辑时,应采用阻塞赋值语句。 时序和组合的混合逻辑——使用非阻塞赋值 有时候将简单的组合逻辑和时序逻辑写在一起很方便。当把组合逻辑和时序逻辑写到一个always块中时,应遵从时序逻辑建模的原则,使用非阻塞赋值,如例22所示。 [例22 ] 在一个always块中同时实现组合逻辑和时序逻辑 module nbex2 (q, a, b, clk, rst_n); output q; input clk, rst_n; input a, b; reg q; always @(posedge clk or negedge rst_n) if (!rst_n) q <= 1'b0; // 时序逻辑 else q <= a ^ b;// 异或,为组合逻辑 endmodule 用两个always块实现以上逻辑也是可以的,一个always块是采用阻塞赋值的纯组合部分,另一个是采用非阻塞赋值的纯时序部分。见例23。 [例23] 将组合和时序逻辑分别写在两个always块中 module nbex1 (q, a, b, clk, rst_n); output q; input clk, rst_n; input a, b; reg q, y; always @(a or b) y = a ^ b; always @(posedge clk or negedge rst_n) if (!rst_n) q <= 1'b0; else q <= y; endmodule 原则4:在同一个always块中描述时序和组合逻辑混合电路时,用非阻塞赋值。 其他将阻塞和非阻塞混合使用的原则 Verilog语法并没有禁止将阻塞和非阻塞赋值自由地组合在一个always块里。虽然Verilog语法是允许这种写法的,但我们不建议在可综合模块的编写中采用这种风格。 [例24] 在always块中同时使用阻塞和非阻塞赋值的例子。 (应尽量避免使用这种风格的代码,在可综合模块中应严禁使用) module ba_nba2 (q, a, b, clk, rst_n); output q; input a, b, rst_n; input clk; reg q; always @(posedge clk or negedge rst_n) begin: ff reg tmp; if (!rst_n) q <= 1'b0; else begin tmp = a & b; q <= tmp; end end endmodule 例24可以得到正确的仿真和综合结果,因为阻塞赋值和非阻塞赋值操作的不是同一个变量。虽然这种方法是可行的,但并不建议使用。 [例25] 对同一变量既进行阻塞赋值,又进行非阻塞赋值会产生综合错误。 module ba_nba6 (q, a, b, clk, rst_n); output q; input a, b, rst_n; input clk; reg q, tmp; always @(posedge clk or negedge rst_n) if (!rst_n) q = 1'b0; // 对 q进行阻塞赋值 else begin tmp = a & b; q <= tmp; // 对 q进行非阻塞赋值 end endmodule 例25在仿真时结果通常是正确的,但是综合时会出错,因为对同一变量既进行阻塞赋值,又进行了非阻塞赋值。因此,必须将其改写才能成为可综合模型。 为了养成良好的编程习惯,建议: 原则5:不要在同一个always块中同时使用阻塞和非阻塞赋值。 对同一变量进行多次赋值 在一个以上always块中对同一个变量进行多次赋值可能会导致竞争冒险,即使使用非阻塞赋值也可能产生竞争冒险。在例26中,两个always块都对输出q进行赋值。由于两个always块执行的顺序是随机的,所以仿真时会产生竞争冒险。 [例25] 使用非阻塞赋值语句,由于两个always块对同一变量q赋值 产生竞争冒险的程序: module badcode1 (q, d1, d2, clk, rst_n); output q; input d1, d2, clk, rst_n; reg q; always @(posedge clk or negedge rst_n) if (!rst_n) q <= 1'b0; else q <= d1; always @(posedge clk or negedge rst_n) if (!rst_n) q <= 1'b0; else q <= d2; endmodule 当综合工具(如Synopsys)读到[例25]的代码时,将产生以下警告信息: Warning: In design 'badcode1', there is 1 multiple-driver net with unknown wired-logic type. 如果忽略这个警告,继续编译例26,将产生两个触发器输出到一个两输入与门。其综合级前仿真与综合后仿真的结果不完全一致。 原则6:严禁在多个always块中对同一个变量赋值。 常见的对于非阻塞赋值的误解 · 非阻塞赋值和$display 误解1: “使用$display命令不能用来显示非阻塞语句的赋值” 事实是: 非阻塞语句的赋值在所有的$display命令执行以后才更新数值 [例] module display_cmds; reg a; initial $monitor("\$monitor: a = %b", a); initial begin $strobe ("\$strobe : a = %b", a); a = 0; a <= 1; $display ("\$display: a = %b", a); #1 $finish; end endmodule 下面是上面模块的仿真结果说明$display命令的执行是安排在活动事件队列中,但排在非阻塞赋值数据更新事件之前。 $display: a = 0 $monitor: a = 1 $strobe : a = 1 · #0 延时赋值 误解2: “#0延时把赋值强制到仿真时间步的末尾” 事实是: #0延时将赋值事件强制加入停止运行事件队列中。 [例] module nb_schedule1; reg a, b; initial begin a = 0; b = 1; a <= b; b <= a; $monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b); $display ("%0dns: \$display: a=%b b=%b", $stime, a, b); $strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b); #0 $display ("%0dns: #0 : a=%b b=%b", $stime, a, b); #1 $monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b); $display ("%0dns: \$display: a=%b b=%b", $stime, a, b); $strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b); $display ("%0dns: #0 : a=%b b=%b", $stime, a, b); #1 $finish; end endmodule 下面是上面模块的仿真结果说明#0延时命令在非阻塞赋值事件发生前,在停止运行事件队列中执行。 0ns: $display: a=0 b=1 0ns: #0 : a=0 b=1 0ns: $monitor: a=1 b=0 0ns: $strobe : a=1 b=0 1ns: $display: a=1 b=0 1ns: #0 : a=1 b=0 1ns: $monitor: a=1 b=0 1ns: $strobe : a=1 b=0 原则7:用$strobe系统任务来显示用非阻塞赋值的变量值 · 对同一变量进行多次非阻塞赋值 误解3: “在Verilog语法标准中未定义可在同一个always块中对某同一变量进行多次非阻塞赋值”。 事实是: Verilog标准定义了在同一个always块中可对某同一变量进行多次非阻塞赋值但多次赋值中,只有最后一次赋值对该变量起作用。 引用IEEE 1364-1995 Verilog标准【2】,第47页,5.4.1节关于决定论的内容如下: “非阻塞赋值按照语句的顺序执行,请看下例: initial begin a <= 0; a <= 1; end 执行该模块时,有两个非阻塞赋值更新事件加入到非阻塞赋值更新队列。以前的规则要求将非阻塞赋值更新事件按照它们在源文件的顺序加入队列,这便要求按照事件在源文件中的顺序,将事件从队列中取出并执行。因此,在仿真第一步结束的时刻,变量a被设置为0,然后为1。” 结论:最后一个非阻塞赋值决定了变量的值。 总结: 本节中所有的原则归纳如下: · 原则1:时序电路建模时,用非阻塞赋值。 · 原则2:锁存器电路建模时,用非阻塞赋值。 · 原则3:用always块写组合逻辑时,采用阻塞赋值。 · 原则4:在同一个always块中同时建立时序和组合逻辑电路时,用非阻塞赋值。 · 原则5:在同一个always块中不要同时使用非阻塞赋值和阻塞赋值。 · 原则6:不要在多个always块中为同一个变量赋值。 · 原则7:用$strobe系统任务来显示用非阻塞赋值的变量值 · 原则8:在赋值时不要使用 #0 延迟 结论:遵循以上原则,有助于正确的编写可综合硬件,并且可以消除90-100%在仿真时可能产生的竞争冒险现象。 q1 q2 q3 d clk 图2 移位寄存器电路 q3 d clk 图3 实际综合的结果
/
本文档为【阻塞赋值和非阻塞赋值】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索