🎉欢迎来到FPGA专栏~阻塞赋值与非阻塞赋值
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
- ✨博客主页:小夏与酒的博客
- 🎈该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
- 📜 欢迎大家关注! ❤️
![]()
阻塞赋值,操作符为 “ = ”,“阻塞”是指在进程语句(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视图:
对于上述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视图为:
📜观察上述两个RTL视图,当交换计算顺序之后,出现了两级寄存器,导致代码的实现电路是不一样的。
对上述编写的计算过程使用非阻塞赋值方式实现,即:
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视图为:
同时,我们改变计算顺序,再观察一下:
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视图为:
📜由于该部分是使用非阻塞赋值的方式实现计算,所以当计算顺序改变之后,RTL视图并不会出现改变。
对非阻塞赋值方式进行仿真:
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
设置好仿真脚本之后进行仿真,需要注意好细节部分的波形图:
之后观察后仿真:
在后仿真中添加子模块之后,会产生很多的信号,这些信号都是由布局布线生成的中间信号。放大波形之后,仔细观察各数据的变化:
从上图的波形仿真中可以看出,非阻塞赋值并不会受到上一语句的影响,即:
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处于未知态:
out的值只与上一时刻的计算结果有关。
为了解决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视图:
📜从上述RTL视图中可以看出:a + b + c执行的是组合逻辑运算。
对于阻塞赋值与非阻塞赋值的区别,本部分只做简单讲解,不会太深入内部原理。
在always中使用的赋值语句有两种:阻塞赋值和非阻塞赋值。赋值语句的左端都必须是reg类型,这是语法的强制要求,所以always结构中所有语句的左端变量都必须是reg型,这是与assign语句不同的,请格外留意。
🔸先看阻塞赋值:
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
直接看波形仿真结果:
可见,当复位信号变为高电平时,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
测试激励文件保持不变,观察波形图:
分析以下代码与仿真结果:
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设计思想与验证方法》 中提及了六个原则,可解决在综合后仿真中出现的绝大多数的冒险竞争问题:
🧸结尾
- ❤️ 感谢您的支持和鼓励! 😊🙏
- 📜您可能感兴趣的内容:
- 【stm32开发】stm32+oled最小系统板资料(原理图、PCB、示例代码)【六一】
- 【FPGA零基础学习之旅#7】BCD计数器设计
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制
![]()