相关推荐recommended
【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解
作者:mmseoamin日期:2024-02-05

🎉欢迎来到FPGA专栏~阻塞赋值与非阻塞赋值


  • ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
  • ✨博客主页:小夏与酒的博客
  • 🎈该系列文章专栏:FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️

    【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,FPGQ2,第1张

【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,CSDN,第2张

🎉 目录-阻塞赋值与非阻塞赋值

  • 一、基础知识讲解
  • 二、阻塞赋值讲解
    • 代码编写
    • 三、非阻塞赋值讲解
      • 2.1 代码编写
      • 2.2 非阻塞赋值仿真
      • 2.3 延时解决
      • 四、阻塞赋值与非阻塞赋值分析与比较
        • 4.1 赋值语句
        • 4.2 分析与比较
        • 五、六个原则

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,遇见未来,第3张

          一、基础知识讲解

          阻塞赋值,操作符为 “ = ”,“阻塞”是指在进程语句(initial 和 always)中,当前的赋值语句会阻断其后语句的正常执行,也就是说后面的语句必须等到当前的赋值语句执行完毕才能执行。而且阻塞赋值可以看成是一步完成的,即:计算等号右边的值并同时赋给左边变量。

          非阻塞赋值,操作符为 “ <= ”,“非阻塞”是指在进程语句(initial 和 always)中,当前的赋值语句不会阻断其后语句的正常执行。

          二、阻塞赋值讲解

          代码编写

          阻塞赋值: 设计三个输入a、b和c,一个输出out:注意计算过程,使用分步计算,先计算a+b的值,然后再对这个值加c。

          BlockAndNonblock.v:

          //--------<阻塞赋值与非阻塞赋值讲解示例代码>----------
          module BlockAndNonblock(
          	input 				Clk,
          	input 				Rst_n,
          	input 				a,
          	input 				b,
          	input 				c,
          	output reg	[1:0] out//out在always块中进行赋值,需要定义为reg类型;
          );
          //--------<计算过程>----------
          //out = a + b + c;
          //a,b,c三者求和的最大值为3,3为一个两位宽的数,因此需要定义out的位宽为2;
          //对上式计算进行分解,并定义中间变量d;
          //d = a + b,out = d + c;
          	
          	reg [1:0]d;//d = a + b;
          	
          	always@(posedge Clk or negedge Rst_n)begin
          		if(!Rst_n)
          			out = 2'b0;
          		else begin
          			d = a + b;
          			out = d + c;
          		end
          	end
          endmodule
          

          上述代码的RTL视图:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,RTL1,第4张

          对于上述RTL视图:a和b通过加法器(ADDER)进行相加,得到的结果再和c相加,最后将值通过寄存器输出。

          对于Verilog HDL的并行执行来说,如果我们交换always块中的计算顺序,RTL视图应该保持不变才对,即:

          always@(posedge Clk or negedge Rst_n)begin
          	if(!Rst_n)
          		out = 2'b0;
          	else begin
          		out = d + c;
          		d = a + b;
          	end
          end
          

          对应的RTL视图为:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,RTL2,第5张

          📜观察上述两个RTL视图,当交换计算顺序之后,出现了两级寄存器,导致代码的实现电路是不一样的。

          三、非阻塞赋值讲解

          2.1 代码编写

          对上述编写的计算过程使用非阻塞赋值方式实现,即:

          always@(posedge Clk or negedge Rst_n)begin
          	if(!Rst_n)
          		out <= 2'b0;
          	else begin
          		d <= a + b;
          		out <= d + c;
          	end
          end
          

          BlockAndNonblock.v:

          //--------<阻塞赋值与非阻塞赋值讲解示例代码>----------
          module BlockAndNonblock(
          	input 				Clk,
          	input 				Rst_n,
          	input 				a,
          	input 				b,
          	input 				c,
          	output reg	[1:0] out//out在always块中进行赋值,需要定义为reg类型;
          );
          //--------<计算过程>----------
          //out = a + b + c;
          //a,b,c三者求和的最大值为3,3为一个两位宽的数,因此需要定义out的位宽为2;
          //对上式计算进行分解,并定义中间变量d;
          //d = a + b,out = d + c;
          	
          	reg [1:0]d;//d = a + b;
          	
          	always@(posedge Clk or negedge Rst_n)begin
          		if(!Rst_n)
          			out <= 2'b0;
          		else begin
          			d <= a + b;
          			out <= d + c;
          		end
          	end
          endmodule
          

          对应的RTL视图为:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,RTL4,第6张

          同时,我们改变计算顺序,再观察一下:

          always@(posedge Clk or negedge Rst_n)begin
          	if(!Rst_n)
          		out <= 2'b0;
          	else begin
          		out <= d + c;
          		d <= a + b;
          	end
          end
          

          对应的RTL视图为:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,RTL3,第7张

          📜由于该部分是使用非阻塞赋值的方式实现计算,所以当计算顺序改变之后,RTL视图并不会出现改变。

          2.2 非阻塞赋值仿真

          对非阻塞赋值方式进行仿真:

          BlockAndNonblock_tb.v:

          `timescale 1ns/1ns
          `define clock_period 20
          module BlockAndNonblock_tb;
          	reg Clk;
          	reg Rst_n;
          	reg a,b,c;
          	
          	wire [1:0]out;
          	BlockAndNonblock BlockAndNonblock0(
          		.Clk(Clk),
          		.Rst_n(Rst_n),
          		.a(a),
          		.b(b),
          		.c(c),
          		.out(out)
          	);
          	initial Clk = 1;
          	always#(`clock_period/2) Clk = ~Clk;
          	
          	initial begin 
          		Rst_n = 0;
          		a = 0;b = 0;c = 0;
          		#(`clock_period*200+1);
          		Rst_n = 1;
          		
          		a = 0;b = 0;c = 0;
          		#(`clock_period*200);
          		a = 0;b = 0;c = 1;
          		#(`clock_period*200);
          		a = 0;b = 1;c = 0;
          		#(`clock_period*200);
          		a = 0;b = 1;c = 1;
          		#(`clock_period*200);
          		a = 1;b = 0;c = 0;
          		#(`clock_period*200);
          		a = 1;b = 0;c = 1;
          		#(`clock_period*200);
          		a = 1;b = 1;c = 0;
          		#(`clock_period*200);
          		a = 1;b = 1;c = 1;
          		#(`clock_period*200);
          		#(`clock_period*200);
          		$stop;
          	end
          endmodule
          

          设置好仿真脚本之后进行仿真,需要注意好细节部分的波形图:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,仿真1,第8张

          之后观察后仿真:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,仿真2,第9张

          在后仿真中添加子模块之后,会产生很多的信号,这些信号都是由布局布线生成的中间信号。放大波形之后,仔细观察各数据的变化:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,仿真4,第10张

          从上图的波形仿真中可以看出,非阻塞赋值并不会受到上一语句的影响,即:

          always@(posedge Clk or negedge Rst_n)begin
          	if(!Rst_n)
          		out <= 2'b0;
          	else begin
          		d <= a + b;
          		out <= d + c;
          	end
          end
          

          其中d <= a + b;并不会阻塞out <= d + c;语句的执行。

          因此,在仿真一开始的阶段里(系统一直处于复位状态时),out的值都是未知态,原因是在d被计算出来之前,d处于未知态:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,仿真5,第11张

          out的值只与上一时刻的计算结果有关。

          2.3 延时解决

          为了解决out值延迟的情况,可将计算过程改写为:

          out <= a + b + c;
          

          即:

          //--------<阻塞赋值与非阻塞赋值讲解示例代码>----------
          module BlockAndNonblock(
          	input 				Clk,
          	input 				Rst_n,
          	input 				a,
          	input 				b,
          	input 				c,
          	output reg	[1:0] out//out在always块中进行赋值,需要定义为reg类型;
          );
          //--------<计算过程>----------
          	
          	always@(posedge Clk or negedge Rst_n)begin
          		if(!Rst_n)
          			out <= 2'b0;
          		else begin
          			out <= a + b + c;
          		end
          	end
          endmodule
          

          对应的RTL视图:

          【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,RTL5,第12张

          📜从上述RTL视图中可以看出:a + b + c执行的是组合逻辑运算。

          四、阻塞赋值与非阻塞赋值分析与比较

          对于阻塞赋值与非阻塞赋值的区别,本部分只做简单讲解,不会太深入内部原理。

          4.1 赋值语句

          在always中使用的赋值语句有两种:阻塞赋值和非阻塞赋值。赋值语句的左端都必须是reg类型,这是语法的强制要求,所以always结构中所有语句的左端变量都必须是reg型,这是与assign语句不同的,请格外留意。

          4.2 分析与比较

          🔸先看阻塞赋值:

          • 顺序块中,一条阻塞赋值语句执行结束后,才能继续执行下一条阻塞赋值语句。
          • 语句执行结束后,左侧值会立刻改变,前面语句赋值的结果可以被后面的语句使用。

            BlockAndNonblockPlus.v:

            module BlockAndNonblockPlus(
            	input Clk,
            	input Rst_n,
            	output reg [1:0]a,
            	output reg [1:0]b,
            	output reg [1:0]c
            );
            	always@(posedge Clk or negedge Rst_n)begin
            		if(!Rst_n)begin
            			a = 2'd1;
            			b = 2'd2;
            			c = 2'd3;
            		end
            		else begin
            			a = 2'd0;
            			b = a;
            			c = b;
            		end
            	end
            endmodule
            

            测试激励文件:

            `timescale 1ns/1ns
            `define clock_period 20
            module BlockAndNonblockPlus_tb;
            	
            	reg Clk;
            	reg Rst_n;
            	
            	wire [1:0]a;
            	wire [1:0]b;
            	wire [1:0]c;
            	
            	BlockAndNonblockPlus BlockAndNonblockPlus0(
            		.Clk(Clk),
            		.Rst_n(Rst_n),
            		.a(a),
            		.b(b),
            		.c(c)
            	);
            	
            	initial Clk = 1;
            	always#(`clock_period/2) Clk = ~Clk;
            	
            	initial begin
            		Rst_n = 0;
            		#(`clock_period*10);
            		Rst_n = 1;
            		#(`clock_period*10);
            		$stop;
            	end
            	
            endmodule
            

            直接看波形仿真结果:

            【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,仿真6,第13张

            可见,当复位信号变为高电平时,a、b、c的值都变为了0。分析下列语句:

            always@(posedge Clk or negedge Rst_n)begin
            	if(!Rst_n)begin
            		a = 2'd1;
            		b = 2'd2;
            		c = 2'd3;
            	end
            	else begin
            		a = 2'd0;
            		b = a;
            		c = b;
            	end
            end
            

            代码中使用的是阻塞赋值语句,从波形图中可以看到,在复位的时候( Rst_n=0),a=1,b=2,c=3;而结束复位之后(Rst_n=1),当 Clk 的上升沿到来时,a=0,b=0,c=0。这是因为阻塞赋值是在当前语句执行完成之后,才会执行后面的赋值语句,因此首先执行的是 a=0,赋值完成后将 a 的值赋值给 b,由于此时 a 的值已经为 0,所以 b=a=0,最后执行的是将 b 的值赋值给 c,而 b 的值已经赋值为 0,所以 c 的值同样等于 0。

            🔸接下来看非阻塞赋值:

            • 同一时间点,前面语句的赋值不能立刻被后面的语句使用。
            • 所有的赋值是在右侧运算完毕时统一完成的。

              BlockAndNonblockPlus.v:

              module BlockAndNonblockPlus(
              	input Clk,
              	input Rst_n,
              	output reg [1:0]a,
              	output reg [1:0]b,
              	output reg [1:0]c
              );
              	always@(posedge Clk or negedge Rst_n)begin
              		if(!Rst_n)begin
              			a <= 2'd1;
              			b <= 2'd2;
              			c <= 2'd3;
              		end
              		else begin
              			a <= 2'd0;
              			b <= a;
              			c <= b;
              		end
              	end
              endmodule
              

              测试激励文件保持不变,观察波形图:

              【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,仿真7,第14张

              分析以下代码与仿真结果:

              always@(posedge Clk or negedge Rst_n)begin
              	if(!Rst_n)begin
              		a <= 2'd1;
              		b <= 2'd2;
              		c <= 2'd3;
              	end
              	else begin
              		a <= 2'd0;
              		b <= a;
              		c <= b;
              	end
              end
              

              代码中使用的是非阻塞赋值语句,从波形图中可以看到,在复位的时候(Rst_n=0),a=1,b=2,c=3;而结束复位之后(Rst_n=1),当 Clk 的上升沿到来时,a=0,b=1,c=2。这是因为非阻塞赋值在计算赋值等号右边的表达式或变量和更新赋值等号左边的表达式或变量期间,允许其它的非阻塞赋值语句同时计算赋值等号右边的表达式或变量和更新赋值等号左边的表达式或变量。

              因此,当复位结束之后的第一个时钟上升沿到来时:a = 0,b = 1,c = 2;第二个时钟上升沿到来时:a = 0,b = 0,c = 1;第三个时钟上升沿到来时:a = 0,b = 0,c = 0。

              🔸观察上述代码生成的RTL视图:

              阻塞赋值:

              【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,RTL6,第15张

              非阻塞赋值:

              【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,RTL7,第16张

              • 在描述组合逻辑的 always 块中用阻塞赋值,则综合成组合逻辑的电路结构。
              • 在描述时序逻辑的 always 块中用非阻塞赋值,则综合成时序逻辑的电路结构。

                五、六个原则

                对于阻塞赋值与非阻塞赋值,在 《小梅哥FPGA设计思想与验证方法》 中提及了六个原则,可解决在综合后仿真中出现的绝大多数的冒险竞争问题:

                1. 时序电路建模时,用非阻塞赋值;
                2. 锁存器电路建模时,用非阻塞赋值;
                3. 用 always块建立组合逻辑模型时,用阻塞赋值;
                4. 在同一个 always块中建立时序和组合逻辑电路时,用非阻塞赋值;
                5. 在同一个 always块中不要既用非阻塞赋值又用阻塞赋值;
                6. 不要在一个以上的 always块中为同一个变量赋值。

                【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,csdn,第17张

                🧸结尾


                • ❤️ 感谢您的支持和鼓励! 😊🙏
                • 📜您可能感兴趣的内容:
                • 【stm32开发】stm32+oled最小系统板资料(原理图、PCB、示例代码)【六一】
                • 【FPGA零基础学习之旅#7】BCD计数器设计
                • 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
                • 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制

                  【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解,遇见未来,第18张