跳轉到

05.2 ff 區塊與時脈重置:把慣例提升為語言#

同步設計:五十年的共識#

在談 always_ff 之前,先回顧它背後的理論根基。Mealy 與 Moore 在 1950 年代發表的有限狀態機理論,奠定了「狀態 + 輸入 → 下一狀態與輸出」的抽象模型。數位電路的同步設計學派在 1960–70 年代成型:把整個電路的時間切成由一個全域時脈驅動的離散節拍,所有狀態都存在 edge-triggered 正反器(flip-flop)中,所有組合邏輯都必須在一個 clock period 內穩定。這個模型簡化了 timing 分析(只需要檢查 setup/hold,不需要考慮任意組合迴圈的傳播時間),成為 ASIC 與 FPGA 設計的主流典範。

同步設計的代價是時脈樹(clock tree)。在 ASIC 流程中,clock 要送到數十萬甚至上千萬個 flip-flop,clock tree synthesis(CTS)在 placement 之後進行,必須平衡每個葉節點的 skew——skew 控制不好,setup 與 hold violation 會同時出現。CTS 是 ASIC backend 中成本極高的一步,clock network 的功耗也常佔整顆晶片功耗的三到五成。這一切都建立在「每一個 flip-flop 的時脈確實連到同一棵樹」的前提上。因此,「時脈與重置訊號必須與資料訊號嚴格區分」不是潔癖,而是整個同步設計與後端流程的基石。

Verilog 時代的痛:什麼都是 logic#

Verilog 沒有型別系統幫你做這個區分。經典的 Verilog-2001 寫法:

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        q <= 0;
    end else begin
        q <= d;
    end
end

這段程式碼有三個潛在的人為錯誤,而工具全部不報錯:

  1. 把 posedge clk 打成 posedge clck——如果剛好有個 wire 叫 clck,工具照單全收,每個 flip-flop 掛上錯誤時脈。
  2. 把 negedge rst_n 打成 posedge rst_n——非同步重置極性寫反,模擬上重置行為相反,合成上工具推論出不同的 reset cell,可能到 STA 或 gate-level 模擬才爆。
  3. if (!rst_n) 忘了寫,或寫成 if (rst_n)——重置極性在事件列表與 if 條件中不一致,是最經典的 gate-level 前後不一致來源。

SystemVerilog 的 always_ff 只是加了一個意圖宣告(工具會檢查塊內只能有一次賦值等),對上述問題幫助有限。真正的解法是型別。

Veryl 的作法:clock 與 reset 是型別#

Veryl 把這個慣例提升為語言層級:clock 與 reset 是獨立的型別。埠宣告必須寫明:

module Counter (
    clk : input clock,
    rst : input reset,
    cnt : output logic<8>,
) {
    var r_cnt: logic<8>;

    always_ff (clk, rst) {
        if_reset {
            r_cnt = 0;
        } else {
            r_cnt = r_cnt + 1;
        }
    }

    assign cnt = r_cnt;
}

幾個重點:

  • clk 的型別是 clock 而非 logic。如果有人想把一條資料訊號接到時脈埠上,或是把時脈接到資料埠,編譯器直接報錯。型別系統替你守住了「時脈訊號必須來自時脈來源」這條鐵律。
  • always_ff (clk, rst) 中,clk 必須是 clock 型、rst 必須是 reset 型。不存在「拼錯時脈名字」的問題——拼錯的名字如果不是一個 clock 型的訊號,編譯不過。
  • if_reset 是語言內建的「重置分支」。它取代了手寫的 if (!rst_n),因此「事件列表極性」與「if 條件極性」永遠一致,因為極性根本不寫在程式碼裡,而是由 rst 的型別(見下一章)決定。這正是把 1990 年代 coding guideline 中「重置極性寫法一致性」那條人肉準則變成語言保證。

範例:shift register#

module ShiftReg (
    clk  : input  clock,
    rst  : input  reset,
    din  : input  logic,
    dout : output logic,
) {
    var r_ff: logic<4>;

    always_ff (clk, rst) {
        if_reset {
            r_ff = 0;
        } else {
            r_ff = {din, r_ff[3:1]};
        }
    }

    assign dout = r_ff[0];
}

注意 Veryl 的串接語法:a << b 等價於 SystemVerilog 的 {a, b}。串接運算子 << / >> 分別對應位元向高位與低位拼接,這在後續章節會反覆出現。

非同步重置寫起來一樣簡單#

在 Verilog 中,同步重置與非同步重置的程式碼差異就在事件列表有沒有 or negedge rst_n,一個字的差異決定了完全不同的電路與 timing 需求(下一章詳談)。在 Veryl 中,這個差異完全隱藏在 reset 型別裡:reset 型的重置埠,其同步/非同步與極性由型別或全域設定決定,always_ff 與 if_reset 的寫法完全相同。工程師寫一次行為描述,reset 策略成為一個可以全域切換的配置選項,而非散佈在數萬行程式碼中的字面量。

小結#

同步設計理論從 Mealy/Moore 機到 CTS 主導的 ASIC backend,走了七十年,其核心不變量始終是「時脈與重置是特殊的、受控的訊號」。Verilog 把它們當成普通 wire,Veryl 用型別把它們標記出來。always_ff (clk, rst) + if_reset 這對組合,讓「時脈/重置慣例」從檢查表上的待辦事項變成編譯器強制執行的不變量。下一章我們深入 reset 型別家族,看同步與非同步重置這場真實的工程論戰。