SystemVerilog · Module 7
Generate Constructs — for, if, case
Structural unrolling at elaboration, genvar, named generate blocks, feature switches via generate-if, multi-way generate-case, and 5 debugging labs.
Module 7 · Page 7.6
Generate constructs let you write hardware that builds itself. Loop over a parameter to create N adder stages. Conditionally include a pipeline register. Select between three memory implementations. All decided at elaboration — zero cost at runtime.
What Are Generate Constructs?
A generate block is a compile-time hardware generator. It runs during elaboration — before simulation or synthesis — and produces RTL statements, module instances, or signal declarations based on parameter values. The result is as if you had hand-written those statements yourself.
Think of it this way: a regular for loop runs at simulation time and computes values. A generate for runs at elaboration time and creates hardware — wires, flip-flops, or sub-module instances. Once elaboration is done, the generate block is gone; only the hardware it produced remains.
| Construct | Purpose |
|---|---|
generate for | Repeat a piece of hardware N times. Classic use: build an N-bit ripple-carry adder, N pipeline stages, or N identical sub-module instances from one loop. |
generate if | Include or exclude hardware based on a parameter. Use when two versions of a module exist — e.g. with or without a pipeline register, FPGA vs ASIC implementation. |
generate case | Select one of several hardware implementations based on a parameter value. Like generate if–else if–else but cleaner for three or more choices. |
genvar — The Generate Loop Variable
A genvar is a special integer variable that exists only during elaboration. It is the loop counter for generate for loops. You cannot use a regular int or integer as a generate loop variable — it must be genvar.
// ── Declare genvar outside the generate block (traditional) ─────
genvar i;
generate
for (i = 0; i < 4; i++) begin : gen_stage
// 'i' is a compile-time constant here, not a runtime variable
half_adder u_ha (.a(a[i]), .b(b[i]), .sum(sum[i]), .cout(c[i+1]));
end
endgenerate
// ── Declare genvar inside the for (SystemVerilog shorthand) ──────
generate
for (genvar j = 0; j < 4; j++) begin : gen_reg
always_ff @(posedge clk)
pipe[j] <= (j == 0) ? data_in : pipe[j-1];
end
endgenerate
// ── Multiple genvars for nested loops ────────────────────────────
genvar row, col;
generate
for (row = 0; row < 4; row++) begin : gen_row
for (col = 0; col < 4; col++) begin : gen_col
pe_cell u_pe (.in_a(A[row][col]), .in_b(B[row][col]));
end
end
endgenerategenerate for — Replicating Hardware
generate for is the most important generate construct. It replicates a block of RTL — signals, logic, or module instances — N times, where N is determined by a parameter. This is how you build parameterised data paths: one loop body, N bits of hardware.
// ── Leaf module: a single 1-bit full adder ───────────────────────
module full_adder (
input logic a, b, cin,
output logic sum, cout
);
assign {cout, sum} = a + b + cin;
endmodule
// ── N-bit ripple-carry adder using generate for ──────────────────
module rca_n #(parameter int N = 4) (
input logic [N-1:0] a, b,
input logic cin,
output logic [N-1:0] sum,
output logic cout
);
// N+1 carry wires: c[0]=cin, c[1..N-1]=internal, c[N]=cout
logic [N:0] c;
assign c[0] = cin;
assign cout = c[N];
// ── generate for: create N full_adder instances ─────────────
generate
for (genvar i = 0; i < N; i++) begin : gen_fa // ← named block
full_adder u_fa (
.a (a[i]),
.b (b[i]),
.cin (c[i]), // carry from previous stage
.sum (sum[i]),
.cout(c[i+1]) // carry to next stage
);
// Hierarchical path: rca_n.gen_fa[0].u_fa, gen_fa[1].u_fa, ...
end
endgenerate
endmodulegenerate for with always blocks (Registered Pipeline)
module pipeline #(
parameter int STAGES = 4,
parameter int DATA_WIDTH = 32
) (
input logic clk, rst_n,
input logic [DATA_WIDTH-1:0] data_in,
output logic [DATA_WIDTH-1:0] data_out
);
// Array of pipeline registers — one per stage
logic [DATA_WIDTH-1:0] stage [0:STAGES-1];
generate
for (genvar s = 0; s < STAGES; s++) begin : gen_pipe
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) stage[s] <= '0;
else stage[s] <= (s == 0) ? data_in : stage[s-1];
end
end
endgenerate
assign data_out = stage[STAGES-1];
endmodule
// Change STAGES=4 to STAGES=8 and you get 8 registers — no edits insidegenerate if — Conditional Hardware
generate if conditionally includes or excludes a block of hardware based on a parameter value. Only the branch where the condition is true is elaborated — the other branch disappears completely. No mux is generated; the hardware literally does not exist.
module alu #(
parameter int DATA_WIDTH = 32,
parameter bit PIPELINE = 1 // 1=add output register, 0=combinational
) (
input logic clk, rst_n,
input logic [DATA_WIDTH-1:0] a, b,
output logic [DATA_WIDTH-1:0] result
);
logic [DATA_WIDTH-1:0] sum_comb;
assign sum_comb = a + b;
// ── generate if: registered or combinational output ──────────
generate
if (PIPELINE == 1) begin : gen_piped
// Add output register — higher Fmax, 1 cycle latency
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) result <= '0;
else result <= sum_comb;
end
end else begin : gen_comb
// Direct output — no register, zero extra latency
assign result = sum_comb;
end
endgenerate
endmodule
// ── Instantiate both configurations ──────────────────────────────
alu #(.DATA_WIDTH(32), .PIPELINE(1)) u_alu_fast (...); // registered
alu #(.DATA_WIDTH(32), .PIPELINE(0)) u_alu_comb (...); // combinationalgenerate if for Technology Selection (FPGA vs ASIC)
module smart_ram #(
parameter int DATA_W = 32,
parameter int DEPTH = 256,
parameter string TARGET = "SIM" // "SIM" | "FPGA" | "ASIC"
) ( /* ports */ );
generate
if (TARGET == "FPGA") begin : gen_fpga
// Infer Xilinx/Intel BRAM with specific attributes
(* ram_style = "block" *)
logic [DATA_W-1:0] bram [0:DEPTH-1];
// ... BRAM access pattern
end else if (TARGET == "ASIC") begin : gen_asic
// Instantiate foundry-provided SRAM macro
sky130_sram #(.WORDS(DEPTH), .WIDTH(DATA_W)) u_sram (/*...*/);
end else begin : gen_sim
// Pure behavioural model — fast simulation, no technology
logic [DATA_W-1:0] mem [0:DEPTH-1];
end
endgenerate
endmodulegenerate case — Multi-Way Hardware Selection
generate case works like a regular case statement but selects between hardware implementations at elaboration time. It is cleaner than a long chain of generate if–else if–else if when you have three or more choices.
module adder_impl #(
parameter int DATA_W = 32,
parameter string ARCH = "RCA" // "RCA"|"CLA"|"KOG"
) (
input logic [DATA_W-1:0] a, b,
input logic cin,
output logic [DATA_W-1:0] sum,
output logic cout
);
generate
case (ARCH)
"RCA": begin : gen_rca
// Ripple-Carry Adder — simple, slow for large N
rca_n #(.N(DATA_W)) u_add (.a, .b, .cin, .sum, .cout);
end
"CLA": begin : gen_cla
// Carry-Lookahead Adder — faster, more gates
cla_n #(.N(DATA_W)) u_add (.a, .b, .cin, .sum, .cout);
end
"KOG": begin : gen_kog
// Kogge-Stone Adder — fastest, most parallel
kog_n #(.N(DATA_W)) u_add (.a, .b, .cin, .sum, .cout);
end
default: begin : gen_err
initial $fatal(1, "Unknown ARCH='%s'. Use RCA, CLA, or KOG.", ARCH);
end
endcase
endgenerate
endmodule
// ── Select the adder architecture at instantiation ───────────────
adder_impl #(.DATA_W(32), .ARCH("RCA")) u_slow (...); // area-efficient
adder_impl #(.DATA_W(32), .ARCH("CLA")) u_medium (...); // balanced
adder_impl #(.DATA_W(64), .ARCH("KOG")) u_fast (...); // max speedNamed Generate Blocks — Hierarchy and Debugging
Every generate block should have a name (begin : block_name). The name creates a scope in the design hierarchy that is visible in waveform viewers, assertions, coverage reports, and hierarchical references.
module rca_4bit;
generate
for (genvar i = 0; i < 4; i++) begin : gen_fa
full_adder u_fa (.a(a[i]), .b(b[i]), .cin(c[i]), .sum(sum[i]), .cout(c[i+1]));
end
endgenerate
// ── Hierarchical paths created by the named block ────────────
// rca_4bit.gen_fa[0].u_fa — first full adder
// rca_4bit.gen_fa[1].u_fa — second full adder
// rca_4bit.gen_fa[2].u_fa — third full adder
// rca_4bit.gen_fa[3].u_fa — fourth full adder
// ── Access signals inside a named generate block ─────────────
// In assertions or hierarchical references:
// gen_fa[2].u_fa.cout ← carry-out of bit 2
endmodule
// ── generate if blocks also create named scopes ──────────────────
generate
if (PIPELINE) begin : gen_piped
always_ff @(posedge clk) result_r <= result_comb;
// Path: module_name.gen_piped.result_r
end else begin : gen_comb
assign result_r = result_comb;
// Path: module_name.gen_comb (only when PIPELINE==0)
end
endgenerateThe generate Keyword — Optional in SystemVerilog
In IEEE 1800 (SystemVerilog), the generate / endgenerate keywords are optional when using for and if at the module level. The tool infers that a top-level for (genvar ...) or if is a generate construct without the wrapper. However, many teams still write generate explicitly for clarity — both styles are valid.
// ── Traditional style — explicit generate/endgenerate ───────────
generate
for (genvar i = 0; i < N; i++) begin : gen_stage
half_adder u_ha (.a(a[i]), .b(b[i]), .sum(s[i]), .cout(c[i+1]));
end
endgenerate
// ── SystemVerilog short style — no generate/endgenerate wrapper ──
for (genvar i = 0; i < N; i++) begin : gen_stage
half_adder u_ha (.a(a[i]), .b(b[i]), .sum(s[i]), .cout(c[i+1]));
end
// Both produce identical hardware. Choose one style and stay consistent.generate for vs if vs case — When to Use Which
| Construct | When to Use | Key Requirement | Classic Example |
|---|---|---|---|
generate for | Replicate identical (or indexed) hardware N times | Needs a genvar loop variable; block must be named | N-bit adder, N pipeline stages, N-port register file rows |
generate if | Include or exclude hardware based on a boolean/integer parameter | Condition must evaluate at elaboration time (no runtime signals) | Optional pipeline register, FPGA vs ASIC memory, with/without ECC |
generate case | Choose one of >=3 hardware implementations | Same as generate if; default branch strongly recommended | Adder architecture selection, memory technology, encoding scheme |
Common Mistakes
// ════ MISTAKE 1: Using int instead of genvar ═════════════════════
generate
for (int i = 0; i < N; i++) begin : gen_s // ❌ int not allowed here
half_adder u_ha (.a(a[i]), .b(b[i]), .sum(s[i]), .cout(c[i+1]));
end
endgenerate
// ✅ FIX: use genvar
for (genvar i = 0; i < N; i++) begin : gen_s
half_adder u_ha (.a(a[i]), .b(b[i]), .sum(s[i]), .cout(c[i+1]));
end
// ════ MISTAKE 2: Unnamed generate block with instances ════════════
for (genvar i = 0; i < N; i++) begin // ❌ no name — tool may error
half_adder u_ha (...);
end
// ✅ FIX: always name the block
for (genvar i = 0; i < N; i++) begin : gen_ha // ✅ named
half_adder u_ha (...);
end
// ════ MISTAKE 3: Condition depends on a runtime signal ════════════
generate
if (enable_reg) begin : gen_opt // ❌ enable_reg is a logic signal!
always_ff @(posedge clk) result <= data;
end
endgenerate
// ✅ FIX: generate if must use a parameter or localparam
generate
if (PIPELINE == 1) begin : gen_opt // ✅ PIPELINE is a parameter
always_ff @(posedge clk) result <= data;
end
endgenerate
// ════ MISTAKE 4: No default in generate case ══════════════════════
generate
case (ARCH)
"RCA": begin : gen_rca rca_n #(.N(W)) u_a (...); end
"CLA": begin : gen_cla cla_n #(.N(W)) u_a (...); end
// ❌ No default — typo "KGA" silently generates nothing
endcase
endgenerate
// ✅ FIX: always add a default with $fatal
default: begin : gen_err initial $fatal(1, "Bad ARCH='%s'", ARCH); endQuick Reference — Generate Constructs Cheat Sheet
// ── generate for — replicate hardware N times ─────────────────
generate
for (genvar i = 0; i < N; i++) begin : gen_block
// module instances, always blocks, assign statements
my_module u_inst (.port(signal[i]));
end
endgenerate
// ── generate if — conditional hardware ───────────────────────
generate
if (PARAM == VAL) begin : gen_yes
// hardware included when condition is true
end else begin : gen_no
// hardware included otherwise
end
endgenerate
// ── generate case — multi-way hardware selection ──────────────
generate
case (STRING_PARAM)
"A": begin : gen_a /* hardware A */ end
"B": begin : gen_b /* hardware B */ end
default: begin : gen_err
initial $fatal(1, "Unknown value: %s", STRING_PARAM);
end
endcase
endgenerate
// ── Rules ─────────────────────────────────────────────────────
// • genvar only used in generate for loops
// • Condition in generate if/case must be a compile-time constant
// • Always name generate blocks: begin : gen_name
// • generate/endgenerate keywords optional in SystemVerilog
// • Always add default: in generate case with $fatal
// • Hierarchical path: module.gen_block[i].instance_nameVerification Usage — Generate Inside the Testbench
generate is not a synthesis-only feature. In a verification environment it lets you scale agents, conditionally include collectors, and switch protocol-specific assertion sets without growing the test code. The same construct that builds N pipeline stages in the DUT builds N parallel agents in the TB.
module multi_master_tb #(parameter int NUM_MASTERS = 4);
// One interface array — one virtual interface array
axi_if axi_intf [NUM_MASTERS](.clk(clk));
// Build NUM_MASTERS independent agents at elaboration time
generate
for (genvar m = 0; m < NUM_MASTERS; m = m + 1) begin : g_master
axi_master_dut u_dut (.axi(axi_intf[m]));
axi_master_agent u_agt (.vif(axi_intf[m]));
end
endgenerate
// Each agent lands at top.g_master[N].u_agt — name-stable hierarchy
initial begin
for (int m = 0; m < NUM_MASTERS; m++) begin
uvm_config_db#(virtual axi_if)::set(
null,
$sformatf("uvm_test_top.env.master[%0d].*", m),
"vif", axi_intf[m]);
end
run_test();
end
endmodulemodule env #(
parameter bit COLLECT_COV = 1'b1,
parameter bit RUN_PROTOCOL_ASSERTIONS = 1'b1
);
// Coverage costs simulation time — skip on smoke runs
generate
if (COLLECT_COV) begin : g_cov
axi_coverage u_cov (.vif(axi_intf));
end
endgenerate
// Protocol assertions can be disabled when stressing the bus with
// intentionally illegal traffic in error-injection tests
generate
if (RUN_PROTOCOL_ASSERTIONS) begin : g_assert
axi_protocol_checker u_chk (.axi(axi_intf));
end
endgenerate
endmodule
// Smoke run: env #(.COLLECT_COV(0), .RUN_PROTOCOL_ASSERTIONS(0)) — fast, low-overhead
// Sign-off run: env #() — full coverage and checker stackSimulation Behavior — What the Simulator Sees After Elaboration
A generate block leaves no runtime trace. By the time the simulator opens time 0, every iteration has been unrolled into independent hierarchical nodes; the genvar no longer exists; the elaborator has lowered every reference to its concrete index value.
module pipe #(parameter int N = 3);
generate
for (genvar i = 0; i < N; i = i + 1) begin : g_stage
initial begin
$display("[%0t] %m elaborated (i=%0d in source)", $time, i);
end
end
endgenerate
endmodule
pipe #(.N(3)) u_pipe();
// Expected output at time 0:
// [0] top.u_pipe.g_stage[0] elaborated (i=0 in source)
// [0] top.u_pipe.g_stage[1] elaborated (i=1 in source)
// [0] top.u_pipe.g_stage[2] elaborated (i=2 in source)
//
// Three independent initial blocks were instantiated — one per iteration.
// The genvar 'i' does not exist at runtime: $display sees a literal 0, 1, 2
// baked in by the elaborator. Each $display lives in its own hierarchical node.Waveform Analysis — N Parallel Signals from One Generate
When a generate-for produces N parallel structures, each one appears in the waveform under its named hierarchy. Opening a parameterised pipeline in Verdi or DVE, you see the per-stage register at top.u_pipe.g_stage[0].stage_reg, top.u_pipe.g_stage[1].stage_reg, … — the indices match the genvar in source.
module pipe #(parameter int N = 4) (
input logic clk, rst_n,
input logic [7:0] din,
output logic [7:0] dout
);
logic [7:0] stage [0:N]; // stage[0]=din, stage[N]=dout
assign stage[0] = din;
generate
for (genvar i = 0; i < N; i = i + 1) begin : g_stage
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) stage[i+1] <= 8'h00;
else stage[i+1] <= stage[i];
end
end
endgenerate
assign dout = stage[N];
endmodule
pipe #(.N(4)) u_pipe (.clk, .rst_n, .din(d_in), .dout(d_out));cycle 0 1 2 3 4 5 6 7
clk _│‾│_│‾│_│‾│_│‾│_│‾│_│‾│_│‾│_│‾│_
din AA AA AA AA AA AA AA AA
g_stage[0]
stage[1] 00 AA AA AA AA AA AA AA ← cycle 1: captured
g_stage[1]
stage[2] 00 00 AA AA AA AA AA AA ← cycle 2: propagated
g_stage[2]
stage[3] 00 00 00 AA AA AA AA AA ← cycle 3
g_stage[3]
stage[4] 00 00 00 00 AA AA AA AA ← cycle 4 → dout
dout 00 00 00 00 AA AA AA AA ← N cycles of latency
Reading order in Verdi/DVE: u_pipe → g_stage[0] → g_stage[1] → g_stage[2] → g_stage[3]
The bracketed index is the genvar value at elaboration. Each block is an independent scope.Industry Insights — How Senior Teams Use Generate
Debugging Academy — 5 Real Generate Bugs
Each lab is a real failure mode. Buggy code, the symptom, the root cause, and the fix.
Unnamed Generate Block — Tool-Generated Path Breaks External References
FRAGILE HIERARCHYmodule pipe #(parameter int N = 4);
generate
for (genvar i = 0; i < N; i = i + 1) begin // ← no : name
stage_reg u_s (.clk, .d_in(stage[i]), .d_out(stage[i+1]));
end
endgenerate
endmodule
// A SystemVerilog Assertion file bound to the design:
bind pipe.genblk1[1].u_s stage_reg_checker u_chk(.*);
// ^^^^^^^^^^ tool-generated name — works on VCS, fails on QuestaThe bind compiles cleanly on VCS (which assigns the default name genblk1). On Questa it fails to elaborate (Questa generates \for[N] with escaped identifiers). On Xcelium it elaborates but silently binds to the wrong scope. Three tools, three behaviours, same source.
IEEE 1800-2017 §27.6 specifies that unnamed generate blocks receive an implementation-defined name. Tools differ. Every external reference through the hierarchy (binds, force, waveform save files, UPF annotations) becomes tool-dependent.
Always label the block: begin : g_stage. The hierarchical path becomes pipe.g_stage[1].u_s — stable across every tool. Add a lint rule rejecting unnamed generates (Spyglass unnamed_genblk); fail the build on violation. No exceptions.
genvar Reused Across Two Generate Loops — Cross-Iteration Confusion
SCOPE COLLISIONmodule bus_array #(parameter int W = 32);
// Two generate loops sharing one genvar
genvar i;
generate
for (i = 0; i < W; i = i + 1) begin : g_pre
assign pre_data[i] = (raw[i] ^ raw[i+1]); // ← uses i
end
for (i = 0; i < W; i = i + 1) begin : g_post
assign post_data[i] = pre_data[i] | mask[i]; // ← also uses i
end
endgenerate
endmoduleSome tools elaborate cleanly (the LRM allows reuse of a single genvar in non-nested loops). Older simulators or older synthesis versions warn or error with confusing "genvar already declared in this scope" or "genvar 'i' value undefined." The build may pass on one tool and fail on another, breaking cross-tool regression.
Older Verilog-2001 required a single genvar i; declaration up front, reused across all loops. SystemVerilog allows the more readable inline for (genvar i = 0; ...), scoping the genvar to that one loop. Mixing the two styles or sharing one declaration across multiple loops works in most modern tools but isn't bullet-proof.
Use the inline genvar declaration per loop: for (genvar i = 0; i < W; i = i + 1) begin : g_pre ... end. Each loop gets its own private i. Source remains clean; no cross-tool ambiguity. As a bonus, the genvar is scoped to the loop body so you can't accidentally reference it after the loop ends.
generate if Condition Depends on a Signal — Elaboration Error
RUNTIME IN STATICmodule ctrl(input logic mode_sel, ...);
generate
if (mode_sel) begin : g_a variant_a u_a (...); end
else begin : g_b variant_b u_b (...); end
endgenerate
endmodule
// Error: "non-constant expression in generate if condition"Elaboration fails with a clear-but-easy-to-misread error: "generate condition must be a constant expression." The author intended runtime switching of the implementation — which generate cannot do.
generate if is evaluated by the elaborator before time 0. It needs to know which branch wins to build the netlist. A signal value isn't known until simulation runs, which is too late.
Two options depending on intent. (a) If the choice is per-instance and fixed at integration time, promote it to a parameter: module ctrl #(parameter bit USE_VARIANT_A = 1), then generate if (USE_VARIANT_A) .... (b) If the choice must vary at runtime, instantiate both variants and select with a multiplexer: variant_a u_a (...); variant_b u_b (...); assign out = mode_sel ? out_a : out_b; — pays double the area but allows runtime switching.
Forgotten generate Keyword — Some Tools Accept, Some Reject
TOOL DRIFTmodule replicate #(parameter int N = 4);
// No 'generate' / 'endgenerate' wrapper:
for (genvar i = 0; i < N; i = i + 1) begin : g_inst
stage u_s (...);
end
endmodule
// Works on every SV-2009-compliant tool.
// Fails on legacy Verilog-2001 mode in some simulators.Builds fine on VCS, Questa, Xcelium in SystemVerilog mode. Fails on legacy Verilog-2001 compilation paths (some IP review tools, some FPGA flows that haven't upgraded). The failure looks like a syntax error on the for keyword, with no mention of generate.
SystemVerilog (IEEE 1800-2009 onward) made the generate/endgenerate keywords optional. Verilog-2001 (IEEE 1364-2001) required them. Legacy tools or non-SV flows reject the implicit form.
Always wrap with generate ... endgenerate in shipping IP. The two extra keywords cost nothing in modern tools and make the source legal on every tool that has ever existed. Style guide: "generate keyword required."
Off-by-One in Generate Loop Bound — Last Iteration Silently Dropped
BOUND ERRORmodule register_file #(parameter int N = 32);
logic [31:0] regs [0:N-1];
generate
for (genvar i = 0; i < N - 1; i = i + 1) begin : g_reg
// ^^^^^ ← should be N, not N-1
always_ff @(posedge clk) regs[i] <= ...;
end
endgenerate
endmoduleRegister file has 32 storage slots but only 31 have a write path. Writes to regs[31] are silently ignored — the storage element exists (because regs was declared with [0:N-1]) but no flip-flop ever drives it. Reading regs[31] always returns X (4-state) or 0 (2-state). The bug only shows when test stimulus exercises the highest register — often a smoke test misses it.
Classic off-by-one. The author wrote the loop bound thinking "iterations 0 through N-1" but expressed it as i < N - 1 instead of i < N. Both expressions look reasonable on a quick read. Code review caught nothing because the loop body looks correct.
Standardise on i = 0; i < N; i = i + 1 as the team's canonical form. Any deviation requires a code-review comment explaining why. Optional: add an elaboration-time check that the generated count matches the array declaration: initial assert ($size(regs) == N) else $fatal(...);. For more sophisticated checks, count populated paths via $count_drivers(regs[i]) on the highest index.
Interview Q&A — 12 Questions on Generate Constructs
Drawn from real interviews at chip-design and EDA companies. Try to answer before reading each response.
A generate construct is a structural hardware-building directive evaluated by the elaborator before time 0. It unrolls into independent module-level constructs — N module instances, N continuous assigns, N always blocks — each living in its own hierarchical scope. A for loop inside an always block is procedural: it generates a single block of sequential simulation code that iterates at runtime (or, in synthesis, unrolls into N operations packed inside one always block). Generate produces N visible siblings; a procedural for produces one opaque chunk.
Best Practices — Generate Rules to Walk Away With
- Always name every generate block.
begin : g_nameon everyfor,if,casegenerate. Hierarchical paths must be tool-stable. - Always include the
generate/endgeneratekeywords. Optional in SV-2009, required for legacy compatibility, free for SV-only flows. - Declare genvar inline.
for (genvar i = 0; i < N; i = i + 1), not a separategenvar i;declaration at module scope. Avoids scope collisions. - Use the canonical loop form.
i = 0; i < N; i = i + 1. Any deviation requires a comment in code review. - Use generate if for feature switches. Sync vs async reset, ECC on/off, dual-port vs single-port — selected at elaboration, no runtime overhead.
- Use generate for for parametric replication. Pipeline stages, channel arrays, comparator arrays. Each iteration is independently retimable.
- Don't use generate to gate signals on runtime conditions. Generate-if needs elaboration-time constants. For runtime selection, use procedural
ifwith a multiplexer. - Add elaboration-time assertions for generate-driven invariants.
initial assert (N >= 1) else $fatal(...);— catches bad overrides before any logic exists. - Test the corner cases of N. Run regression at N=1 (smallest), N=2 (boundary), N=4 (typical), N=MAX (stress). Bugs often live at N=1 (degenerate loops) or N=MAX (resource limits).
- Lint for unnamed generates and procedural for inside always_comb where structural was intended. Both are silent failure modes that ship to silicon without warnings.