VLSI

Verilog Parameters

By CourseTron Team ·  2 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    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.

    One source, two hardened netlists Parent (SoC top) counter #(.WIDTH(32)) u_big (...); counter #(.WIDTH(4)) u_small (...); counter (WIDTH = 32) 32 flip-flops wide incrementer counter (WIDTH = 4) 4 flip-flops override CourseTron · coursetron.com
    One parameterized source → two hardened netlists. The parent chooses WIDTH per instance; elaboration bakes each into silicon-ready logic.

    Two ways a parent overrides a parameter

    When you instantiate a parameterized module, you can replace its defaults in two syntactic styles.

    StyleExampleNotes
    Named (recommended)counter #(.WIDTH(32)) u_c (...);Order-independent, self-documenting, skips unspecified params
    Positionalcounter #(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.

    Three ways to override a parameter Named #(.WIDTH(32)) Order-independent Self-documenting Recommended Positional #(32) Depends on order Shifts silently Avoid in IP defparam defparam u_c.WIDTH = 32; Reaches across hierarchy Legacy, discouraged Deprecated CourseTron · coursetron.com
    Override styles compared: named overrides are order-independent, while positional and defparam forms are fragile.

    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.

    4-bit counter (WIDTH = 4): each bit toggles at half the rate below it clk b0b1b2b3 Counts 0 to 2^WIDTH − 1 (15 here), then wraps. A larger WIDTH only adds more rows. CourseTron · coursetron.com
    Animated view: each clock edge the carry ripples up; a wider WIDTH simply pushes the wrap point further out (2^WIDTH − 1).

    Common pitfalls and sign-off notes

    • Width-type parameters without bounds. A parameter of 0 can 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 WIDTH from 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.

    Advertisement