Verilog中的阻塞与非阻塞

时间:2020-12-28 08:30:58

这篇文档值得阅读  

按说阻塞与非阻塞是Verilog中最基本的东西,也是老生常谈。但是最近看到很多程序里用到阻塞语句竟然不是很明白,说到底是从来没有自己仔细分析过。当然一般情况程序中也是推荐用非阻塞的。

  一般来说大家都会用以下几个例子来说明阻塞与非阻塞:

大家可以参考http://www.cnblogs.com/crazybingo/archive/2012/03/20/2408980.html 

 

 

HDL源代码
对应的RTL电路
module Shifter1(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 = D;
Q2 = Q1;
Q3 = Q2;
end
endmodule
Verilog中的阻塞与非阻塞

 

▲ 大家可以看到Q1、Q2被优化掉了
module Shifter2(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 <= Q2;
end
endmodule
Verilog中的阻塞与非阻塞

 

module Shifter3(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q3 = Q2;
Q2 = Q1;
Q1 = D;
end
endmodule
Verilog中的阻塞与非阻塞

 

module Shifter4(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;
always @(posedge Clk)
begin
Q1 <= D;
Q2 = Q1;
Q3 = Q2;
end
endmodule
Verilog中的阻塞与非阻塞

 

module Shifter5(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 = Q2;
end
endmodule
Verilog中的阻塞与非阻塞

 

module Shifter6(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;
always @(posedge Clk)
begin
Q1 <= D;
Q2 = Q1;
Q3 <= Q2;
end
endmodule
Verilog中的阻塞与非阻塞

 

 从上面的例子可以看出:

(1)阻塞语句定义的寄存器是可能被优化掉的

(2)阻塞语句定义的寄存器是否被优化是与语句顺序有关系的

参考文档中提到非阻塞语句执行的过程中先把每条语句当作一个事件放入事件队列中来执行的。它能很方便帮助理解非阻塞过程。

 

 所谓非阻塞赋值,顾名思义,就是指当前语句的执行不会阻塞下一语句的执行。

 

 always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 <= Q2;
end

 

   首先执行Q1 <= D,产生一个更新事件,将D的当前值赋给Q1,但是这个赋值过程并没有立刻执行,而是在事件队列中处于等待状态。
        然后执行Q2 <= Q1,同样产生一个更新事件,将Q1的当前值(注意上一语句中将D值赋给Q1的过程并没有完成,Q1还是旧值)赋给Q2,这个赋值事件也将在事件队列中处于等待状态。
        再执行Q3 <= Q2,产生一个更新事件,将Q2的当前值赋给Q3,这个赋值事件也将在事件队列中等待执行。
        这时always语句块执行完成,开始对下一个Clk上升沿敏感。

 

  那么什么时候才执行那3个在事件队列中等待的事件呢?只有当当前仿真时间内的所有活跃事件和非活跃事件都执行完成后,才开始执行这些非阻塞赋值的更新事件。这样就相当于将D、Q1和Q2的值同时赋给了Q1、Q2和Q3。

 

 下面是参考中的例子及阻塞实现过程:

这里有一个数组:Data[0]、Data[1]、Data[2]和Data[3],它们都是4比特的数据。我们需要在它们当中找到一个最小的数据,同时将该数据的索引输出到LidMin中,这个算法有点类似于“冒泡排序”的过程,而且需要在一个时钟周期内完成。例如,如果这4个数据中 Data[2]最小,那么LidMin的值则为2。

module Bubble_Up(                                                 
                    Rst_n,                                        
                    Clk,                                          
                    Data0, 
                    Data1,
                    Data2,
                    Data3,                                        
                    Lid_Min                                       
                    );                                            
    input Rst_n;                                                  
    input Clk;   
    
    input [3:0] Data0;
    input [3:0] Data1;
    input [3:0] Data2;
    input [3:0] Data3;                                                 
    reg [3:0] Data [3:0];                                       
    output [1:0] Lid_Min;                                         
    reg [1:0] Lid_Min;      
    
    always @( posedge Clk )
    begin
            Data[0] <= Data0;
            Data[1] <= Data1;
            Data[2] <= Data2;
            Data[3] <= Data3;
    end                                      
    always @(posedge Clk   )                        
    begin                                                         
                                                                                                             
               if (Data[0] <= Data[Lid_Min])    //"<="表示小于等于
               begin                                              
                   Lid_Min = 2'd0;    //"<="表示非阻塞赋值        
               end                                                
                                                                  
               if (Data[1] <= Data[Lid_Min])                      
               begin                                              
                   Lid_Min = 2'd1;                                
               end                                                
                                                                  
                if (Data[2] <= Data[Lid_Min])                     
                begin                                             
                    Lid_Min = 2'd2;                               
                end                                               
                                                                  
                if (Data[3] <= Data[Lid_Min])                     
                begin                                             
                    Lid_Min = 2'd3;                               
                end                                               
        end                                                       
    endmodule   

 

Verilog中的阻塞与非阻塞

从实现的RTL视图上,我突然觉得阻塞一步到位的完成了很多组合逻辑。接下来我再说一些在程序上经常会遇到的例子。

reg current_state; reg next_state; reg inc_loops; reg clr_loops; reg set_valid; reg [4-1:0] loops; reg [23:0] threshold; reg [28-1:0] dim_cnt_thresh_r; reg [28-1:0] cnt_thresh; always @ (posedge clk ) begin current_state <= next_state; end

always @ (*) begin next_state = current_state; clr_loops = 1'b0;
    inc_loops = 1'b0;
    set_valid = 1'b0;
      
    case (current_state) 0: begin clr_loops = 1'b1;
            if (measure_valid) begin next_state = 1'b1;
            end        
        end
        
        1: begin inc_loops = 1'b1;
            if (match | loops_max) begin set_valid = 1'b1;
               clr_loops = 1'b1;
               next_state = 1'b0;
            end                         
        end
    endcase  
end 

 

 上面一段程序,从RTL视图可以看到,除current_state为寄存器之外,其余全部综合为了组合逻辑,所以说,reg定义的数据也不全会综合成寄存器。但是贴出程序还是为了要体会阻塞的用法。

好像记得有这样的说法:在组合逻辑中用阻塞,在时序逻辑中用非阻塞。

如果把上面的程序修改成非阻塞赋值,如下,这样会不会存在问题呢?我个人认为有可能,可能要看综合器本身,如果case语句外的next_state <= current_state与case内的next_state <= 1'b0(或者1‘b1)是随机的,那状态可能会出错。不会一般都不会(因为我以前就有这样做过,没问题(>'~'<))

 

reg current_state; reg next_state; reg inc_loops; reg clr_loops; reg set_valid; reg [4-1:0] loops; reg [23:0] threshold; reg [28-1:0] dim_cnt_thresh_r; reg [28-1:0] cnt_thresh; always @ (posedge clk ) begin current_state <= next_state; end

always @ (*) begin next_state <= current_state; clr_loops <= 1'b0;
    inc_loops <= 1'b0;
    set_valid <= 1'b0;
      
    case (current_state) 0: begin clr_loops <= 1'b1;
            if (measure_valid) begin next_state <= 1'b1;
            end        
        end
        
        1: begin inc_loops <= 1'b1;
            if (match | loops_max) begin set_valid <= 1'b1;
               clr_loops <= 1'b1;
               next_state <= 1'b0;
            end                         
        end
    endcase  
end 

 

 不过在这篇博客中,也给出了一种表达

 
module and_nonblocking
(
  input      a,
  input      b,
  input      c,
  output reg y
); 
 
always@*
begin               // y_entry = y
  y <= a;           // y_exit  = a
  y <= y & b;       // y_exit  = y_entry & b
  y <= y & c;       // y_exit  = y_entry & c
end                 // y       = y_exit
 
endmodule

 

注意always块内的前2条语句将不会产生任何效果。上述always块等价与:

always@*       
  y <= y & c;