Skip to content

Verilog · Chapter 6.1 · Constant Variables

parameter in Verilog — User-Overridable Compile-Time Constants

The parameter keyword is the entry point to parameterised RTL design, the discipline that turns a single source module into many hardware variants at instantiation time. Every reusable IP block, such as a FIFO, counter, register file, arbiter, decoder, or ALU, has parameters for width, depth, mode, and encoding. Without parameters, every variant would need its own source-code copy and the maintenance debt would compound until the project collapsed under its own weight. This lesson is the deep drill: every declaration form in the port list or in the body, every override syntax whether named or ordered, the hierarchical-passing pattern that propagates parameters through multiple module levels, typed parameters with explicit width and signedness, the difference between a parameter and the strictly internal localparam, and the deprecated defparam that every code review should reject.

Foundation22 min readVerilogparameterOverrideParameterisationIPReuse

Chapter 6 · Section 6.1 · Constant Variables

1. The Engineering Problem

A team has three counters in their design:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Counter 1 — 8-bit byte counter for UART
module uart_byte_counter (
    input  wire       clk, rst_n, en,
    output reg  [7:0] count_q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) count_q <= 8'h00;
        else if (en) count_q <= count_q + 8'h01;
    end
endmodule
 
// Counter 2 — 32-bit cycle counter
module cycle_counter (
    input  wire        clk, rst_n, en,
    output reg  [31:0] count_q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) count_q <= 32'h00000000;
        else if (en) count_q <= count_q + 32'h00000001;
    end
endmodule
 
// Counter 3 — 12-bit ADC sample counter
module sample_counter (...);  // same shape, 12-bit

Three modules, three files, three sets of bugs to fix when the team finds an issue. The team's tech lead writes a parameterised version:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module up_counter #(
    parameter WIDTH = 8                              // user-overridable, default 8
)(
    input  wire             clk, rst_n, en,
    output reg  [WIDTH-1:0] count_q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)        count_q <= {WIDTH{1'b0}};
        else if (en)       count_q <= count_q + 1'b1;
    end
endmodule

One module. The three counters become three instantiations:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
up_counter #(.WIDTH(8))  u_uart_cnt  (.clk(clk), .rst_n(rst_n), .en(uart_en), .count_q(uart_cnt));
up_counter #(.WIDTH(32)) u_cycle_cnt (.clk(clk), .rst_n(rst_n), .en(1'b1),    .count_q(cycle_cnt));
up_counter #(.WIDTH(12)) u_adc_cnt   (.clk(clk), .rst_n(rst_n), .en(adc_smp), .count_q(adc_cnt));

Same source. Three different widths. Bug fixes apply once and propagate to all three. New features (e.g., adding a synchronous load) land in one place. The team's productivity multiplies.

This is the parameterisation pattern — and the parameter keyword is the language mechanism that makes it work. This page covers everything you need to know to write parameterised modules and instantiate them correctly.

2. Anatomy of a parameter Declaration

Verilog supports two syntactic forms for declaring parameters: port-list style (Verilog-2001, preferred) and in-body style (Verilog-1995, legacy).

2.1 Port-list style (preferred)

port-list-parameter.v — Verilog-2001 form
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module fifo #(
    parameter WIDTH       = 8,                      // simple parameter, default 8
    parameter DEPTH       = 16,                      // simple parameter
    parameter [3:0] MODE  = 4'h0,                    // typed parameter (4-bit value)
    parameter signed [31:0] OFFSET = -1              // typed signed parameter
)(
    input  wire             clk,
    input  wire             rst_n,
    output reg  [WIDTH-1:0] q
);
    // ... use WIDTH, DEPTH, MODE, OFFSET anywhere ...
endmodule

The parameters live between module name and the port list, inside #(...). Each parameter has:

  • Name — the identifier (WIDTH, DEPTH, MODE).
  • Default value — what to use if the parent doesn't override.
  • Optional type — width and signedness specifier ([3:0], signed [31:0]).

The convention: ALL_CAPS names for parameters, no exceptions.

2.2 In-body style (legacy)

in-body-parameter.v — Verilog-1995 form
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module fifo (
    input  wire             clk,
    input  wire             rst_n,
    output reg  [WIDTH-1:0] q
);
    parameter WIDTH = 8;          // in-body parameter declaration
    parameter DEPTH = 16;
 
    // ... use WIDTH, DEPTH ...
endmodule

The parameters appear after the port list, inside the module body. Functionally equivalent to the port-list style for override purposes — a parent module can override either form via the same #(.NAME(value)) syntax at instantiation.

The in-body form is the only option in pre-2001 Verilog codebases; modern code uses port-list style for module-public parameters. Mixing both forms in one module is legal but rarely useful — most modules use either all-port-list or all-in-body.

2.3 The default value

Every parameter should have a default value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
parameter WIDTH = 8;              // default 8 — used if not overridden
parameter MODE  = 0;
parameter [15:0] OFFSET = 16'h00; // typed, default 0

Omitting the default is legal but discouraged — a parameter declared parameter WIDTH; requires every instantiation to override it, which defeats the purpose of "configurable with a sensible default." Some tools also produce warnings for parameters without defaults.

3. Mental Model

The mental-model has three practical consequences:

  • Pick parameter names that match the design intent. WIDTH, DEPTH, MODE are universal; DATA_WIDTH, ADDR_DEPTH, BURST_MODE are more specific. Match the granularity to the codebase convention.
  • Provide defaults that are useful. The default should produce a working instance — e.g., the smallest reasonable width, the most-common mode. If the default is "user must override," remove the default and let the instantiation fail at elaboration with a clear error.
  • Document the parameter contract. The parameter is part of the module's public API; the comment on its declaration is part of the API contract. "Default 8-bit; supports 1, 2, 4, 8, 16, 32, 64-bit widths" is the right level of detail.

4. Parameter Override Syntax

The parent module overrides parameters at instantiation. Two syntactic forms exist.

4.1 Named override (preferred)

named-override.v — the preferred form
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module top;
    wire clk, rst_n;
    wire [31:0] cycle_count;
    wire [11:0] adc_count;
 
    // Named override — clear, order-independent
    up_counter #(.WIDTH(32)) u_cycle (
        .clk     (clk),
        .rst_n   (rst_n),
        .en      (1'b1),
        .count_q (cycle_count)
    );
 
    up_counter #(.WIDTH(12)) u_adc (
        .clk     (clk),
        .rst_n   (rst_n),
        .en      (adc_sample_en),
        .count_q (adc_count)
    );
