Verilog Crash Course Lesson 7 / 8
Lesson 7 · Scaling Up

Parameters & Generate Blocks

Learning objective Write a module whose bit width is configurable per-instance with 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.

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.

Don't confuse this with a testbench loop Lesson 6's 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.
Q: A generate-for loop runs from i = 0 to WIDTH-1, instantiating one full_adder per iteration. What determines how many full_adder instances exist in the final synthesized chip?
However many iterations actually execute while the chip is running. The value of WIDTH at elaboration (compile) time — exactly WIDTH separate, permanent instances, fixed before the chip exists. The clock frequency the design is run at.
Try it Parameterize the 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.

Previous← Testbenches & Simulation Contents⌂ All lessons