• 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语句的电路功能比较难理解,可以将这些语句展开,增强代码的可读性。


  • 相关阅读:
    【Python五篇慢慢弹(3)】函数修行知python
    【Python五篇慢慢弹】数据结构看python
    【项目管理】GitHub使用操作指南
    【Python五篇慢慢弹】快速上手学python
    【NLP】十分钟快览自然语言处理学习总结
    【NLP】条件随机场知识扩展延伸(五)
    【NLP】基于统计学习方法角度谈谈CRF(四)
    【NLP】基于机器学习角度谈谈CRF(三)
    【NLP】基于自然语言处理角度谈谈CRF(二)
    【NLP】前戏:一起走进条件随机场(一)
  • 原文地址:https://www.cnblogs.com/youngforever/p/3104743.html
Copyright © 2020-2023  润新知