zoukankan      html  css  js  c++  java
  • CPLD/FPGA/Verilog_Verilog中阻塞与非阻塞的区别

    转自:http://blog.csdn.net/yangtalent1206/article/details/6430119

    在Verilog中有两种类型的赋值语句:阻塞赋值语句(“=”)和非阻塞赋值语句(“<=”)。正确地使用这两种赋值语句对于Verilog的设计和仿真非常重要。下面我们以例子说明阻塞和非阻塞赋值的区别。

      我们先来看几段代码及其对应的电路

     

    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
    
    1.jpg▲ 大家可以看到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
    
    3.jpg
    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
    
    3.jpg
    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
    
    2.jpg
    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
    
    3.jpg
    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
    
    2.jpg


      从上面的例子中,我们可以看出,在阻塞赋值语句中,赋值的次序非常重要,而在非阻塞赋值语句中,赋值的次序并不重要。

     


     

      下面我们具体分析一下阻塞和非阻塞赋值的语义本质:

      阻塞:在本语句中“右式计算”和“左式更新”完全完成之后,才开始执行下一条语句;
      非阻塞:当前语句的执行不会阻塞下一语句的执行。

     


      先看阻塞赋值的情况:

      我们来看这段代码:

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

      always语句块对Clk的上升沿敏感,当发生Clk 0~1的跳变时,执行该always语句。

      在begin...end语句块中所有语句是顺序执行的,而且最关键的是,阻塞赋值是在本语句中“右式计算”和“左式更新”完全完成之后,才开始执行下一条语句的。

      在本例中,D的值赋给Q1以后,再执行Q2 = Q1;同样在Q2的值更新以后,才执行Q3 = Q2。这样,最终的计算结果就是Q3 = D。

      所有的语句执行完以后,该always语句等待Clk的上升沿,从而再一次触发begin...end语句。

     


      接下来,再看看非阻塞赋值的情况。

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

    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。

      注:

        *仿真器首先按照仿真时间对事件进行排序,然后再在当前仿真时间里按照事件的优先级顺序进行排序。

        *活跃事件是优先级最高的事件。在活跃事件之间,它们的执行顺序是随机的。阻塞赋值(=)、连续赋值(assign)以及非阻塞赋值的右式计算等都属于活跃事件。

     


     

      下面通过一个典型案例,进一步说明阻塞赋值和非阻塞赋值的区别。

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

    module Bubble_Up(
                        Rst_n,
                        Clk,
                        Data,
                        Lid_Min
                    );
    
    input Rst_n;
    input Clk;
    input [5:0] Data [0:3];
    
    output [1:0] Lid_Min;
    
    reg [1:0] Lid_Min; 
    
        always @(posedge Clk or negedge Rst_n)
            begin
                if (~Rst_n)
                    begin
                        Lid_Min <= 2'd0;
                    end
                else
                    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
            end
    
    endmodule
    

      我们的原意是首先将Lid_Min设置为一个初始值(任意值都可以),然后将Data[0]~Data[3]与Data[Lid_Min]进行比较,每比较一个数,就将较小的索引暂存在Lid_Min中,然后再进行下一次比较。当4组数据比较完成之后,最小的数据索引就会保留在Lid_Min中。

      我们在以上代码中使用了非阻塞赋值,结果发现,仿真波形根本不是我们所需要的功能,如图所示,图中的Data[0]~Data[3]分别为11、3、10和12,Lid_Min的初始值为0。按道理来说,Lid_Min的计算结果应该为1,因为Data[1]最小,但仿真波形却为2。

    4.jpg

      为什么会得出这样的结果呢?

      在时钟上升沿到来以后,且Rst_n信号无效时开始执行以下4个语句,假设这时候的Lid_Min是0,Data[0]~Data[3]分别为11、3、10和12:

    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
    

      第一句的if为真,因此执行Lid_Min <= 2’d0,而这时候,Lid_Min并没有立刻被赋值,而是调度到事件队列中等待执行,这是非阻塞赋值的特点。

      第二句的if为真,因此执行Lid_Min <= 2’d1,这是Lid_Min也没有立刻被赋值为1,而是调度到事件队列中等待执行。当前的Lid_Min还是0,没有发生任何变化。

      同样,第三句的if也为真,因此执行Lid_Min <= 2’d2,将更新事件调度到事件队列中等待执行。当前的Lid_Min还是0。

      而第四句的if为假,因此直接跳过Lid_Min <= 2’d3,这时跳出always语句,等待下一个时钟上升沿。

      在以上的always语句执行完成以后,仿真时间没有前进。这时存在于事件队列中当前仿真时间上的3个被调度的非阻塞更新事件开始执行,它们分别将Lid_Min更新为0、1和2。

      按照Verilog语言的规范,这3个更新事件属于同一仿真时间内的事件,它们之间的执行顺序随机,这就产生了不确定性。一般的仿真器在实现的时候是根据它们被调度的先后顺序执行的,事件队列就像一个存放事件的FIFO,它是分层事件队列的一部分,如图所示:

    5.jpg

      这3个事件在同一仿真时间被一一执行,而真正起作用的时最后一个更新事件,因此在仿真的时候得到的最终结果时Lid_Min为2。

      然后我们想要得到的结果是,在每个if语句判断并执行完成以后,Lid_Min先暂存这个中间值,再进行下一次比较,也就是说在进行下一次比较之前,这个Lid_Min必须被更新,而这一点也正是阻塞赋值的特点,因此我们将代码作如下更改:

    module Bubble_Up(
                        Rst_n,
                        Clk,
                        Data,
                        Lid_Min
                    );
    
    input Rst_n;
    input Clk;
    input [5:0] Data [0:3];
    
    output [1:0] Lid_Min;
    
    reg [1:0] Lid_Min; 
    
        always @(posedge Clk or negedge Rst_n)
            begin
                if (~Rst_n)
                    begin
                        Lid_Min <= 2'd0;
                    end
                else
                    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
            end
    
    endmodule
    

      其仿真波形如图所示:

    6.jpg

      在代码仿真过程中,第二句的if为真,执行Lid_Min = 2'd1,根据阻塞赋值的特点,Lid_Min被立刻赋值为1。在执行第三句if的时候,if (Data[2] <= Data[Lid_Min])为假,直接跳过Lid_Min = 2'd2不执行,同样也跳过Lid_Min = 2'd3不执行。Lid_Min被最终赋值为1,这正是我们想要的结果。

      另外,为了使代码看起来更简洁,我们使用for语句改写了代码:

    module Bubble_Up(
                        Rst_n,
                        Clk,
                        Data,
                        Lid_Min
                    );
    
    input Rst_n;
    input Clk;
    input [5:0] Data [0:3];
    
    output [1:0] Lid_Min;
    
    reg [1:0] Lid_Min; 
    
    integer i;
    
        always @(posedge Clk or negedge Rst_n)
            begin
                if (~Rst_n)
                    begin
                        Lid_Min = 2'd0;
                    end
                else
                    begin
                        for (i = 2'd0; i <= 2'd3; i = i + 2'd1)
                            begin
                                if (Data[i] <= Data[Lid_Min])
                                    begin
                                        Lid_Min = i;
                                    end
                            end
                    end
            end
    
    endmodule
    

      这种写法与前面展开的写法完全等效,功能完全一致。今后大家在读代码时发现带有for语句的电路功能比较难理解,可以将这些语句展开,增强代码的可读性。


  • 相关阅读:
    SDUT 2143 图结构练习——最短路径 SPFA模板,方便以后用。。 Anti
    SDUT ACM 1002 Biorhythms 中国剩余定理 Anti
    nyist OJ 119 士兵杀敌(三) RMQ问题 Anti
    SDUT ACM 2157 Greatest Number Anti
    SDUT ACM 2622 最短路径 二维SPFA启蒙题。。 Anti
    二叉索引树 区间信息的维护与查询 Anti
    SDUT ACM 2600 子节点计数 Anti
    UVA 1428 Ping pong 二叉索引树标准用法 Anti
    2010圣诞Google首页效果
    Object
  • 原文地址:https://www.cnblogs.com/youngforever/p/3104743.html
Copyright © 2011-2022 走看看