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 章的通用元件就水到渠成。