Skip to content

SystemVerilog · Module 7

Parameter Overrides — defparam & # Notation

Named vs positional overrides, why defparam is deprecated, type-checked parameters, string-parameter pitfalls, and hierarchy propagation.

Module 7 · Page 7.5

Two mechanisms let you change a module's parameters at instantiation time. One is modern, safe, and widely used. The other is legacy and actively discouraged. This page covers both — so you can write one and read the other — together with the elaboration timeline, hierarchical propagation patterns, type-checked parameters, verification implications, five real debugging labs, and twelve interview-ready questions.

Two Ways to Override a Parameter

When you instantiate a module that has parameters, you can change the parameter values for that specific instance. SystemVerilog gives you two mechanisms:

  • #( ) notation — the modern, recommended method. Parameters are overridden inline, right at the instantiation point using #(.PARAM(value)). Safe, readable, and supported everywhere.
  • defparam (legacy) — the old Verilog method. Parameters are overridden from anywhere in the file, not at the instantiation site. Confusing, hard to maintain, and formally deprecated. Read-only skill — never write new code with it.
✅ # Notation — Modern & Recommendedfifo #(.DATA_WIDTH(32),.DEPTH (256)) u_fifo (.clk (clk),.rst_n(rst_n));✅ Parameters visible right where the instance is created. Self-documenting. Tool catches wrong parameter names.⚠️ defparam — Legacy (Know to Read, Never Write)fifo u_fifo (.clk (clk),.rst_n(rst_n));defparam u_fifo.DATA_WIDTH = 32;defparam u_fifo.DEPTH = 256;// ↑ could be in a different file!⚠️ Parameters set AWAY from instance. Could be in a completely different file. Impossible to trace. Formally deprecated.Both produce identical hardware — but only one is maintainableBoth produce identical hardware — but only one is maintainable
Figure 1 — Both methods set the same parameter values and produce identical hardware. The difference is readability and safety.

# ( ) Notation — The Modern Way

The #( ) block is placed between the module name and the instance name at the point of instantiation. It has the same two styles as port connections: positional (values in order) and named (explicit parameter names). Named is strongly preferred for exactly the same reasons as named port connections — safe when parameters are reordered or added.

SystemVerilog — # Notation: Named vs Positional Override
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Module being instantiated:
// module fifo #(parameter int DATA_WIDTH=8, parameter int DEPTH=16) (...);
 
// ════ NAMED # override — ALWAYS USE THIS ══════════════════════════
// Syntax:  module_name  #(.PARAM1(val1), .PARAM2(val2))  instance_name (ports);
fifo #(
    .DATA_WIDTH(32),          // override DATA_WIDTH to 32
    .DEPTH     (256)          // override DEPTH to 256
) u_cpu_fifo (
    .clk  (clk),
    .rst_n(rst_n)
    // ... other ports
);
 
 
// ════ POSITIONAL # override — FRAGILE (avoid) ══════════════════════
// Values must be in the exact order the parameters were declared
fifo #(32, 256) u_pos_fifo (...);
//      ↑DATA_WIDTH  ↑DEPTH  — order must match module definition
 
 
// ════ PARTIAL override — only one parameter, rest stay at default ══
fifo #(.DEPTH(64)) u_small_fifo (...);
// DATA_WIDTH uses default (8); only DEPTH is overridden to 64
 
 
// ════ Use default — no # block at all ══════════════════════════════
fifo  u_default_fifo (...);
// Both DATA_WIDTH=8 and DEPTH=16 use their declared defaults

Override with Expressions and Constants

SystemVerilog — Parameter override with expressions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Top-level constants drive sub-module parameters ─────────────
localparam int SYS_DATA_W = 32;
localparam int CACHE_DEPTH = 512;
 
fifo #(
    .DATA_WIDTH(SYS_DATA_W),           // constant drives parameter
    .DEPTH     (CACHE_DEPTH / 2)       // expression drives parameter
) u_cache_fifo (...);
 
 
// ── String parameter override for mode selection ─────────────────
// module uart_tx #(parameter string MODE = "SYNC") (...);
 
uart_tx #(.MODE("ASYNC")) u_async_uart (...); // override to async mode
uart_tx #(.MODE("SYNC"))  u_sync_uart  (...); // use default (same value)
 
 
// ── Passing a parent's own parameter down to a child ─────────────
// (covered in detail in Section 5 — Hierarchical Override)
module cpu_core #(parameter int BUS_WIDTH = 32) (...);
 
    adder_n #(.WIDTH(BUS_WIDTH)) u_alu_add (...); // pass-through
 
