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.
1. parameter — Module Constants
Parameters are named constants defined in the module header. They can be overridden at instantiation time.
// 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
- Parameters can be integers, strings, or real values
- You can use parameters in port declarations, expressions, and other parameters
- The default value is used when no override is provided
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.
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
localparam ADDR_W = $clog2(DEPTH);3. Overriding Parameters at Instantiation
// 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.
// DON'T DO THIS — use #(.PARAM()) at instantiation instead defparam u_add.WIDTH = 16; // sets parameter via hierarchical path
#(.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.
generate // genvar declaration for loop variables // generate-for, generate-if, generate-case // module instances, assign, always — all valid here endgenerate
- Use
genvarfor loop variables — declare it outside the generate block - Each generated instance gets a unique name:
inst_name[0],inst_name[1], etc. - Label generate blocks for cleaner hierarchical names:
begin : label_name
6. generate-for Loop
The most common use: replicate a module or logic N times.
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.
// 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:
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 (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
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)
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
| Feature | Use When | Notes |
|---|---|---|
parameter | Value can vary between instances | Override with #(.NAME(val)) at instantiation |
localparam | Internal derived constant | Cannot be overridden; use $clog2 for widths |
defparam | Never | Deprecated — use parameter override syntax |
generate-for | Replicate N identical structures | Use genvar; label begin blocks |
generate-if | Select architecture based on parameter | One branch is compiled; the other is discarded |
generate-case | Multiple architecture options | Same as generate-if but for multiple choices |