endmodule

The #(.NAME(value)) syntax names each parameter explicitly. Three benefits:

  1. Order-independent. Adding a new parameter to up_counter doesn't break existing instantiations — the new parameter's default applies unless the instantiation also names it.
  2. Self-documenting. The name at the call site explains what each override controls.
  3. Refactor-safe. Renaming a parameter in the module breaks compilation at every instantiation, making the migration visible.

Use the named form everywhere. It is the modern Verilog standard and required by most style guides.

4.2 Ordered override (legacy, fragile)

ordered-override.v — discouraged
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module top;
    // Ordered override — positional, fragile
    up_counter #(32) u_cycle (.clk(clk), .rst_n(rst_n), .en(1'b1), .count_q(cycle_count));
    up_counter #(12) u_adc   (.clk(clk), .rst_n(rst_n), .en(en),   .count_q(adc_count));
endmodule

The #(value1, value2, ...) syntax assigns values to parameters by position in the module's declaration order. Two problems:

  1. Fragile to parameter list changes. Adding a new parameter to up_counter between existing ones shifts every subsequent override silently — every instantiation now sets the wrong parameters.
  2. Opaque at the call site. Reading up_counter #(32, 16, 1) requires consulting the module's declaration to know what each value means.

The ordered form is legal Verilog but forbidden in most working codebases. Use named overrides exclusively.

4.3 Multi-parameter override

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Named — adding a new parameter doesn't break this
fifo #(
    .DATA_WIDTH (32),
    .DEPTH      (64),
    .MODE       (2)
) u_fifo (
    .clk     (clk),
    .rst_n   (rst_n),
    .data_in (din),
    .data_out(dout)
);
 
// Ordered — fragile
fifo #(32, 64, 2) u_fifo (...);

Named overrides scale cleanly to many parameters. Ordered overrides become unreadable past 2-3 parameters and brittle when the module evolves.

5. Hierarchical Parameter Passing

A common pattern: parameters propagate through multiple levels of module hierarchy.

hierarchical-passing.v — parameter cascade
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module top #(parameter DATA_WIDTH = 32, parameter DEPTH = 256) (
    input  wire clk, rst_n,
    // ... ports ...
);
    // Pass DATA_WIDTH and DEPTH to the fifo
    fifo #(
        .WIDTH (DATA_WIDTH),
        .DEPTH (DEPTH)
    ) u_fifo (
        .clk   (clk),
        .rst_n (rst_n)
        // ... ports ...
    );