endmodule

defparam (deprecated) — Legacy Verilog Method

defparam was the original Verilog mechanism for overriding parameters. Instead of putting the override at the instantiation point, defparam is a separate statement that can appear anywhere in the module hierarchy — even in a completely different file.

SystemVerilog — defparam syntax (know to read, never write)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── defparam syntax ──────────────────────────────────────────────
// defparam  hierarchical_path.PARAM_NAME = value;
 
module chip_top;
 
    // The instance — NO parameter override at the instantiation site
    fifo  u_fifo (
        .clk  (clk),
        .rst_n(rst_n)
    );
 
    // defparam sets the parameters — SEPARATE from the instance
    defparam u_fifo.DATA_WIDTH = 32;   // instance_name.PARAM_NAME = value
    defparam u_fifo.DEPTH      = 256;
 
    // ── defparam can use hierarchical paths across multiple levels ──
    // This is the REAL danger — modifying a parameter inside a
    // sub-sub-module from the top level, invisibly:
    defparam u_cpu.u_alu.u_adder.WIDTH = 64;  // ← buried 3 levels deep!
 
endmodule

How Parameter Override Works: Elaboration

Parameter overrides happen at elaboration — the compile phase where the tool builds the full module hierarchy before simulation or synthesis begins. No hardware exists yet; parameters are pure numbers. Understanding this timeline explains several rules that otherwise feel arbitrary.

time① ParseRead all .sv filesModule definitionsare catalogued② ElaborateExpand hierarchy, resolve #() overridesParameters resolved top-down:chip_top.u_cpu.u_alu.WIDTH = 32All localparams computed from them③ Synthesise / SimulateHardware generated with fixed values32-bit adder netlist created.No more parameters — just wiresParameters are compile-time constants. They do not exist in the synthesised netlist.
Figure 2 — Parameter overrides are resolved during elaboration (step ②), before any hardware is generated. The final netlist contains no parameters — only the resulting widths and depths.

Hierarchical Parameter Passing

In real designs, a top-level module defines a system-wide constant (like BUS_WIDTH = 32) and passes it down through every level of the hierarchy. Each intermediate module must accept it as a parameter and forward it to its children.

chip_toplocalparam BUS_WIDTH = 64defines the constant here#(.BUS_WIDTH(BUS_WIDTH))cpu_coreparameter int BUS_WIDTH = 32default 32, overridden to 64 by chip_topalu_unitparameter int BUS_WIDTH = 32receives 64 from cpu_corereg_fileparameter int BUS_WIDTH = 32receives 64 from cpu_coreBUS_WIDTH=64 set once at chip_top — flows automatically through the entire hierarchy
Figure 3 — A single constant defined at the top level is forwarded down through every intermediate module. Change it once at the top — every sub-block resizes automatically.
SystemVerilog — Passing Parameters Down Through Three Levels
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Level 0: chip_top — defines the system constant ─────────────
module chip_top;
    localparam int BUS_WIDTH = 64;      // single source of truth
 
    cpu_core #(
        .BUS_WIDTH(BUS_WIDTH)             // forward to child
    ) u_cpu (...);
endmodule
 
 
// ── Level 1: cpu_core — accepts and forwards the parameter ───────
module cpu_core #(
    parameter int BUS_WIDTH = 32         // default 32, overridden to 64 above
) (...);
 
    alu_unit #(.BUS_WIDTH(BUS_WIDTH)) u_alu (...);  // forward down again
    reg_file #(.BUS_WIDTH(BUS_WIDTH)) u_rf  (...);  // same parameter
 
endmodule
 
 
// ── Level 2: alu_unit — uses the parameter, derives localparams ──
module alu_unit #(
    parameter int BUS_WIDTH = 32         // receives 64 at runtime
) (
    input  logic [BUS_WIDTH-1:0] a, b,
    output logic [BUS_WIDTH-1:0] result
);
    localparam int HALF_W = BUS_WIDTH / 2; // 32 — auto-computed
 
    assign result = a + b;
endmodule

Type-Checked Parameters — Catching Mistakes at Elaboration

When you declare a parameter with an explicit type (parameter int WIDTH = 8), the tool checks that the override value is compatible with that type. This catches entire classes of bugs at elaboration — before simulation even starts.

