07-阻塞赋值与非阻塞赋值原理分析——小梅哥FPGA设计思想与验证方法视频教程配套文档
芯航线——普利斯队长精心奉献
实验目的:掌握阻塞赋值与非阻塞赋值的区别
实验平台:无
实验原理:
阻塞赋值,操作符为"=","阻塞"是指在进程语句(initial和always)中,当前的赋值语句阻断了其后的语句,也就是说后面的语句必须等到当前的赋值语句执行完毕才能执行。而且阻塞赋值可以看成是一步完成的,即:计算等号右边的值并同时赋给左边变量。
非阻塞赋值,操作符为"<=","非阻塞"是指在进程语句(initial和always)中,当前的赋值语句不会阻断其后的语句。
实验步骤:
为了详细说明阻塞赋值与非阻塞赋值对实际形成电路的影响,以下写了五个设计。其中端口列表均为以下所示,各部分代码不再重复。
module block_nonblock(Clk,Rst_n,a,b,c,out); input Clk; input Rst_n; input a,b,c; output reg [1:0]out; 。。。。。。。。。。。。。。。 endmodule |
首先在时序电路中使用阻塞赋值的方式,生成一个加法器。这种方式生成的实际逻辑电路如图7-1所示。
reg [1:0] d; always@(posedge Clk or negedge Rst_n) if(!Rst_n) out = 2'b0; else begin d = a + b ; out = d + c; end |
图7-1
现在把阻塞赋值的两条语句顺序颠倒一下,再次综合可以得到图7-2所示的逻辑电路。可以在调整顺序后与不调整时生成的逻辑电路不一致。现结合实验原理部分给出详细解释,当执行out = d + c时,d的数据此时并不是更新后a+b的数据,而是上一个Clk上升沿到来时d的数据,这也就解释了为何还有一个D触发器的存在。通俗讲阻塞,out的这条语句阻塞了d的语句执行。对比图7-1的代码,由于d的语句在out的前面,虽然使用了阻塞赋值但是相当于out=a+b+c。
reg [1:0] d; always@(posedge Clk or negedge Rst_n) if(!Rst_n) out = 2'b0; else begin out = d + c; d = a + b; end |
图7-2
现在把赋值方式改为非阻塞赋值,进行综合后可以看到如图7-3所示的逻辑电路。
reg [1:0] d; always@(posedge Clk or negedge Rst_n) if(!Rst_n) out <= 2'b0; else begin d <= a + b; out <= d + c; end |
图7-3
现在使用非阻塞方式,交换语句执行顺序,综合后实现的逻辑电路如图7-4所示。这里由于采用的非阻塞赋值,因此交换语句前后顺序并不会对最终生成的逻辑电路有实际影响。
reg [1:0] d; always@(posedge Clk or negedge Rst_n) if(!Rst_n) out <= 2'b0; else begin out <= d + c; d <= a + b; end |
图7-4
为了在其各自的时序图中更直观的观察效果,新建仿真block_nonblock_tb.v文件保存到testbench文件夹下,输入以下内容再次进行分析和综合直至没有错误以及警告。本激励文件除产生正常的时钟以及复位信号外,还生成了a、b、c三个信号。这里调用待仿真文件使用的调用方式是显式调用,这种方式要求调用时信号顺序需要与编写的文件顺序一致且不能在一个激励文件中调用两次。可以看出这种方式容易出错,且具有局限性,不推荐使用因此之后的例子均不采用,此处只做介绍。
`timescale 1ns/1ns `define clock_period 20
module block_nonblock_tb;
reg Clock; reg Rst_n; reg a,b,c;
wire [1:0]out;
block_nonblock block_nonblock0(Clock,Rst_n,a,b,c,out);
initial Clock = 1; always#(`clock_period/2) Clock = ~Clock;
initial begin Rst_n = 1'b0; a = 0; b = 0; c = 0; #(`clock_period*200 + 1); Rst_n = 1'b1; #(`clock_period*200); 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 |
设置好仿真脚本后进行功能仿真,可以看到如图7-5所示的波形文件,可以看出在复位信号置高之前输出为0。直观看上去没有问题。现在放大细节可以看出如图
图7-5
变化在第一个时钟沿之后因此第一个时钟沿检测不到,下一个时钟检测到011 直接赋值计算。
图7-6
放大细节可以看出,在第一个上升沿out还是0,这是由于虽然是非阻塞赋值并且d已经更新为1,但是实际电路中总会存在延迟,这个时钟沿out已经采不到当前d数据了还是采到0。为了更好的说明进行门级仿真。
图7-7
全编译后进行门级后仿,可以在图7-8清晰的看出这种现象。
图7-8
再次改为非阻塞赋值,如下所示综合出来如图7-9所示。可以与图7-1比较分析。
always@(posedge Clk or negedge Rst_n) if(!Rst_n) out <= 2'b0; else begin out <= a + b + c; end |
图7-9
本节对比了 Verilog 语法中阻塞赋值和非阻塞赋值的区别,通过证明非阻塞赋值多种赋值顺序生产电路的唯一性,与非阻塞赋值多种赋值书序生成电路的不确定性,来展示使用非阻塞赋值对设计可预测性的重要意义
掌握可综合风格的Verilog模块编程的八个原则会有很大的帮助。在编写时牢记这八个要点可以为绝大多数的Verilog用户解决在综合后仿真中出现的90-100% 的冒险竞争问题。
1) 时序电路建模时,用非阻塞赋值。
2) 锁存器电路建模时,用非阻塞赋值。
3) 用always块建立组合逻辑模型时,用阻塞赋值。
4) 在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。
5) 在同一个always块中不要既用非阻塞赋值又用阻塞赋值。
6) 不要在一个以上的always块中为同一个变量赋值。
7) 用$strobe系统任务来显示用非阻塞赋值的变量值
8) 在赋值时不要使用 #0 延迟