跳轉到

06.3 通用元件實作:FIFO、Mux、Arbiter#

Cummings 論文文化#

在進入程式碼之前,先講一段真實的歷史。2000 年代的 SNUG(Synopsys Users Group)年會上,Clifford Cummings(Sunburst Design 創辦人)發表的一系列論文成為業界事實標準:Simulation and Synthesis Techniques for Asynchronous FIFO Design(2002 年前後,含 independent-clock FIFO 的格雷碼指標設計)、Asynchronous & Synchronous Reset Design Techniques(重置技術,見 05.3 章)等。這些論文之所以重要,是因為它們指出:FIFO 這種「人人都寫過」的元件,寫對的人非常少——跨時脈 FIFO 的滿/空旗標若用二進位指標跨域比較,會因多位元同時變化的 CDC 問題產生錯誤。Cummings 的解法(格雷碼指標、兩級同步器、方向判斷位元)此後寫進了幾乎所有公司的 design guideline。

「通用元件的正確寫法值得反覆論證與論文發表」這個文化,是本章的背景:通用元件是工程知識密度最高的地方,也因此最值得用參數化語言寫一次、驗證一次、處處使用。

二元 Mux#

最簡單的通用元件:N 選一 mux,選擇訊號為二進位編碼。

module Mux::<W: u32, SEL: u32> (
    sel : input  logic<$clog2(SEL)>,
    din : input  logic<SEL, W>,
    dout: output logic<W>,
) {
    always_comb {
        dout = din[sel];
    }
}

SEL 是輸入數目,W 是資料寬度,din 是 logic<SEL, W> 的二維陣列。用 generics 讓「輸入數目」與「資料寬度」獨立參數化,$clog2(SEL) 自動算出選擇訊號寬度。如果需要 one-hot 選擇的版本,可以再寫一個 MuxOneHot,介面型別不同(sel: logic<SEL>),錯誤的混用會在編譯期被攔截——這就是泛型版的好處。

同步 FIFO(含幾乎滿/空)#

module SyncFifo::<W: u32, DEPTH: u32> (
    clk      : input  clock,
    rst      : input  reset,
    wdata    : input  logic<W>,
    wvalid   : input  logic,
    wready   : output logic,
    rdata    : output logic<W>,
    rvalid   : output logic,
    rready   : input  logic,
    almost_full : output logic,
    almost_empty: output logic,
) {
    param AW: u32 = $clog2(DEPTH);
    param TH : u32 = DEPTH * 3 / 4;

    var mem : logic<DEPTH, W>;
    var wp  : logic<AW>;
    var rp  : logic<AW>;
    var cnt : logic<AW + 1>;
    let full  : logic = cnt == logic<AW + 1>'(DEPTH);
    let empty : logic = cnt == 0;

    always_ff (clk, rst) {
        if_reset {
            wp  = 0;
            rp  = 0;
            cnt = 0;
        } else {
            if wvalid && wready {
                mem[wp] = wdata;
                wp = wp + 1;
                cnt = cnt + 1;
            }
            if rvalid && rready {
                rp = rp + 1;
                cnt = cnt - 1;
            }
            if wvalid && wready && rvalid && rready {
                cnt = cnt; // 同時讀寫時數量不變
            }
        }
    }

    assign wready = !full;
    assign rvalid = !empty;
    assign rdata  = mem[rp];
    assign almost_full  = cnt >= logic<AW + 1>'(TH);
    assign almost_empty = cnt <= logic<AW + 1>'(1);

    always_comb {
    }
}

設計要點:

  • 深度 DEPTH 為 2 的冪次時,指標自然回繞;cnt 多一位元以區分滿與空(或用滿/空判斷式直接比較計數)。
  • 同時讀寫的那一拍,寫與讀各自的指標推進與計數變化要一致——上述寫法以「先各加減、再修正同拍」呈現,實務上更常將寫讀條件組合成一個 case;重點是這個 corner case 必須被明確處理與驗證(Cummings 論文的核心主張之一:FIFO 的 bug 都藏在同時讀寫與邊界條件)。
  • almost_full / almost_empty 以可程式閾值 TH(此處為 3/4 深度)輸出,這是流量控制中比滿旗標更早介入的軟體水位線。
  • 本元件為單時脈同步 FIFO;跨時脈(asynchronous FIFO)需要格雷碼指標與兩級同步器,屬進階主題,此處不展開。

注意 var 與 let 的分工:會被多個條件更新的 wp/rp/cnt/mem 用 var;單一表達式定義的 full/empty 用 let,永遠不可能被 latch 推論污染。

Round-robin Arbiter#

輪詢(round-robin)仲裁器保證公平:每個 requester 依序獲得授權,上次獲授權者排到隊尾。N 個 request 的版本:

module RRArbiter::<N: u32> (
    clk : input  clock,
    rst : input  reset,
    req : input  logic<N>,
    gnt : output logic<N>,
) {
    var r_ptr: logic<$clog2(N)>;

    always_ff (clk, rst) {
        if_reset {
            r_ptr = 0;
        } else {
            if req[r_ptr] {
                r_ptr = (r_ptr + 1) % logic<$clog2(N)>'(N);
            } else {
                // 指標停在原地,等待下一個 request
            }
        }
    }

    always_comb {
        gnt = 0;
        for i: u32 in 0..N {
            if i == r_ptr && req[r_ptr] {
                gnt[i] = 1;
            }
        }
    }
}

這是一個指標式的簡化輪詢:指標 r_ptr 指向「下一個輪到的 agent」,若該 agent 有 request 則授權並推進指標,否則保持。缺點是若指標位置沒有 request,會跳過後面的 agent(不是嚴格 RR)。嚴格的 round-robin 用「double mask」技巧:把 request 對指標旋轉後做 priority encode,再旋轉回來。概念上:

// 嚴格 RR 的核心(概念示意):
// masked_req = req << (ptr + 1) 依指標旋轉
// winner = priority_encode(masked_req 或 req)
// next_ptr = winner

完整的 double-mask 實作留給讀者作為練習;此處想強調的是,仲裁器的公平性證明(無飢餓、服務順序)是靠指標狀態的 invariant 支撐,這也是為什麼它值得做成一個被充分驗證的通用元件,而不是在每個模組裡重寫一次。

泛型的價值#

三個元件共用同一個模式:以 ::<...> 接收規格、以 param/let 推導內部常數($clog2)、以 var 存狀態、以 always_comb 組合輸出。寫一次、驗證一次,任何規格的實例化都經過相同的型別檢查。這正是 Cummings 論文文化在語言層面的延伸:元件知識被固化為參數化的、可重複使用的程式碼,而不是散落在各專案的隱性經驗。下一章補上最後一塊拼圖:function。