endmodule
 
module fifo #(parameter WIDTH = 8, parameter DEPTH = 16) (
    input  wire clk, rst_n
    // ... ports ...
);
    // Forward WIDTH and DEPTH to the storage
    fifo_storage #(
        .W (WIDTH),
        .D (DEPTH)
    ) u_storage (
        .clk   (clk),
        .rst_n (rst_n)
        // ... ports ...
    );
endmodule
 
module fifo_storage #(parameter W = 8, parameter D = 16) (
    input  wire clk, rst_n
);
    reg [W-1:0] mem [0:D-1];
    // ... storage logic ...
endmodule

Each level declares its own parameters. The override at the top (DATA_WIDTH = 32) propagates through fifo to fifo_storage. The parameter names can differ at each level (DATA_WIDTHWIDTHW) — only the override syntax #(.NAME(expression)) connects them.

The convention: name the parameter to match the level. The top module's DATA_WIDTH is descriptive; the storage module's W is terse because it's an internal sub-module. Either approach is fine — pick the granularity that matches the codebase.

6. Typed Parameters

A parameter can have an explicit type — width and signedness — beyond the inferred type from its default value.

typed-parameters.v — explicit width and signedness
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module example #(
    parameter             WIDTH = 8,                    // inferred from default (integer)
    parameter [7:0]       BYTE_VAL = 8'hAA,             // 8-bit unsigned
    parameter signed [15:0] OFFSET = -100,              // 16-bit signed
    parameter [31:0]      DEFAULT_ADDR = 32'h0000_1000  // 32-bit value
)(
    input  wire clk
);
    // ... use parameters ...
endmodule

When to use typed parameters:

  • Bit-vector constants that should have a specific width regardless of the default value's bit width. parameter [7:0] BYTE_VAL = 8'hAA; is exactly 8 bits; parameter BYTE_VAL = 8'hAA; (untyped) might be promoted to a wider integer in arithmetic.
  • Signed values. parameter signed [15:0] OFFSET = -1; makes the sign-extension behaviour explicit. The untyped form might be ambiguous.
  • Override safety. A typed parameter rejects overrides that don't match the type — BYTE_VAL #(.BYTE_VAL(1024)) would warn because 1024 doesn't fit in 8 bits.

The untyped form (parameter WIDTH = 8;) is inferred from the default value's type — IEEE 1364-2005 treats it as a 32-bit signed integer for purposes of arithmetic. For most "configuration knob" parameters this works fine; for bit-pattern constants, use the typed form.

7. Visual Explanation

Three figures cover the parameterisation pattern.

7.1 Visual A — parameter declaration and override flow

The interaction between module declaration and module instantiation.

parameter declaration and override flow

data flow
parameter declaration and override flowmodule up_counterdeclares parameter WIDTH = 8instance#(WIDTH=32)parent overrides defaultinstance#(WIDTH=8)parent uses default32-bit counter32 flops8-bit counter8 flops
One source module + N parameter overrides = N hardware variants. The default is the smallest reasonable instance; instantiations can override per-instance for different needs. Both instances share the same source — bug fixes apply once and propagate everywhere.

7.2 Visual B — named vs ordered override

The structural difference between the two override syntaxes.

Named vs ordered parameter overridemodule fifoparameter WIDTH=8, DEPTH=16Named override#(.WIDTH(32), .DEPTH(64))Ordered override#(32, 64)RobustAdding parameter is safeFragileAdding parameter SHIFTSpositions12
Named override binds parameter values by NAME — adding a new parameter doesn't break existing call sites. Ordered override binds by POSITION — adding a parameter shifts every value silently. The named form is order-independent, self-documenting, and refactor-safe.

7.3 Visual C — hierarchical parameter passing

The cascade pattern. A top-level parameter propagates through multiple module levels.

Hierarchical parameter cascadetopparameter DATA_WIDTH = 32fifoparameter WIDTH = 8fifo_storageparameter W = 8override #1.WIDTH(DATA_WIDTH)override #2.W(WIDTH)12
The hierarchical parameter cascade. top has DATA_WIDTH; it overrides fifo's WIDTH; fifo's WIDTH overrides fifo_storage's W. Each level declares its own parameter; the names can differ at each level, but the override syntax connects them. The synthesis tool elaborates the entire chain at once, producing one correctly-sized FIFO storage block.