SystemVerilog — Typed Parameter Guards
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module fifo #(
    parameter int          DATA_WIDTH = 8,   // must be a positive integer
    parameter int unsigned DEPTH      = 16,  // must be unsigned — no negatives
    parameter string       IMPL       = "LUT" // must be a string
) (...);
 
// ── Valid overrides ───────────────────────────────────────────────
fifo #(.DATA_WIDTH(32), .DEPTH(1024), .IMPL("BRAM")) u_ok (...);
 
// ── Tool will warn/error on these ────────────────────────────────
fifo #(.DATA_WIDTH(-8))   u_neg   (...); // ❌ negative width — nonsensical
fifo #(.DEPTH(3.14))     u_real  (...); // ❌ real value to int — truncated
fifo #(.IMPL(42))         u_int   (...); // ❌ integer to string — type error
 
 
// ── Parameter constraints with assertions (extra safety) ─────────
module safe_fifo #(
    parameter int DATA_WIDTH = 8,
    parameter int DEPTH      = 16
) (...);
 
    // Catch illegal parameter values at elaboration time
    initial begin
        if (DATA_WIDTH < 1 || DATA_WIDTH > 128)
            $fatal(1, "DATA_WIDTH must be 1..128, got %0d", DATA_WIDTH);
        if (DEPTH < 2 || (DEPTH & (DEPTH-1)) != 0)
            $fatal(1, "DEPTH must be a power of 2 >= 2, got %0d", DEPTH);
    end
 
endmodule
 
// Now this crashes at time-0 with a clear message:
safe_fifo #(.DEPTH(100)) u_bad (...);
// Fatal: "DEPTH must be a power of 2 >= 2, got 100"

String Parameters for Mode Selection

A parameter string lets you select between named behavioural modes at elaboration time — much more readable than a magic integer. Use this when a module has fundamentally different implementations depending on a configuration choice.

SystemVerilog — String parameter for mode selection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module ram_model #(
    parameter int    DATA_WIDTH = 32,
    parameter int    DEPTH      = 256,
    parameter string IMPL       = "BEHAVIORAL"  // "BEHAVIORAL" | "SRAM" | "ROM"
) (
    input  logic                   clk, we,
    input  logic [$clog2(DEPTH)-1:0] addr,
    input  logic [DATA_WIDTH-1:0]    wdata,
    output logic [DATA_WIDTH-1:0]    rdata
);
 
    generate
        if (IMPL == "BEHAVIORAL") begin : gen_behav
            logic [DATA_WIDTH-1:0] mem[0:DEPTH-1];
            always_ff @(posedge clk) begin
                if (we) mem[addr] <= wdata;
                rdata <= mem[addr];
            end
        end else if (IMPL == "ROM") begin : gen_rom
            // ROM: ignore writes, always read from pre-loaded array
            logic [DATA_WIDTH-1:0] rom_mem[0:DEPTH-1];
            initial $readmemh("rom_init.hex", rom_mem);
            always_ff @(posedge clk) rdata <= rom_mem[addr];
        end else begin : gen_unknown
            initial $fatal(1, "ram_model: unknown IMPL='%s'", IMPL);
        end
    endgenerate
 
endmodule
 
 
// ── Select the mode at instantiation ─────────────────────────────
ram_model #(.DATA_WIDTH(32), .DEPTH(1024), .IMPL("BEHAVIORAL")) u_sim_ram (...);
ram_model #(.DATA_WIDTH(32), .DEPTH(256),  .IMPL("ROM"))        u_boot_rom(...);

defparam vs # Notation — Full Comparison

Aspect# Notation (Named)defparam (Legacy)
Where is it written?At the instantiation point — inlineAnywhere in the module or file hierarchy
Self-documenting?Yes — visible with the instanceNo — override may be in a different file
Breaks on rename?No — uses parameter name, not instance pathYes — uses full hierarchical path; rename breaks silently
Named parameter support?Yes — .PARAM(val)No — uses hierarchical path
Tool supportUniversal — all tools fully support itDeclining — some tools warn or restrict it
IEEE 1800 statusPrimary, preferred mechanismFormally deprecated since IEEE 1800-2012
Use in new code?Yes — alwaysNever write it; read it in legacy code only
Type checking?Yes — with typed parametersLimited — no type enforcement

Common Mistakes

