Tutorial 08 · Verilog Series

Verilog Parameters & generate

Parameters make modules configurable without changing source code. The generate statement lets you build hardware structures programmatically — creating N-bit datapaths, repeating logic arrays, or conditionally instantiating different submodules. Together they are the foundation of reusable IP design.

parameter / localparam defparam (avoid) generate / endgenerate genvar generate-for generate-if
module adder #(parameter N=8) input [N-1:0] a,b output [N:0] sum One module, any bit width 8-bit instance #(.N(8)) 16-bit instance #(.N(16)) 32-bit instance #(.N(32)) generate-for genvar i; for(i=0; i<N; i=i+1) full_adder fa_i(...); Elaboration-time loop → N hardware instances
One parameterized module generates any-width hardware at elaboration time. generate-for replicates instances N times.

1. parameter — Module Constants

Parameters are named constants defined in the module header. They can be overridden at instantiation time.

verilog
// Modern ANSI-style parameter declaration
module adder #(
    parameter WIDTH = 8,          // default: 8-bit
    parameter SIGNED = 0          // 0=unsigned, 1=signed
) (
    input  [WIDTH-1:0] a, b,
    output [WIDTH  :0] sum   // one extra bit for carry
);
    assign sum = {1'b0, a} + {1'b0, b};
endmodule

2. localparam — Internal Constants

localparam is a constant visible only inside its module — it cannot be overridden. Use it for constants derived from parameters or for internal state encoding.

verilog
module fifo #(
    parameter DEPTH = 16,
    parameter WIDTH = 8
) (/*...*/);
    // Derived from parameter — cannot be overridden
    localparam ADDR_W = $clog2(DEPTH);  // log2(16)=4
    localparam FULL  = DEPTH - 1;

    reg [WIDTH-1:0] mem    [0:DEPTH-1];
    reg [ADDR_W-1:0] rd_ptr, wr_ptr;
endmodule
$clog2: This system function computes ⌈log₂(N)⌉ — the number of bits needed to represent N values. It's synthesizable and is the standard way to compute address widths: localparam ADDR_W = $clog2(DEPTH);

3. Overriding Parameters at Instantiation

verilog
// Named parameter override (recommended — order-independent)
adder #(
    .WIDTH(16),
    .SIGNED(1)
) u_add16 (
    .a(op_a),
    .b(op_b),
    .sum(result)
);

// Positional parameter override (fragile — avoid)
adder #(32, 0) u_add32 (.a(x), .b(y), .sum(z));

// Default (use module's default values)
adder u_add_default (.a(x), .b(y), .sum(z));  // WIDTH=8, SIGNED=0

4. defparam (Legacy — Avoid)

defparam lets you set a parameter from outside the module by hierarchical path. It is deprecated in SystemVerilog and should not be used in new designs.

verilog — defparam (avoid this)
// DON'T DO THIS — use #(.PARAM()) at instantiation instead
defparam u_add.WIDTH = 16;  // sets parameter via hierarchical path
Problems with defparam: It breaks hierarchical analysis — tools can't determine parameter values just by reading the module. It's not allowed in synthesis flows. Always use #(.PARAM(value)) at instantiation instead.

5. generate Statement Overview

The generate block runs at elaboration time — before simulation starts. It can create hardware instances, wires, assign statements, and always blocks based on parameters.

verilog — structure
generate
    // genvar declaration for loop variables
    // generate-for, generate-if, generate-case
    // module instances, assign, always — all valid here
endgenerate

6. generate-for Loop

The most common use: replicate a module or logic N times.

verilog — N-bit ripple carry adder using generate
module rca #(parameter N=8) (
    input  [N-1:0] a, b,
    input        cin,
    output [N-1:0] sum,
    output       cout
);
    wire [N:0] carry;       // N+1 carry wires
    assign carry[0] = cin;
    assign cout     = carry[N];

    genvar i;
    generate
        for (i=0; i<N; i=i+1) begin : fa_stage
            full_adder fa (
                .a   (a[i]),
                .b   (b[i]),
                .cin (carry[i]),
                .sum (sum[i]),
                .cout(carry[i+1])
            );
        end
    endgenerate
endmodule

Each iteration creates a labeled instance: fa_stage[0].fa, fa_stage[1].fa, …, fa_stage[N-1].fa. You can probe any stage in simulation by hierarchical path.

verilog — generate-for with assign (no submodule needed)
// Bit-reverse a vector without a submodule
module bit_reverse #(parameter N=8) (
    input  [N-1:0] in_bus,
    output [N-1:0] out_bus
);
    genvar j;
    generate
        for (j=0; j<N; j=j+1) begin : rev
            assign out_bus[j] = in_bus[N-1-j];
        end
    endgenerate
endmodule

7. generate-if / generate-case

Conditionally include different hardware implementations based on parameter values:

verilog — conditional hardware via generate-if
module multiplier #(parameter USE_DSP=1, parameter N=8) (
    input  [N-1:0] a, b,
    output [2*N-1:0] product
);
    generate
        if (USE_DSP) begin : dsp_mult
            // Use dedicated DSP primitive (FPGA / ASIC cell)
            dsp_block u_mul (.A(a), .B(b), .P(product));
        end else begin : lut_mult
            // Synthesize from LUTs (smaller but slower)
            assign product = a * b;
        end
    endgenerate
endmodule
generate-if vs always-if: generate if (PARAM) checks a constant at elaboration time — only one branch is ever compiled into the netlist. always @(*) if (signal) checks a runtime signal — both branches are synthesized as mux hardware. Use generate-if for architecture selection; use always-if for runtime control.

8. Full Design Examples

Parameterizable Synchronous RAM

verilog
module sync_ram #(
    parameter DEPTH = 256,
    parameter WIDTH = 8
) (
    input                   clk, wr_en,
    input  [$clog2(DEPTH)-1:0] addr,
    input  [WIDTH-1:0]        din,
    output reg [WIDTH-1:0]  dout
);
    reg [WIDTH-1:0] mem [0:DEPTH-1];
    always @(posedge clk) begin
        if (wr_en) mem[addr] <= din;
        dout <= mem[addr];   // registered read
    end
endmodule

// Instantiate 1024-deep, 32-bit wide RAM
sync_ram #(.DEPTH(1024), .WIDTH(32)) u_ram (...);

N-bit Gray Code Counter (generate-for + always)

verilog
module gray_counter #(parameter N=4) (
    input         clk, rst_n,
    output [N-1:0] gray
);
    reg [N-1:0] bin;
    always @(posedge clk or negedge rst_n)
        if (!rst_n) bin <= 0;
        else        bin <= bin + 1;

    // Binary to Gray: gray[i] = bin[i] ^ bin[i+1]
    genvar i;
    generate
        for (i=0; i<N-1; i=i+1) begin : g2b
            assign gray[i] = bin[i] ^ bin[i+1];
        end
    endgenerate
    assign gray[N-1] = bin[N-1];  // MSB stays same
endmodule

9. Summary & Best Practices

FeatureUse WhenNotes
parameterValue can vary between instancesOverride with #(.NAME(val)) at instantiation
localparamInternal derived constantCannot be overridden; use $clog2 for widths
defparamNeverDeprecated — use parameter override syntax
generate-forReplicate N identical structuresUse genvar; label begin blocks
generate-ifSelect architecture based on parameterOne branch is compiled; the other is discarded
generate-caseMultiple architecture optionsSame as generate-if but for multiple choices