8. The Parameter-Driven Generate Block

A parameter can drive a generate block — producing a different hardware structure depending on the parameter's value.

parameter-driven-generate.v — conditional hardware via generate
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module configurable_adder #(parameter MODE = 0) (
    input  wire [7:0]  a,
    input  wire [7:0]  b,
    output wire [7:0]  y
);
    // MODE = 0 → ripple-carry adder
    // MODE = 1 → carry-lookahead adder
    // MODE = 2 → carry-skip adder
 
    generate
        if (MODE == 0) begin : g_ripple
            ripple_carry_adder u_add (.a(a), .b(b), .y(y));
        end
        else if (MODE == 1) begin : g_cla
            carry_lookahead_adder u_add (.a(a), .b(b), .y(y));
        end
        else if (MODE == 2) begin : g_skip
            carry_skip_adder u_add (.a(a), .b(b), .y(y));
        end
        else begin : g_default
            assign y = a + b;          // fallback: synthesis tool's default
        end
    endgenerate
endmodule

The generate block expands at elaboration time based on the parameter's value. Only one of the four code paths is included in the netlist — the others are stripped before synthesis. The synthesis tool sees a single adder structure determined by MODE.

This is the canonical pattern for mode-dependent hardware — the same module produces different gates depending on a parameter. Chapter 14.7 covers generate blocks in detail.

9. The Parameterised Register File

A canonical real-world parameter use: a register file with configurable size and read-port count.

