Verilog 競爭與冒險

2022-05-18 09:51 更新

關(guān)鍵字:競爭,冒險,書寫規(guī)范

產(chǎn)生原因

數(shù)字電路中,信號傳輸與狀態(tài)變換時都會有一定的延時。

  • 在組合邏輯電路中,不同路徑的輸入信號變化傳輸?shù)酵稽c門級電路時,在時間上有先有后,這種先后所形成的時間差稱為競爭(Competition)。
  • 由于競爭的存在,輸出信號需要經(jīng)過一段時間才能達到期望狀態(tài),過渡時間內(nèi)可能產(chǎn)生瞬間的錯誤輸出,例如尖峰脈沖。這種現(xiàn)象被稱為冒險(Hazard)。
  • 競爭不一定有冒險,但冒險一定會有競爭。

例如,對于給定邏輯 ?F = A & A'?,電路如左下圖所示。

由于反相器電路的存在,信號 ?A'? 傳遞到與門輸入端的時間相對于信號 ?A? 會滯后,這就可能導(dǎo)致與門最后的輸出結(jié)果 ?F? 會出現(xiàn)干擾脈沖。如下圖所示。



其實實際硬件電路中,只要門電路各個輸入端延時不同,就有可能產(chǎn)生競爭與冒險。

例如一個簡單的與門,輸入信號源不一定是同一個信號變換所來,由于硬件工藝、其他延遲電路的存在,也可能產(chǎn)生競爭與冒險,如下圖所示。


判斷方法

代數(shù)法

在邏輯表達式,保持一個變量固定不動,將剩余其他變量用 0 或 1 代替,如果最后邏輯表達式能化簡成?Y = A + A'?或?Y = A · A'?的形式,則可判定此邏輯存在競爭與冒險。

例如邏輯表達式 ?Y = AB + A'C?,在 ?B=C=1? 的情況下,可化簡為 ?Y = A + A'?。顯然,?A? 狀態(tài)的改變,勢必會造成電路存在競爭冒險。

卡諾圖法

有兩個相切的卡諾圈,并且相切處沒有其他卡諾圈包圍,可能會出現(xiàn)競爭與冒險現(xiàn)象。

例如左下圖所存在競爭與冒險,右下圖則沒有。


其實,卡諾圖本質(zhì)上還是對邏輯表達式的一個分析,只是可以進行直觀的判斷。

例如,左上圖邏輯表達式可以簡化為 ?Y = A'B' + AC?,當 ?B=0? 且 ?C=1? 時,此邏輯表達式又可以表示為 ?Y = A' + A?。所以肯定會存在競爭與冒險。

右上圖邏輯表達式可以簡化為 ?Y = A'B' + AB?,顯然 B 無論等于 1 還是 0,此式都不會化簡成 ?Y = A' + A?。所以此邏輯不存在競爭與冒險。

需要注意的是,卡諾圖是首尾相臨的。如下圖所示,雖然看起來兩個卡諾圈并沒有相切,但實際上,m6 與 m4 也是相鄰的,所以下面卡諾圖所代表的數(shù)字邏輯也會產(chǎn)生競爭與冒險。


其他較為復(fù)雜的情況,可能需要采用 "計算機輔助分析 + 實驗" 的方法。

消除方法

