Parameters, localparam, and generate
Make a module reusable without turning its internal settings into hardware inputs.
A parameter is fixed before synthesis
A parameter configures the structure of a module when it is instantiated. It does not become a circuit input and cannot change while the circuit is running.
module masked_register #(
parameter WIDTH = 8
) (
input wire i_clk,
input wire i_reset,
input wire i_load,
input wire [WIDTH-1:0] i_data,
input wire [WIDTH-1:0] i_mask,
output reg [WIDTH-1:0] o_data
);
always @(posedge i_clk) begin
if (i_reset)
o_data <= {WIDTH{1'b0}};
else if (i_load)
o_data <= (o_data & ~i_mask) | (i_data & i_mask);
end
endmoduleThe same source can produce an 8, 16, or 32-bit register. The {WIDTH{1'b0}} replication automatically follows the selected width.
Overriding a parameter at instantiation
Named association avoids relying on parameter order:
masked_register #(
.WIDTH(16)
) u_control_register (
.i_clk (i_clk),
.i_reset (i_reset),
.i_load (i_load),
.i_data (i_data),
.i_mask (i_mask),
.o_data (o_data)
);A width used in the ports must exactly match the signals connected to the instance. A truncation warning is not something to ignore.
Keeping internal calculations in localparam
A localparam defines an internal constant that an instance cannot override. It is appropriate for derived values and encodings that belong to the implementation.
module pulse_every #(
parameter PERIOD = 10
) (
input wire i_clk,
input wire i_reset,
output reg o_tick
);
localparam COUNTER_WIDTH = 32;
localparam LAST_COUNT = PERIOD - 1;
reg [COUNTER_WIDTH-1:0] count;
always @(posedge i_clk) begin
if (i_reset) begin
count <= 0;
o_tick <= 1'b0;
end else if (count == LAST_COUNT) begin
count <=
Here, PERIOD belongs to the configuration interface. LAST_COUNT is only the bound used by the implementation. A real project must also reject or clearly handle PERIOD = 0.
Repeating a structure with generate
A generate loop creates several instances during elaboration. It is not a loop executed on every clock cycle.
module parity_per_byte #(
parameter BYTE_COUNT = 4
) (
input wire [BYTE_COUNT*8-1:0] i_data,
output wire [BYTE_COUNT-1:0] o_parity
);
genvar lane;
generate
for (lane = 0; lane < BYTE_COUNT; lane = lane + 1) begin : g_parity
assign o_parity[lane] = ^i_data[lane*8 +: 8];
end
endgenerate
endmoduleEach iteration produces separate parity logic. The g_parity name makes an individual lane easy to find in the simulation hierarchy.
Selecting a structure during elaboration
An if inside a generate block depends on a constant. Only one branch exists in the resulting circuit.
module optional_output_register #(
parameter WIDTH = 8,
parameter REGISTER_OUTPUT = 1
) (
input wire i_clk,
input wire i_reset,
input wire [WIDTH-1:0] i_data,
output wire [WIDTH-1:0] o_data
);
generate
if (REGISTER_OUTPUT) begin : g_registered
reg [WIDTH-1:0] data_reg;
always @(posedge i_clk) begin
if (i_reset)
data_reg <= {WIDTH{1'b0}};
else
The registered version adds flip-flops and one cycle of latency. The direct version does not. This choice therefore belongs in the block documentation.
Key points
- A
parameterconfigures an instance before synthesis. - A
localparamprotects a constant owned by the implementation. - A
generateloop duplicates hardware. - An
if generatekeeps only one architecture for a given configuration. - A configurable value must remain valid for every advertised configuration.
📝 Test your knowledge - Chapter quiz