parameter, and use
generate/for to replicate hardware structurally — while keeping straight that
this "loop" runs once at compile time and produces N permanent, simultaneous copies of real hardware,
not N sequential iterations.
parameter: compile-time configuration
Every module you've written so far has hardcoded widths. parameter lets a module declare a
constant that the instantiating code can override, making one module definition reusable at
many sizes:
module reg_n #(
parameter WIDTH = 8 // default if not overridden
) (
input wire clk,
input wire rst,
input wire [WIDTH-1:0] d,
output reg [WIDTH-1:0] q
);
always @(posedge clk)
if (rst) q <= {WIDTH{1'b0}};
else q <= d;
endmodule
reg_n #(.WIDTH(16)) my_reg (.clk(clk), .rst(rst), .d(d16), .q(q16));
#(.WIDTH(16)) between the module name and the instance name overrides the default at
this specific instance — other instances of reg_n elsewhere in the design can use
a completely different width. This is resolved once, before simulation or synthesis even begins, not at
runtime — there's no hardware cost to "choosing" a width this way, because only one width is ever built
for a given instance.
Recall localparam from the FSM in Lesson 5. The distinction now matters: parameter
is meant to be overridden from outside; localparam is a fixed, internal constant (or one
derived from a parameter, e.g. localparam WIDTH_LOG2 = $clog2(WIDTH);) that instantiating
code cannot change.
generate: a loop that builds hardware, not one that runs
A for loop inside a generate block looks exactly like a software loop, and
that similarity is exactly what makes it dangerous to misread. It does not execute N times while the
circuit runs. It is unrolled once, at elaboration (compile) time, into N separate, permanent, physically
distinct pieces of hardware — all simultaneously present and active, in the same spirit as Lesson 1's
"four always-live blocks."
Worked example: a ripple-carry adder of any width, built from WIDTH copies of the
full_adder you wrote back in Lesson 3, each one's carry-out wired to the next one's carry-in:
module ripple_adder #(
parameter WIDTH = 4
) (
input wire [WIDTH-1:0] a, b,
input wire cin,
output wire [WIDTH-1:0] sum,
output wire cout
);
wire [WIDTH:0] carry;
assign carry[0] = cin;
assign cout = carry[WIDTH];
genvar i;
generate
for (i = 0; i < WIDTH; i = i + 1) begin : adder_stage
full_adder fa (
.a (a[i]),
.b (b[i]),
.cin (carry[i]),
.sum (sum[i]),
.cout (carry[i+1])
);
end
endgenerate
endmodule
Full source: code/ripple_carry_adder_gen.v.
genvar i — a special integer that only exists during elaboration, used purely to index
the unrolling. It has no hardware representation itself.begin : adder_stage — naming the generate block matters: it's how each unrolled copy
shows up in a waveform viewer or synthesis report, e.g. adder_stage[0].fa,
adder_stage[1].fa, ... — invaluable when you're debugging instance 5 of 16 and need to
find it.carry is WIDTH+1 bits wide specifically so each stage has both a
carry-in and carry-out wire, with carry[0] tied to the module's cin and
carry[WIDTH] exposed as cout.
Each box is one real, physically distinct full_adder instance produced by unrolling the
generate for loop — not one adder reused N times. Changing WIDTH changes how
much silicon this module occupies, decided entirely before synthesis runs.
repeat (6) begin ... end inside an initial block is a real
runtime loop — it's simulation-only scripting, executed step by step over simulated time. A
generate for loop is the opposite: it's synthesizable, and it disappears entirely before
runtime even begins, having already done its job of stamping out repeated structure. Same keyword
(for), two unrelated semantics — which context you're in (generate block vs.
initial/always) is what determines which one you get.
mux4 module from Lesson 3 with a WIDTH parameter (so it can
select between four buses of any width, not just 8 bits). Then write a generate loop that instantiates
WIDTH copies of a 1-bit mux2 to build the same 4-to-1 mux structurally out of
2-to-1 muxes, instead of using a case statement — a good exercise in seeing that the same
logical behavior can come from a "structural" description (wiring together known building blocks) as
easily as from a "behavioral" one (case/if).
New terms — parameter, elaboration, generate block, genvar — are in the glossary.