In short: Verilog parameter declares a module-level constant a parent can override per instance to build one reusable design (e.g. a configurable N-bit bus), while localparam declares a derived constant that is sealed inside the module and cannot be overridden.
Ask any RTL engineer what separates a throwaway testbench hack from production-grade IP, and parameterization will be near the top of the list. A good adder, FIFO, or arbiter is written once and reused at 8 bits, 32 bits, 64 bits, with depth 16 or depth 1024, without ever touching the source. Verilog gives you two closely related constructs to do this cleanly: parameter and localparam. Understanding exactly how they differ — and how a parent overrides a child — is a classic interview question and a daily working skill.
What a parameter actually is
A parameter is a named, elaboration-time constant that belongs to a module. "Elaboration-time" matters: the value is fixed when the tool builds the netlist hierarchy, before any simulation time advances and long before synthesis maps gates. Because it is a constant, synthesis tools (Synopsys Design Compiler, Cadence Genus) treat it exactly like a literal — a 32-bit counter and an 8-bit counter from the same source become two completely different, independently optimized netlists.
module counter #(
parameter int WIDTH = 8 // default, overridable
) (
input logic clk, rst_n,
output logic [WIDTH-1:0] count
);
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) count <= '0;
else count <= count + 1'b1;
endmodule
The #( ... ) list is the ANSI-style parameter port, the modern form introduced with Verilog-2001 and the only one you should write today. The older style (parameter WIDTH = 8; on a line inside the body) still compiles but reads worse and interacts awkwardly with ports.
Key fact: A parameter is resolved at elaboration, not at run time. There is zero area or timing cost for the "configurability" — each instance is hardened to its chosen value, so a WIDTH=4 instance literally has only 4 flip-flops.
localparam: constants you must not let anyone change
A localparam looks like a parameter but cannot be overridden from outside the module. Use it for values derived from parameters, or for internal magic numbers that would be dangerous to expose. The classic example is an address width computed from a depth:
module sync_fifo #(
parameter int DEPTH = 16,
parameter int DATA_W = 32
) ( /* ports */ );
localparam int ADDR_W = $clog2(DEPTH); // derived, sealed
logic [ADDR_W-1:0] wr_ptr, rd_ptr;
// ...
endmodule
Here ADDR_W must always equal $clog2(DEPTH). If it were a plain parameter, a careless parent could override ADDR_W to 3 while leaving DEPTH at 16 — the pointers would wrap at 8 and silently corrupt data. Making it localparam makes that bug impossible: the relationship is locked.
Two ways a parent overrides a parameter
When you instantiate a parameterized module, you can replace its defaults in two syntactic styles.
| Style | Example | Notes |
|---|---|---|
| Named (recommended) | counter #(.WIDTH(32)) u_c (...); | Order-independent, self-documenting, skips unspecified params |
| Positional | counter #(32) u_c (...); | Fragile — depends on declaration order; avoid in real IP |
defparam (legacy) | defparam u_c.WIDTH = 32; | Deprecated; breaks easily with hierarchy and is discouraged by LRM and all vendors |
Key fact: Always use named overrides in production RTL. Positional overrides silently shift when someone adds a parameter above an existing one — a defect that passes lint and only shows up as a mis-sized bus three modules down.
Parameters that configure structure, not just width
Parameters are not limited to bus widths. They commonly select behavior: a FIFO's FIRST_WORD_FALL_THROUGH mode, an ALU's supported-operation set, or whether a pipeline register is present. Combined with Verilog generate blocks, a single parameter can switch entire hardware topologies in or out at elaboration:
module mac #(parameter bit PIPELINED = 1, parameter int W = 16) ( ... );
generate
if (PIPELINED) begin : g_pipe
// registered multiply then add
end else begin : g_flat
// combinational multiply-add
end
endgenerate
endmodule
This is how vendor-provided IP (Xilinx/AMD, Intel/Altera memory and DSP cores) exposes dozens of knobs from one encrypted source. The same discipline underpins the reusable blocks taught in our synthesizable RTL design track.
A parameter in motion: watching a configurable counter run
The animation below shows the lower bits of a WIDTH-configured up-counter toggling on each clock. Change WIDTH and the same RTL just gains or loses flip-flops — the logic never changes.
Common pitfalls and sign-off notes
- Width-type parameters without bounds. A parameter of
0can create a null range ([-1:0]). Guard with an elaboration assertion:initial if (WIDTH < 1) $fatal("WIDTH must be >= 1"); - Signedness and type. In SystemVerilog prefer typed parameters (
parameter int,parameter logic [7:0]). An untyped parameter infers its type from the override, which can surprise you in Siemens Questa or VCS when a value is passed as signed. - Lint it. Spyglass and the open-source Verilator both flag unused parameters and width mismatches caused by overrides — run lint before every synthesis handoff.
- Don't over-parameterize. Every knob is a verification obligation. If you expose
WIDTHfrom 1 to 64, you should have a regression sweep that elaborates and checks representative values, not just the default.
Teams building production IP often lean on structured training and review — our partners at Avecas VLSI design services use exactly these parameterization patterns across reusable analog-mixed-signal and digital blocks.
Want to build reusable RTL you can actually tape out? CourseTron's hands-on Verilog & SystemVerilog course walks you from parameters to full parameterized IP with lint, synthesis, and coverage. Explore the courses →
Frequently asked questions
Can a localparam be overridden with named instantiation?
No. That is the entire point of localparam. Only parameter appears in the override list; attempting to pass a value to a localparam is a compile error in every compliant tool.
Is parameter resolved at run time or compile time?
At elaboration (build) time — before simulation time zero and before synthesis. Each instance becomes a distinct, constant-folded design, so there is no runtime overhead for configurability.
Why is defparam discouraged?
Because defparam can reach across hierarchy and reassign parameters from anywhere, making designs hard to analyze and order-dependent. The IEEE 1800 LRM and every major vendor recommend named #(...) overrides instead.
When should I use localparam vs parameter?
Use parameter for knobs the parent should set (bus width, FIFO depth, feature flags). Use localparam for anything derived from those knobs (address width, state encodings, computed constants) so the relationship can never be broken from outside.