對數(shù)字電路來說,常見的避免競爭與冒險的方法主要有 4 種。

  1. 增加濾波電容,濾除窄脈沖
  2. 此種方法需要在輸出端并聯(lián)一個小電容,將尖峰脈沖的幅度削弱至門電路閾值以下。

    此方法雖然簡單,但是會增加輸出電壓的翻轉(zhuǎn)時間,易破壞波形。

  3. 修改邏輯,增加冗余項
  4. 利用卡諾圖,在兩個相切的圓之間,增加一個卡諾圈,并加在邏輯表達式之中。

    如下圖所示,對數(shù)字邏輯 ?Y = A'B' + AC? 增加冗余項 ?B'C?,則此電路邏輯可以表示為 ?Y = A'B' + AC + B'C?。此時電路就不會再存在競爭與冒險。


  5. 使用時鐘同步電路,利用觸發(fā)器進行打拍延遲
  6. 同步電路信號的變化都發(fā)生在時鐘邊沿。對于觸發(fā)器的 D 輸入端,只要毛刺不出現(xiàn)在時鐘的上升沿并且不滿足數(shù)據(jù)的建立和保持時間,就不會對系統(tǒng)造成危害,因此可認為 D 觸發(fā)器的 D 輸入端對毛刺不敏感。 利用此特性,在時鐘邊沿驅(qū)動下,對一個組合邏輯信號進行延遲打拍,可消除競爭冒險。

    延遲一拍時鐘時,會一定概率的減少競爭冒險的出現(xiàn)。實驗表明,最安全的打拍延遲周期是 3 拍,可有效減少競爭冒險的出現(xiàn)。

    當然,最終還是需要根據(jù)自己的設(shè)計需求,對信號進行合理的打拍延遲。

    為說明對信號進行打拍延遲可以消除競爭冒險,我們建立下面的代碼模型。

    module competition_hazard
        (
          input             clk ,
          input             rstn ,
          input             en ,
          input             din_rvs ,
          output reg        flag
        );
    
        wire    condition = din_rvs & en ;  //combination logic
        always @(posedge clk or negedge !rstn) begin
            if (!rstn) begin
                flag   <= 1'b0 ;
            end
            else begin
                flag   <= condition ;
            end
        end
    
    endmodule

    testbench 描述如下:

    `timescale 1ns/1ns
    
    module test ;
        reg          clk, rstn ;
        reg          en ;
        reg          din_rvs ;
        wire         flag_safe, flag_dgs ;
    
        //clock and rstn generating
        initial begin
            rstn              = 1'b0 ;
            clk               = 1'b0 ;
            #5 rstn           = 1'b1 ;
            forever begin
                #5 clk = ~clk ;
            end
        end
    
        initial begin
            en        = 1'b0 ;
            din_rvs   = 1'b1 ;
            #19 ;      en        = 1'b1 ;
            #1 ;       din_rvs   = 1'b0 ;
        end
    
        competition_hazard         u_dgs
         (
          .clk              (clk           ),
          .rstn             (rstn          ),
          .en               (en            ),
          .din_rvs          (din_rvs       ),
          .flag             (flag_dgs      ));
    
        initial begin
            forever begin
                #100;
                if ($time >= 1000)  $finish ;
            end
        end
    
    endmodule // test

    仿真結(jié)果如下:

    由圖可知,信號 ?condition ?出現(xiàn)了一個尖峰脈沖,這是由于信號 ?din_rvs ?與信號 ?en ?相對于模塊內(nèi)部時鐘都是異步的,所以到達內(nèi)部門電路時的延時是不同的,就有可能造成競爭冒險。

    雖然最后的仿真結(jié)果 ?flag ?一直為 0,似乎是我們想要的結(jié)果。但是實際電路中,這個尖峰脈沖在時間上非常靠近時鐘邊沿,就有可能被時鐘采集到而產(chǎn)生錯誤結(jié)果。


    下面我們對模型進行改進,增加打拍延時的邏輯,如下:

    module clap_delay
        (
          input             clk ,
          input             rstn ,
          input             en ,
          input             din_rvs ,
          output reg        flag
        );
    
        reg                  din_rvs_r ;
        reg                  en_r ;
        always @(posedge clk or !rstn) begin
            if (!rstn) begin
                din_rvs_r      <= 1'b0 ;
                en_r           <= 1'b0 ;
            end
            else begin
                din_rvs_r      <= din_rvs ;
                en_r           <= en ;
            end
        end
    
        wire                 condition = din_rvs_r & en_r ;
        always @(posedge clk or negedge !rstn) begin
            if (!rstn) begin
                flag   <= 1'b0 ;
            end
            else begin
    
                flag   <= condition ;
            end
        end // always @ (posedge clk or negedge !rstn)
    
    endmodule

    將此模塊例化到上述 testbench 中,得到如下仿真結(jié)果。

    由圖可知,信號 ?condition ?沒有尖峰脈沖的干擾了,仿真結(jié)果中 ?flag ?為 0 也如預(yù)期。

    其實,輸入信號與時鐘邊沿非常接近的情況下,時鐘對輸入信號的采樣也存在不確定性,但是不會出現(xiàn)尖峰脈沖的現(xiàn)象。對輸入信號多打 2 拍,是更好的處理方式,對競爭與冒險有更好的抑制作用。


  7. 采用格雷碼計數(shù)器
  8. 遞加的多 bit 位計數(shù)器,計數(shù)值有時候會發(fā)生多個 bit 位的跳變。

    例如計數(shù)器變量 counter 從 5 計數(shù)到 6 時, 對應(yīng)二進制數(shù)字為 4'b101 到 4'b110 的轉(zhuǎn)換。因為各 bit 數(shù)據(jù)位的延時,counter 的變換過程可能是: ?4'b101 -> 4'b111 -> 4'b110?。如果有以下邏輯描述,則信號 cout 可能出現(xiàn)短暫的尖峰脈沖,這顯然是與設(shè)計相悖的。

    cout = counter[3:0] == 4'd7 ;     

    而格雷碼計數(shù)器,計數(shù)時相鄰的數(shù)之間只有一個數(shù)據(jù) bit 發(fā)生了變化,所以能有效的避免競爭冒險。

    好在 Verilog 設(shè)計時,計數(shù)器大多都是同步設(shè)計。即便計數(shù)時存在多個 bit 同時翻轉(zhuǎn)的可能性,但在時鐘驅(qū)動的觸發(fā)器作用下,只要信號間滿足時序要求,就能消除掉 100% 的競爭與冒險。

小結(jié)

一般來說,為消除競爭冒險,增加濾波電容和邏輯冗余,都不是 Verilog 設(shè)計所考慮的。

計數(shù)采用格雷碼計數(shù)器,大多數(shù)也是應(yīng)用在高速時鐘下減少信號翻轉(zhuǎn)率來降低功耗的場合。

利用觸發(fā)器在時鐘同步電路下對異步信號進行打拍延時,是 Verilog 設(shè)計中經(jīng)常用到的方法。

除此之外,為消除競爭冒險,Verilog 編碼時還需要注意一些問題,詳見下一小節(jié)。

Verilog 書寫規(guī)范

在編程時多注意以下幾點,也可以避免大多數(shù)的競爭與冒險問題。

  1. 時序電路建模時,用非阻塞賦值。
  2. 組合邏輯建模時,用阻塞賦值。
  3. 在同一個 always 塊中建立時序和組合邏輯模型時,用非阻塞賦值。
  4. 在同一個 always 塊中不要既使用阻塞賦值又使用非阻塞賦值。
  5. 不要在多個 always 塊中為同一個變量賦值。
  6. 避免 latch 產(chǎn)生。

下面,對以上注意事項逐條分析。

  1. 時序電路建模時,用非阻塞賦值
  2. 前面講述非阻塞賦值時就陳述過,時序電路中非阻塞賦值可以消除競爭冒險。

    例如下面代碼描述,由于無法確定 a 與 b 阻塞賦值的操作順序,就有可能帶來競爭冒險。

    always @(posedge clk) begin
        a = b ;
        b = a ;
    end

    而使用非阻塞賦值時,賦值操作是同時進行的,所以就不會帶來競爭冒險,如以下代碼描述。

    always @(posedge clk) begin
        a <= b ;
        b <= a ;
    end
  3. 組合邏輯建模時,用阻塞賦值
  4. 例如,我們想實現(xiàn) C = A&B, F=C&D 的組合邏輯功能,用非阻塞賦值語句如下。

    兩條賦值語句同時賦值,F(xiàn) <= C & D 中使用的是信號 C 的舊值,所以導(dǎo)致此時的邏輯是錯誤的,F(xiàn) 的邏輯值不等于 A&B&D。

    而且,此時要求信號 C 具有存儲功能,但不是時鐘驅(qū)動,所以 C 可能會被綜合成鎖存器(latch),導(dǎo)致競爭冒險。

    always @(*) begin
        C <= A & B ;
        F <= C & D ;
    end

    對代碼進行如下修改,F(xiàn) = C & D 的操作一定是在 C = A & B 之后,此時 F 的邏輯值等于 A&B&D,符合設(shè)計。

    always @(*) begin
        C = A & B ;
        F = C & D ;
    end
  5. 在同一個 always 塊中建立時序和組合邏輯模型時,用非阻塞賦值
  6. 雖然時序電路中可能涉及組合邏輯,但是如果賦值操作使用非阻塞賦值,仍然會導(dǎo)致如規(guī)范 1 中所涉及的類似問題。

    例如在時鐘驅(qū)動下完成一個與門的邏輯功能,代碼參考如下。

    always @(posedge clk or negedge rst_n)
        if (!rst_n) begin
            q <= 1'b0;
        end
        else begin
            q <= a & b;  //即便有組合邏輯,也不要寫成:q = a & b
         end
    end
  7. 在同一個 always 塊中不要既使用阻塞賦值又使用非阻塞賦值
  8. always 涉及的組合邏輯中,既有阻塞賦值又有非阻塞賦值時,會導(dǎo)致意外的結(jié)果,例如下面代碼描述。

    此時信號 C 阻塞賦值完畢以后,信號 F 才會被非阻塞賦值,仿真結(jié)果可能正確。

    但如果 F 信號有其他的負載,F(xiàn) 的最新值并不能馬上傳遞出去,數(shù)據(jù)有效時間還是在下一個觸發(fā)時刻。此時要求 F 具有存儲功能,可能會被綜合成 latch,導(dǎo)致競爭冒險。

    always @(*) begin
        C = A & B ;
        F <= C & D ;
    end

    如下代碼描述,仿真角度看,信號 C 被非阻塞賦值,下一個觸發(fā)時刻才會有效。而 F = C & D 雖然是阻塞賦值,但是信號 C 不是阻塞賦值,所以 F 邏輯中使用的還是 C 的舊值。

    always @(*) begin
        C <= A & B ;
        F = C & D ;
    end

    下面分析假如在時序電路里既有阻塞賦值,又有非阻塞賦值會怎樣,代碼如下。

    假如復(fù)位端與時鐘同步,那么由于復(fù)位導(dǎo)致的信號 q 為 0,是在下一個時鐘周期才有效。

    而如果是信號 a 或 b 導(dǎo)致的 q 為 0,則在當期時鐘周期內(nèi)有效。

    如果 q 還有其他負載,就會導(dǎo)致 q 的時序特別混亂,顯然不符合設(shè)計需求。

    always @(posedge clk or negedge rst_n)
        if (!rst_n) begin  //假設(shè)復(fù)位與時鐘同步
            q <= 1'b0;
        end
        else begin
            q = a & b;  
        end
    end

    需要說明的是,很多編譯器都支持這么寫,上述的分析也都是建立在仿真角度上。實際中如果阻塞賦值和非阻塞賦值混合編寫,綜合后的電路時序?qū)⑹清e亂的,不利于分析調(diào)試。

  9. 不要在多個 always 塊中為同一個變量賦值
  10. 與 C 語言有所不同,Verilog 中不允許在多個 always 塊中為同一個變量賦值。此時信號擁有多驅(qū)動端(Multiple Driver),是禁止的。當然,也不允許 assign 語句為同一個變量進行多次連線賦值。 從信號角度來講,多驅(qū)動時,同一個信號變量在很短的時間內(nèi)進行多次不同的賦值結(jié)果,就有可能產(chǎn)生競爭冒險。

    從語法來講,很多編譯器檢測到多驅(qū)動時,也會報 Error。

  11. 避免 latch 產(chǎn)生
  12. 具體分析見下一章:《避免 Latch》。

點擊這里下載源碼


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號