SystemVerilog — Parameter override mistakes and fixes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ MISTAKE 1: Using positional #() when order is non-obvious ══
fifo #(32, 256) u_bad (...);  // ❌ which is DATA_WIDTH, which is DEPTH?
// ✅ FIX: always use named
fifo #(.DATA_WIDTH(32), .DEPTH(256)) u_good (...);  // ✅ obvious
 
 
// ════ MISTAKE 2: Forgetting the # block — using wrong defaults ═══
// Module: parameter int DATA_WIDTH = 8, DEPTH = 16
fifo  u_oops (...);                 // ❌ forgot # — both use tiny defaults
// ✅ FIX: always write # even when only overriding one param
fifo #(.DEPTH(1024)) u_ok (...);   // ✅ DEPTH=1024, DATA_WIDTH stays at 8
 
 
// ════ MISTAKE 3: Trying to override a localparam ════════════════
// Inside fifo: localparam int PTR_W = $clog2(DEPTH);
fifo #(.PTR_W(10)) u_bad2 (...); // ❌ Error: PTR_W is localparam — cannot override
// ✅ FIX: override the parameter it derives from
fifo #(.DEPTH(1024)) u_good2 (...);// ✅ PTR_W = $clog2(1024) = 10 — auto-computed
 
 
// ════ MISTAKE 4: Not validating parameter values ═════════════════
fifo #(.DEPTH(100)) u_bad3 (...);  // ❌ 100 is not a power of 2 — pointer wraps wrong
// ✅ FIX: add $fatal check inside the module (see Section 6)

Quick Reference — Parameter Override Cheat Sheet

SystemVerilog — Parameter Override Quick Reference
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Named # override — ALWAYS USE THIS ────────────────────────
mod #(.P1(val1), .P2(val2)) u_inst (...);
 
// ── Partial override — only one param, rest use defaults ───────
mod #(.P2(val2)) u_inst (...);
 
// ── Use all defaults — no # block needed ───────────────────────
mod  u_inst (...);
 
// ── Override with expression or constant ───────────────────────
mod #(.WIDTH(SYS_W), .DEPTH(CACHE_D / 2)) u_inst (...);
 
// ── Forward parent's parameter to child ────────────────────────
module parent #(parameter int W = 32) (...);
    child #(.WIDTH(W)) u_c (...);    // pass-through
endmodule
 
// ── Validate parameters at elaboration ─────────────────────────
initial begin
    if (WIDTH < 1) $fatal(1, "WIDTH must be >= 1");
end
 
// ── defparam (LEGACY — read only) ──────────────────────────────
defparam u_inst.PARAM_NAME = value;      // ← never write new code like this
 
// ── localparam cannot be overridden ────────────────────────────
// localparam int HALF = WIDTH/2;  ← not in #() block — cannot be overridden

Verification Usage — Overrides Across the DUT/TB Boundary

Every override that varies a DUT width must be matched by an identical override on every TB component that talks to it. A bus driver hand-rolled at 32 bits cannot meaningfully test a DUT instantiated at 64 bits — but nothing in SystemVerilog stops you from connecting them. The cleanest production pattern is to feed a single config package to both the DUT and the TB stack.

SystemVerilog — One config package overrides DUT and TB in lockstep
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── soc_widths_pkg.sv — the single source of truth ───────────
package soc_widths_pkg;
    localparam int AXI_AW = 32;
    localparam int AXI_DW = 128;   // SoC-wide AXI data width
    localparam int APB_AW = 16;
    localparam int APB_DW = 32;
endpackage
 
// ── DUT instantiation — override from the package ───────────
import soc_widths_pkg::*;
axi_master_bfm #(
    .AW(AXI_AW),
    .DW(AXI_DW)
) u_dut ( .axi(axi_if_h), .clk, .rst_n );
 
// ── UVM agent + interface — same override values ──────────
import soc_widths_pkg::*;
axi_if #(.AW(AXI_AW), .DW(AXI_DW))   axi_if_h(.aclk(clk));
axi_driver #(.AW(AXI_AW), .DW(AXI_DW)) drv;
axi_monitor #(.AW(AXI_AW), .DW(AXI_DW)) mon;
axi_scoreboard #(.AW(AXI_AW), .DW(AXI_DW)) scb;
 
