跳轉到

06.2 generate 生成:編譯期的電路複製#

generate 解決什麼問題#

有些硬體結構無法用參數「調」出來,只能「複製」或「選擇」:一個 32 條的 comparator 陣列、依資料寬度選不同實作的 adder、依 endian 反轉的位元序。Verilog-2001 之前,這些只能靠 text macro(`for 並不存在,得手展開)或 script 產生程式碼。Verilog-2001 正式引入 generate 敘述:generate ... endgenerate 包裹的 for 與 if,在細化(elaboration)時展開成具體電路。SystemVerilog 進一步允許省略 generate/endgenerate 關鍵字,但仍保留了 generate block 的命名規則(for ... begin : label)這類冗長的形式。

Veryl 的 generate 語法更簡潔,而且變數必須標註型別(延續 Rust 風格的嚴格性):

  • 條件生成:if COND: u32 == 0 { ... } else if ... { ... } else { ... }
  • 迴圈生成:for i: u32 in 0..W { ... }

注意兩個重點:條件生成的條件寫成 COND: u32 == 0 這種「參數 : 型別比較」的形式;迴圈變數 i 必須寫明型別 u32。範圍 0..W 是半開區間,包含 0 不包含 W,與 Rust 一致。

條件生成:依參數選實作#

一個典型情境:同一個模組,資料寬度為 1 時用簡化實作,否則用一般實作。

module Mux2::<W: u32> (
    sel : input  logic,
    a   : input  logic<W>,
    b   : input  logic<W>,
    y   : output logic<W>,
) {
    if W: u32 == 1 {
        assign y = sel ? a : b;
    } else {
        assign y = sel ? a : b;
        // 實務上這裡可放位寬相關的特殊實作
    }
}

再一個例子:依 endian 參數反轉位元序。

module BitRev::<W: u32, LITTLE: u32> (
    din : input  logic<W>,
    dout: output logic<W>,
) {
    if LITTLE: u32 == 1 {
        assign dout = din;
    } else {
        let reversed: logic<W> = 0;
        always_comb {
            for i: u32 in 0..W {
                reversed[i] = din[W - 1 - i];
            }
        }
        assign dout = reversed;
    }
}

條件生成的分支在細化時只保留一個,另一個分支完全不存在於產出的 Verilog 中——這與 text macro 的「展開後一堆死代碼」不同,也與參數化的 mux(執行期選擇)不同:這裡是編譯期選擇,不消耗任何硬體。

迴圈生成:N-entry 結構#

以 carry-select adder 為例:把加法拆成數段,每段同時算出「進位為 0」與「進位為 1」兩種結果,再由上一段的進位選擇。用 generate 迴圈展開:

module CarrySelect::<W: u32> (
    a: input  logic<W>,
    b: input  logic<W>,
    y: output logic<W>,
) {
    param SEG: u32 = 4;
    let SW: u32 = W / SEG;

    var sum : logic<W>;
    var cout: logic;

    for i: u32 in 0..SEG {
        // 每段產生兩個 candidate 加法器與一個 mux
        // (概念結構;完整電路含前段進位傳遞)
        let lo: u32 = i * SW;
        let hi: u32 = lo + SW;
    }
}

實務上會把每段的 candidate adder 與 selection 寫成子模組,在 for 生成塊中 inst。重點是:generate 迴圈中的 inst、assign、always 都會被複製 SEG 份,如同手寫 SEG 遍——但位寬、命名都由迴圈變數保證一致。

一個更直接的例子,N 條獨立的暫存器鏈:

module CellArray::<N: u32> (
    clk : input clock,
    rst : input reset,
    d   : input  logic<N>,
    q   : output logic<N>,
) {
    for i: u32 in 0..N {
        var r: logic;
        always_ff (clk, rst) {
            if_reset {
                r = 0;
            } else {
                r = d[i];
            }
        }
        assign q[i] = r;
    }
}

對照 SystemVerilog#

同一件事在 SystemVerilog 中的樣子:

generate
  for (genvar i = 0; i < N; i = i + 1) begin : g_cells
    // ...
  end
endgenerate

genvar、begin : label、endgenerate 三層儀式性語法,在大型設計中大量出現,降低了可讀性。Veryl 把它壓縮成 for i: u32 in 0..N { },且 i 的型別標註讓迴圈變數的算術(如 i * SW)有明確的型別語意。這類「保留語意、刪除儀式」的設計在 Veryl 中隨處可見。

generate 與 generics 的關係#

generate 與 generics(06.1 章)都是編譯期機制,但角色不同:generics 讓「一個模組」適配多種規格;generate 讓「一個模組內部」依規格組裝出不同的電路拓撲。兩者常攜手出現:模組以 ::<W> 接收寬度,內部以 if W: u32 == 64 選擇管線級數、以 for i: u32 in 0..W/64 展開資料通道。掌握這對工具後,06.3 章的通用元件就水到渠成。