rtl/regfile_param.v — fully parameterised register file
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module regfile #(
    parameter DATA_WIDTH = 32,
    parameter NUM_REGS   = 32,
    parameter NUM_READ_PORTS = 2
)(
    input  wire                              clk,
    input  wire                              rst_n,
    input  wire                              wr_en,
    input  wire [ADDR_W-1:0]                wr_addr,
    input  wire [DATA_WIDTH-1:0]             wr_data,
    input  wire [NUM_READ_PORTS-1:0][ADDR_W-1:0] rd_addr,
    output reg  [NUM_READ_PORTS-1:0][DATA_WIDTH-1:0] rd_data
);
    localparam ADDR_W = $clog2(NUM_REGS);
 
    reg [DATA_WIDTH-1:0] rf [0:NUM_REGS-1];
 
    integer i;
 
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (i = 0; i < NUM_REGS; i = i + 1)
                rf[i] <= {DATA_WIDTH{1'b0}};
        end
        else if (wr_en) begin
            rf[wr_addr] <= wr_data;
        end
    end
 
    // Asynchronous read for each read port
    genvar p;
    generate
        for (p = 0; p < NUM_READ_PORTS; p = p + 1) begin : g_rd_port
            always @(*) rd_data[p] = rf[rd_addr[p]];
        end
    endgenerate
endmodule
 
// Instantiation examples:
//   regfile #(.DATA_WIDTH(32), .NUM_REGS(32), .NUM_READ_PORTS(2)) u_riscv_rf (...);
//   regfile #(.DATA_WIDTH(64), .NUM_REGS(16), .NUM_READ_PORTS(3)) u_dsp_rf  (...);
//   regfile #(.DATA_WIDTH(8),  .NUM_REGS(256), .NUM_READ_PORTS(1)) u_misc_rf (...);

Three parameters, all overridable: data width, register count, read-port count. The synthesis tool produces a different hardware block for each instantiation — different flop counts, different mux structures, different read-port replications. Same source.

10. The defparam Override (Forbidden)

Verilog-1995 introduced a third override mechanism — defparam — that allows changing a parameter's value from outside the instantiation:

defparam-DEPRECATED.v — do NOT use in new code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module top;
    sub u_sub (...);
 
    // defparam override — DEPRECATED
    defparam u_sub.WIDTH = 32;
endmodule
 
module sub;
    parameter WIDTH = 8;
    // ...
endmodule

The defparam statement reaches into u_sub's scope and overrides its WIDTH parameter to 32. The override works in pure Verilog-2001, but every modern synthesis flow and most style guides forbid it for three reasons:

  1. Action-at-a-distance. The override happens in top, but the affected parameter is in sub. Reading sub's code doesn't reveal the override.
  2. Hierarchical naming requirement. The path u_sub.WIDTH binds to the specific instance name. Renaming the instance in top silently breaks the override.
  3. Deprecated in SystemVerilog. SystemVerilog removed defparam; most synthesis tools reject it; the construct is forbidden in any forward-looking codebase.

The replacement is the instantiation-time override from §4.1:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sub #(.WIDTH(32)) u_sub (...);     // clear, scope-correct, modern

If you encounter defparam in an existing codebase, the right action is to rewrite the affected instantiations to use the inline override syntax. The 6 (constant-variables) overview's §9 covers this in detail.

11. Simulation and Synthesis Behavior

Parameters are resolved at elaboration time — before simulation starts and before synthesis maps to gates. The elaborator:

  1. Walks the module hierarchy from top to bottom.
  2. For each instantiation, applies the override values to the instantiated module's parameters.
  3. Substitutes the resolved values into every expression in the instantiated module.
  4. The result is a "flattened" hierarchy where every module instance has concrete parameter values.

By the time simulation starts, parameters appear as literal values in the compiled simulation code. The simulator's event queue never schedules an update on a parameter — they are constants from the simulator's perspective. The same is true at synthesis time — the synthesis tool sees the concrete values and produces gates accordingly.

12. Common Mistakes

Six pitfalls that catch engineers using parameter.

12.1 Using ordered override

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fifo #(32, 64, 2) u_fifo (...);    // BAD — positional, fragile

Adding a new parameter to fifo (e.g., between WIDTH and DEPTH) shifts the override positions silently — u_fifo now sets the wrong parameters. Use named overrides: fifo #(.WIDTH(32), .DEPTH(64), .MODE(2)) u_fifo (...).

12.2 Forgetting to override

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fifo u_fifo (...);                  // uses ALL defaults

Legal but probably wrong — the FIFO instantiates with default WIDTH = 8 and DEPTH = 16 regardless of what you wanted. The compile-time check is "the parameters have defaults"; there's no check that you intended those defaults. Lint may warn for important parameters.

12.3 Mismatched parameter types

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
parameter [7:0] BYTE = 8'hAA;
// ... in the instantiating module ...
fifo #(.BYTE(1024)) u_fifo (...);   // 1024 doesn't fit in 8 bits

The synthesis tool typically truncates 1024 (which is 11-bit) to 8 bits, producing BYTE = 8'h00 (or similar). Lint warns; some tools error out. Make sure the override value fits the parameter's declared type.

12.4 Using parameter for internal magic numbers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module fifo #(parameter BURST_MAX = 16) (...);   // BURST_MAX is internal, should be localparam

A constant like BURST_MAX that the user has no business changing should be localparam. Exposing it as parameter invites bad overrides. The 6.2 sub-page covers localparam in depth.

12.5 Hierarchical parameters not propagating

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module top #(parameter WIDTH = 32);
    sub u_sub (...);                // BUG: WIDTH not passed
endmodule
 
module sub #(parameter WIDTH = 8);
    // uses default WIDTH = 8 — top's WIDTH is ignored
endmodule

The top module's WIDTH = 32 doesn't automatically propagate to sub. The instantiation must pass it explicitly: sub #(.WIDTH(WIDTH)) u_sub (...). This is the most common parameter bug in multi-level designs.

12.6 Using defparam

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
defparam u_sub.WIDTH = 32;          // FORBIDDEN

See §10. Use inline override #(.WIDTH(32)) instead.

13. Industry Use Cases

Three patterns where parameter is the right tool.

13.1 Width-configurable IP blocks

Every commercial IP (Synopsys DesignWare, Cadence IP, ARM cores, Xilinx Vivado IP) is parameterised for width. The user picks 8/16/32/64-bit and the IP produces the right gates.

13.2 Mode-dependent designs

A module that can run in multiple modes (e.g., a UART that supports 5/6/7/8-bit data, 1/2 stop bits, parity on/off) uses parameters for each mode flag. The generate block expands the right hardware for each mode.

13.3 Customer-specific configurations

A chip company sells the same RTL to multiple customers. Each customer has slightly different requirements (one wants a 32-bit data bus, another wants 64). Parameters allow one source codebase with per-customer configurations.

14. Debugging Lab

Three parameter debug post-mortems

15. Coding Guidelines

  1. Use port-list parameter declaration (Verilog-2001 style). module name #(parameter X = 8) (...); is the modern convention.
  2. Always provide default values. Even if every instantiation overrides them, the default documents the module's intended use.
  3. Use named overrides exclusively. #(.NAME(value)) is robust; #(value) is fragile.
  4. ALL_CAPS for parameter names. Convention: WIDTH, DEPTH, MODE, RESET_VAL.
  5. Use typed parameters for bit-vector constants. parameter [7:0] DEFAULT_VAL = 8'hAA; makes the width explicit.
  6. Pass parameters explicitly through every hierarchy level. Top-module parameters don't automatically propagate.
  7. Never use defparam. Deprecated, action-at-a-distance, rejected by SystemVerilog.
  8. Use localparam for derived values, parameter for user knobs. The 6.2 sub-page covers this in depth.

16. Interview Q&A

17. Exercises

Three exercises that turn parameter usage into reflex.

Exercise 1 — Convert to parameterised

Convert the following module to support arbitrary widths via a WIDTH parameter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module saturating_counter (
    input  wire        clk, rst_n, en,
    output reg  [7:0]  count_q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)                count_q <= 8'h00;
        else if (en && count_q < 8'hFF) count_q <= count_q + 8'h01;
    end
endmodule

Hints. Default WIDTH = 8. Use {WIDTH{1'b1}} for "all ones." The < 8'hFF comparison becomes < {WIDTH{1'b1}}.

Exercise 2 — Hierarchical override

Given the following hierarchy, write the instantiation in top that produces a 64-bit data path throughout.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module top (...);
    middle u_mid (...);
endmodule
 
module middle #(parameter MW = 16) (...);
    bottom #(.BW(MW)) u_btm (...);
endmodule
 
module bottom #(parameter BW = 8) (
    output wire [BW-1:0] dout
);
    // ... 
endmodule

Hints. Add a parameter to top and pass it down through middle. The override must explicitly happen at each instantiation.

Exercise 3 — Spot the bugs

The following module has three parameter-related bugs. Identify and fix each.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module buggy (
    input  wire             clk,
    output wire [WIDTH-1:0] data_out
);
    parameter WIDTH = 32, DEPTH = 16;            // bug 1: in-body declaration order
 
    sub_module u_sub (.data_out(data_out));       // bug 2: missing override
 
    localparam ADDR_W = 4;                         // bug 3: derived value not actually derived
endmodule
 
module sub_module (
    output reg [SUB_WIDTH-1:0] data_out
);
    parameter SUB_WIDTH = 8;
endmodule

What to produce. (a) Identify each bug. (b) Show the fix. (c) Explain why the broken version compiled but produced wrong results.

18. Summary

The parameter keyword declares a user-overridable compile-time constant — the foundation of parameterised RTL design. Every reusable IP block uses parameters for width, depth, mode, encoding, and any other knob the user might want to configure at instantiation time.

The declaration:

  • Port-list (Verilog-2001, preferred)module foo #(parameter WIDTH = 8) (...);
  • In-body (Verilog-1995, legacy)module foo (...); parameter WIDTH = 8; ...
  • Typedparameter [7:0] BYTE_VAL = 8'hAA; for explicit width.

The override:

  • Named (preferred)module_name #(.PARAM(value))
  • Ordered (legacy, fragile)module_name #(value1, value2)
  • defparam (FORBIDDEN) — deprecated, never use.

The patterns:

  • Width / depth parameterisation — one source produces N hardware variants.
  • Mode-dependent generategenerate block expands different hardware per parameter value.
  • Hierarchical passing — propagate parameters through every level of the design.

The day-to-day discipline:

  • Always use named overrides at every instantiation.
  • Always provide default values for parameters.
  • ALL_CAPS for parameter names.
  • Typed parameters for bit-vector constants.
  • Pass parameters explicitly through every hierarchy level — they don't propagate automatically.
  • Never use defparam.
  • Use localparam for derived values; parameter for user knobs.

The next sub-page is 6.2 localparam — the strictly-internal compile-time constant that complements parameter. After 6.2, Chapter 6 closes and Chapter 7 picks up with compiler directives (the `-prefixed constructs).

  • Constant Variables — Chapter 6; the parent overview that introduces both parameter and localparam.
  • Scalar vs Vector vs Arrays — Chapter 5.2.4; the dimensions that parameter-derived widths configure.
  • reg — Chapter 5.2.1; the runtime variable counterpart.
  • RTL Designing — Chapter 3; the broader RTL framing that motivates parameterised reuse.
  • Number Representation — Chapter 4.4; literal syntax for parameter values.