// Bump AXI_DW to 256 in the package → every override picks it up.
// No hunt-and-replace across 40+ instantiation sites.
SystemVerilog — Width sanity assertion at TB instantiation
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Drop-in safety net: at TB build time, prove the DUT and TB
// were elaborated with matching widths. Catches mismatched
// overrides before stimulus starts.
initial begin
    if ($bits(u_dut.axi.wdata) != $bits(drv.vif.wdata)) begin
        $fatal(1, "AXI_DW mismatch: DUT=%0d  TB=%0d",
                  $bits(u_dut.axi.wdata), $bits(drv.vif.wdata));
    end
    `uvm_info("WIDTH_OK", $sformatf("AXI_DW = %0d on both sides",
                                       $bits(drv.vif.wdata)), UVM_LOW)
end

Simulation Behavior — When the Override Takes Effect

Overrides are consumed during elaboration, before time 0. By the time the simulator's first Active region fires, every parameter has its final integer value baked into the netlist. There is no "parameter change event" at any simulation time — the value is constant from time 0 forward, and identical for every initial block that reads it.

SystemVerilog — Confirm the override resolved at time 0
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module ram #(
    parameter int AW = 8,
    parameter int DW = 32
) ( input logic clk );
    initial begin
        $info("%m elaborated: AW=%0d DW=%0d (depth=%0d)",
              AW, DW, 1 << AW);
    end
endmodule
 
// Two overrides — each instance reports independently at time 0
ram #(.AW(10), .DW(64))  u_l1 (.clk);
ram #(.AW(14), .DW(128)) u_l2 (.clk);
 
// Expected $info output at time 0:
//   [INFO] top.u_l1 elaborated: AW=10 DW=64  (depth=1024)
//   [INFO] top.u_l2 elaborated: AW=14 DW=128 (depth=16384)
//
// Add this pattern at every parameterised module's initial — the elab log
// becomes a self-documenting record of what every instance ended up with.

Waveform Analysis — How Override Values Become Visible Signals

A parameter is invisible in the waveform viewer (it's a constant, not a signal). What is visible is everything sized by it: the bit width of the address bus, the rollover point of a counter, the wrap point of a FIFO pointer. The same RTL source produces visibly different waveforms for different overrides — and that visible difference is your fastest check that the override took.

SystemVerilog — RAM under two parameter overrides
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module ram #(
    parameter int AW = 4,
    parameter int DW = 8
) (
    input  logic          clk, we,
    input  logic [AW-1:0] addr,
    input  logic [DW-1:0] wd,
    output logic [DW-1:0] rd
);
    logic [DW-1:0] mem [0:(1<<AW)-1];
    always_ff @(posedge clk) begin
        if (we) mem[addr] <= wd;
        rd <= mem[addr];
    end
endmodule
 
ram #(.AW(4),  .DW(8))   u_small  (...);  // 16 × 8 — 16-byte buffer
ram #(.AW(10), .DW(32))  u_large  (...);  // 1024 × 32 — 4 KiB

Both instances driven by the same address generator counting up from 0. The override changes what you see on the address bus and what the RAM does at rollover.

Expected waveform — same RTL, two overrides
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cycle             0    1    2    3   ...  14   15   16   17  ...
 
u_small (AW=4, DW=84-bit addr, 8-bit data):
  addr           00   01   02   03   ...  0E   0F   00   01  ←  wraps every 16
  mem write    [0]  [1]  [2]  [3]   ... [14] [15]  [0]  [1]
  rd[7:0]      8-bit value                                    ← narrow data bus
 
u_large (AW=10, DW=3210-bit addr, 32-bit data):
  addr         000  001  002  003   ... 00E  00F  010  011  ←  wraps every 1024
  mem write    [0]  [1]  [2]  [3]   ... [14] [15] [16] [17]
  rd[31:0]     32-bit value                                   ← wide data bus
 
Observation: identical address generator, identical clock —
  • the two address buses display different bit widths in the viewer
  • the two write activities target different memory depths
  • u_small's write at cycle 16 lands on address 0 (rollover);
    u_large's write at cycle 16 lands on address 16 (continuing).

Industry Insights — Override Practices in Production Teams

Debugging Academy — 5 Real Parameter-Override Bugs

Each lab is a real failure mode that shipped at least once. Buggy code, the symptom in simulation or silicon, why it happened, and the fix.

1

defparam in Legacy File Survives the Build — Tool-Dependent Netlist

CROSS-FILE OVERRIDE
Buggy Code
Legacy defparam fights a modern named override
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// In legacy_overrides.sv (someone added this in 2008, no one removed it):
defparam top.u_soc.u_dma.u_buf.DEPTH = 256;
 
// In modern top.sv:
dma_buf #(.DEPTH(1024)) u_buf (...);
 
// Which value wins?  Tool-dependent.
Symptom

RTL simulation on VCS shows DEPTH=1024. Regression on Questa shows DEPTH=256. Synthesis on Design Compiler picks one; Genus picks the other. Gate-level simulation mismatches RTL simulation. The team spends a week blaming the netlist compiler before finding the legacy defparam.

Root Cause

IEEE 1364 leaves resolution order between a defparam and an in-place named override implementation-defined. Different tools resolve in different orders. The two parameter mechanisms are not equivalent — they compete, and the tool decides who wins.

Fix

Delete every defparam in the codebase. Add a lint rule that fails CI on any new occurrence (Spyglass defparam_usage, Verilator -Wfatal-DEFPARAM). For each removed defparam, replace it with a named #() override at the original instantiation site. No exceptions, including in legacy code carved out of the build — the lint rule covers all source the tool reads.

2

Positional Override Drift After Parameter Reorder

POSITIONAL BREAKAGE
Buggy Code
Reordering parameters silently inverts every positional caller
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// fifo.sv — original
module fifo #(
    parameter int DATA_WIDTH = 8,
    parameter int DEPTH      = 16
) (...);
 
// Every parent uses positional override:
fifo #(32, 256) u_a (...);   // DATA_WIDTH=32, DEPTH=256
 
// Six months later, fifo.sv refactored:
module fifo #(
    parameter int DEPTH      = 16,   // ← reordered to "logical" position
    parameter int DATA_WIDTH = 8
) (...);
 
// 40 parent instantiations still use:
fifo #(32, 256) u_a (...);   // NOW: DEPTH=32, DATA_WIDTH=256 — silently inverted
Symptom

All 40 FIFOs now operate at unintended depth and width. Tests that previously passed start hitting overflow or width-mismatch warnings. Bug surfaces unevenly because some FIFOs were "close enough" (e.g., 32 wide × 32 deep instead of 256 wide × 32 deep is still functional for short-lived data).

Root Cause

Positional override binds by index, not by name. A module-level reorder silently rewires every positional override. SystemVerilog's parameter naming isn't part of the contract when the caller uses positional binding.

Fix

Convert every override to named form. fifo #(.DATA_WIDTH(32), .DEPTH(256)) u_a (...). Add a lint rule rejecting positional parameter overrides (Spyglass parameter_overrides_positional). Refactor the codebase once; never see this bug again.

3

Override Typo — Module Silently Uses Default

SILENT DEFAULT
Buggy Code
Typo in the parameter name disappears into a warning
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// fifo module declares DEPTH
module fifo #(parameter int DEPTH = 16) (...);
 
// Parent typos the override:
fifo #(.DEPHT(1024)) u_buf (...);
//      ^^^^^ should be DEPTH
 
// Most tools warn and proceed with default DEPTH=16.
Symptom

FIFO is 16 deep instead of 1024 deep. Backpressure on the producer kicks in after just 16 transactions — looks like a throughput regression. Engineering spends hours instrumenting the producer/consumer side before checking the FIFO parameter resolution.

Root Cause

SystemVerilog allows unknown parameter names in named override lists with at most a warning, not an error. Most tools have the warning suppressed because harmless cases produce thousands of them (e.g., wildcard overrides for unknown params).

Fix

Promote unknown-parameter-override from warning to error in CI: vcs +lint=PCWM -error=PCWM-L, Questa -pedanticerrors, Xcelium -LICQUEUE -wprintexit. Once enforced, the typo cannot silently ship.

4

Hierarchy Break — Parameter Stops Propagating Mid-Tree

PROPAGATION GAP
Buggy Code
Intermediate module forgets to forward the parameter
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module soc_top;
    cpu_subsystem #(.DATA_W(128)) u_cpu (...);
endmodule
 
module cpu_subsystem #(parameter int DATA_W = 32) (...);
    // Forgets to pass DATA_W down:
    l1_cache u_l1 (...);   // l1_cache.DATA_W = its default (often 64)
endmodule
 
module l1_cache #(parameter int DATA_W = 64) (...);  // DATA_W never updated
Symptom

CPU bus runs 128 bits, but L1 cache is 64 bits internally. The synthesis tool inserts byte-enable trimming at the boundary; gate-sim shows half the cache lines silently lose their upper 64 bits. Silicon validation finds the corruption on the most aggressive memory test.

Root Cause

Parameter propagation through hierarchy is manual. Each instantiation must explicitly pass parameters down; SystemVerilog does not inherit them automatically. A skipped propagation step truncates the override silently.

Fix
Explicit propagation at every level
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module cpu_subsystem #(parameter int DATA_W = 32) (...);
    l1_cache #(.DATA_W(DATA_W)) u_l1 (...);   // explicit propagation
endmodule

Better still: source DATA_W from a shared package (soc_widths_pkg::DATA_W) as the default for every module's local parameter. Propagation gaps still happen, but the package default keeps every module in sync without manual passthrough.

5

String Parameter Override — Case Mismatch Picks Wrong Variant

CASE SENSITIVITY
Buggy Code
Lowercase override doesn't match the module's UPPERCASE compare
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module reset_sync #(
    parameter string RESET_TYPE = "SYNC"   // or "ASYNC"
) (...);
    generate
        if (RESET_TYPE == "ASYNC") begin : async_reset
            always_ff @(posedge clk or negedge rst_n) ...
        end else begin : sync_reset
            always_ff @(posedge clk) ...
        end
    endgenerate
endmodule
 
// Parent override:
reset_sync #(.RESET_TYPE("async")) u_rst (...);  // lowercase typo!
Symptom

"async" does not equal "ASYNC", so the generate if falls through to the synchronous branch. The block uses synchronous reset where the integration plan said asynchronous. Power-on reset (which is asynchronous from the PMIC) doesn't reach the flops until the clock starts — which it can't, because the PLL is in reset. Deadlock at power-on.

Root Cause

String comparisons in SystemVerilog are case-sensitive. There is no implicit normalisation. The override is "technically valid" (string type, valid value) but doesn't match the magic strings the module compares against.

Fix

Inside the module, normalise the string before comparing (RESET_TYPE.tolower() == "async" — SystemVerilog string methods), or add an elaboration-time assertion that rejects any value outside the valid set: initial assert (RESET_TYPE inside {"SYNC", "ASYNC"}) else $fatal("Invalid RESET_TYPE: %s", RESET_TYPE);. Best practice: prefer enum-typed parameters over string parameters wherever possible, because enums catch the mismatch at elaboration.

Interview Q&A — 12 Questions on Parameter Overrides

Drawn from real interviews. Try to answer each before reading the response.

#() notation overrides parameters at the instantiation site, locally bound to one instance. defparam overrides from anywhere in the hierarchy by referencing the target instance by its full hierarchical path. The two mechanisms are not equivalent — they compete, and the IEEE LRM does not specify the resolution order if both apply to the same instance, so the elaborated design can differ between tools. Use named #() always; never use defparam.

Best Practices — Override Rules to Walk Away With

  1. Always use named overrides. fifo #(.DEPTH(256)) — never fifo #(256). Named override is immune to parameter reorder.
  2. Never use defparam. Lint rule fails CI on any occurrence. Tool-dependent resolution order makes defparam a tape-out risk.
  3. Promote unknown-parameter warning to error. vcs +lint=PCWM -error=PCWM-L, Questa -pedanticerrors. Typos silently using defaults is the #1 override bug.
  4. Source defaults from a shared package. parameter int DATA_W = soc_widths_pkg::AXI_DATA_W. One change in the package propagates everywhere.
  5. Propagate parameters explicitly at every hierarchy level. Child #(.W(W)) at every module ... #() () instantiation. Missing propagation is silent truncation.
  6. Add elaboration-time assertions for parameter invariants. initial assert (DEPTH > 0) else $fatal(...);. Catch invalid overrides before time 0.
  7. Prefer enum over string for mode parameters. typedef enum {SYNC, ASYNC} mode_e. Eliminates case-sensitivity and typo bugs by construction.
  8. Document the override contract per parameter. Legal range, dependencies, silicon-impact comment above each parameter declaration.
  9. Add a width-sanity initial in the TB. Compare $bits() of DUT and TB signals at time 0; $fatal on mismatch. Catches missed override propagation before stimulus starts.
  10. Run STA per shipped configuration. Each override produces a different netlist with a different critical path. A passing timing report on one width tells you nothing about the others.