Parameters
Everything SV adds to Verilog-2001 parameters: typed value parameters, type parameters (parameter type), omitting the parameter keyword in port lists, localparam in generate/package/compilation-unit scope, the $ unbounded token, $isunbounded(), and dependent parameter chains — with real-world parameterised checker and class examples.
📋 What Verilog-2001 Provided
Verilog-2001 provided three compile-time constant constructs and four methods for setting them:
parameter— overridable per-instance constant with a default value.localparam— read-only constant; cannot be overridden at instantiation.specparam— timing constants insidespecifyblocks.
Override methods: implicit positional (#(val1, val2)), explicit named (#(.p1(v1))), or defparam statements. Verilog parameters had no data type — they were size and sign inferred from their assigned value. SV adds explicit types, type parameters, and a cleaner set of scopes for localparam.
defparam statement may be removed from a future version of SV. Use explicit named overrides (#(.name(value))) instead. defparam was problematic because it could silently override a parameter across compilation boundaries, making designs harder to verify and port.
💡 What SV Adds
int, bit[N:0], real, struct, enum, etc. Type-checks overrides at elaboration.parameter type T = int — the parameter is a data type. Allows modules and classes to be generic over types, similar to C++ templates.localparam now valid inside generate blocks, packages, and the compilation-unit scope. parameter acts as a synonym for localparam in those contexts.📋 Typed Value Parameters
Adding an explicit data type to a parameter declaration constrains what values can be used to override it. The type is checked at elaboration time — a type mismatch produces an error rather than a silent truncation or sign change.
// Verilog-2001: no type — p1 infers type from its value module old #(parameter p1 = 8) (...); endmodule // SV: explicit types — p1 is int, p2 is shortint (type parameter) module ma #(parameter int p1 = 1, parameter type p2 = shortint) (input logic [p1:0] i, output logic [p1:0] o); p2 j = 0; // j has type shortint unless p2 is overridden always @(i) begin o = i; j++; end endmodule // Override at instantiation — p2 becomes int instead of shortint module mb; logic [3:0] i, o; ma #(.p1(3), .p2(int)) u1(i, o); // In u1: p1=3 → ports are [3:0]; p2=int → j is int endmodule // Other typed parameter examples parameter bit [7:0] MASK = 8'hFF; parameter real FREQ = 100.0; parameter string TAG = "default"; parameter int unsigned DEPTH = 64;
logic of arbitrary size (for backward compatibility). An integer parameter declared without a type can silently expand to more bits than intended when overridden. Declaring parameter int DEPTH = 64 ensures the override is always checked against the int type.
📋 No-Type Default Behaviour
For compatibility with Verilog-2001, if no data type is given, a parameter defaults to logic of arbitrary size. The actual width and signing are inferred from the assigned value — either the default or the override.
// No type: inferred as logic, width from default value 8 → logic[3:0] parameter N = 8; // Override with 16 → now treated as 4-bit logic containing 4'b10000? No — // the override VALUE determines the width at that instance. // This is exactly why typing parameters prevents surprises. // In a parameter port list, the 'parameter' keyword on the first entry // sets the type for subsequent entries that only have the data type: #(parameter int N = 5, M = N*16) // N is int, M is also int (inherits type from previous)
📋 Type Parameters — parameter type
A type parameter is a parameter whose value is a data type. This makes modules, interfaces, and classes generic over types — like C++ templates. The actual type is substituted at elaboration, and all variables and ports using that type get the substituted type.
// module ma: p2 is a type parameter, default = shortint // j takes the type of p2 at every instance module ma #(parameter int p1 = 1, parameter type p2 = shortint) (...); p2 j = 0; // shortint by default endmodule // Override p2 to int at instantiation ma #(.p2(int)) u1(...); // j is int in u1 ma #(.p2(real)) u2(...); // j is real in u2 ma #(.p2(my_struct_t)) u3(...); // j is my_struct_t in u3 // Type parameter with a user-defined type as default typedef struct { int x; int y; } point_t; module geom #(parameter type T = point_t); T vertices[3]; // array of 3 T — type set per instance endmodule
📋 Uses of Type Parameters
Type parameters make design code reusable across different data widths, signedness, and even struct types — all without preprocessor macros or code duplication.
// Generic FIFO: data type configurable per instance module generic_fifo #(parameter int DEPTH = 16, parameter type T = logic[7:0]) (input T wr_data, output T rd_data, ...); T mem[0:DEPTH-1]; // memory uses the same type T endmodule // Three different instantiations with different data types generic_fifo #(.T(byte)) byte_fifo (...); generic_fifo #(.T(logic[31:0])) word_fifo (...); generic_fifo #(.T(packet_t)) pkt_fifo (...); // struct type // Type parameter used in a task prototype inside a module module processor #(parameter type OP = int); task automatic execute(input OP operand); // ... operand type changes with OP endtask endmodule
$typeof(expr) system function (Section 23) returns the type of an expression as an elaboration-time constant. This lets you use a signal’s actual type as a type parameter override: module m #(parameter type T = $typeof(some_signal)). This creates a tighter coupling between types that must match.
📋 localparam Enhancements
SV extends the places where localparam can appear and adds a rule that parameter acts as a synonym for localparam in certain scopes.
// localparam in a generate block — new in SV generate for(genvar i=0; i<4; i++) begin localparam int LANE_W = 8 * (i+1); // computed per iteration // LANE_W is different for each generated block end endgenerate // localparam (or parameter) in a package — acts as a package constant package bus_pkg; parameter int BUS_W = 32; // parameter as synonym for localparam in packages localparam MASK = 32'hFF; endpackage // localparam (or parameter) in compilation-unit scope parameter int CLK_PERIOD = 10; // outside all modules — unit-local constant
parameter and localparam are identical. Using parameter in a package is the conventional style for shared constants.
📋 Omitting the parameter Keyword in Port Lists
In a parameter port list, the first entry must use the parameter keyword. Subsequent entries can omit it — they inherit the parameter nature from context. Only the data type (or type) needs to be written.
// Full form — every parameter written explicitly module full #(parameter int N=5, parameter int M=10, parameter type T=int) (...); // Shorthand — keyword omitted after the first entry module short #(parameter int N=5, int M=10, type T=int) (...); // M and T still 'parameter'-qualified — just shorter to write // Class with shortened parameter list class vector #(size = 1); // 'parameter' omitted — inferred as parameter logic [size-1:0] v; endclass typedef vector #(16) word; // specialise to 16-bit word // Interface with type parameter and omitted keyword interface simple_bus #(AWIDTH = 64, type T = word) (input bit clk); endinterface
$ The $ Unbounded Token
$ can be assigned to integer-type parameters to mean unbounded. A parameter holding $ may only be used where $ is legal as a literal constant — primarily as the upper bound of a range.
// Parameter holding $: means "no upper bound" parameter r2 = $; // Use in a property range: r1 to r2 clock ticks // With r2=$, the range is [3:$] — unbounded upper end property inq1(r1, r2); @(posedge clk) a ##[r1:r2] b ##1 c |=> d; endproperty assert inq1(3); // r1=3 (explicit), r2=$ (default) → ##[3:$] // $ in assertion sequences: 'true ##[1:$] b → b eventually holds // $ in coverage bins: bins d = {[1000:$]} → 1000 to max value of the variable // $ in array dimensions: logic [N:0] arr[0:$] — NOT valid; $ is for ranges, not declarations
$ in a type declaration (e.g. bit [N:$] is not legal as a variable declaration). It is only valid where an unbounded range literal is allowed: in assertion sequence delay ranges (##[1:$]), in property range bounds, and in coverage bin value ranges ([low:$]).
📋 $isunbounded()
$isunbounded(const_expression) returns true if the constant expression evaluates to $. It is used in generate blocks to choose the correct property or logic based on whether a parameter is bounded or unbounded.
// $isunbounded used in a generate condition parameter int foo = $; // $isunbounded(foo) → true // $isunbounded(42) → false // Pattern: generate different logic for bounded vs unbounded parameter generate if ($isunbounded(max_val)) begin // property that assumes no upper limit end else begin // property that checks upper limit end endgenerate
▶ Parameterised Quiet-Time Checker
This interface uses $isunbounded to write a parameterised protocol checker that works for both finite and infinite quiet-time windows — without any preprocessor tricks.
interface quiet_time_checker #(parameter min_quiet = 0, parameter max_quiet = 0) (input logic clk, reset_n, [1:0] en); generate if (max_quiet == 0) begin // No quiet window at all: exactly one enable must always be active property quiet_time; @(posedge clk) reset_n |-> ($countones(en) == 1); endproperty a1: assert property (quiet_time); end else begin // Bounded or unbounded quiet window: en transitions to 0, stays quiet // for [min_quiet:max_quiet] cycles, then exactly one enable returns property quiet_time; @(posedge clk) (reset_n && ($past(en) != 0) && en == 0) |-> (en == 0)[*min_quiet:max_quiet] ##1 ($countones(en) == 1); endproperty a1: assert property (quiet_time); end // Warn if parameters are inconsistent if ((min_quiet == 0) && $isunbounded(max_quiet)) $display("warning: quiet window is unbounded"); endgenerate endinterface // Three instantiations with completely different behaviour quiet_time_checker #(0, 0) quiet_never (clk, 1, enables); // no quiet time allowed quiet_time_checker #(2, 4) quiet_in_window (clk, 1, enables); // quiet 2–4 cycles quiet_time_checker #(0, $) quiet_any (clk, 1, enables); // unbounded quiet OK
▶ Parameterised Width Checker
A second example shows how $isunbounded makes it possible to write a single interface that correctly handles both exact-width and minimum-width specifications without duplicating assertion code.
interface width_checker #(parameter min_cks = 1, parameter max_cks = 1) (input logic clk, reset_n, expr); generate begin if ($isunbounded(max_cks)) begin // Minimum-width only: expr must stay high for AT LEAST min_cks cycles property width; @(posedge clk) (reset_n && $rose(expr)) |-> (expr [* min_cks]); endproperty a2: assert property (width); end else begin // Exact-width window: expr must stay high for min_cks to max_cks cycles, // then must go low property assert_width_p; @(posedge clk) (reset_n && $rose(expr)) |-> (expr[* min_cks:max_cks]) ##1 (!expr); endproperty a2: assert property (assert_width_p); end end endgenerate endinterface width_checker #(3, $) max_width_unspecified (clk, 1, enables); // ≥3 cycles width_checker #(2, 4) width_specified (clk, 1, enables); // exactly 2–4 cycles
`ifdef to choose between two property implementations, you can instead use generate if ($isunbounded(param)). This is cleaner because it uses the actual parameter value rather than a separate compile-time define, keeping the checker self-contained.
🔁 Dependent Parameter Chains
A parameter’s default value can reference earlier parameters in the same port list. This creates a dependency chain resolved at elaboration. The chain can mix value parameters and type parameters.
// Four-parameter chain: // M depends on N; x depends on type T module mc #(int N = 5, M = N*16, // M = 80 by default (N*16) type T = int, // T defaults to int T x = 0) // x has type T (= int) and default 0 (...); endmodule // Overriding N changes M automatically (if M not also overridden) mc #(.N(8)) u1(...); // N=8, M=128, T=int, x=0 // Override T changes the type of x mc #(.T(real)) u2(...); // N=5, M=80, T=real, x=0.0 (real 0) // Override all four mc #(.N(4), .M(64), .T(bit[7:0]), .x(8'hAA)) u3(...);
📋 Parameters in Classes and Interfaces
Classes and interfaces support the same parameter syntax as modules, including type parameters and dependent chains.
// Parameterised class with a size parameter class vector #(size = 1); logic [size-1:0] v; endclass typedef vector #(16) word; // specialize to 16-bit word // Parameterised interface with type parameter T defaulting to word interface simple_bus #(AWIDTH = 64, type T = word) (input bit clk); T data; // uses type T (= word by default) logic[AWIDTH-1:0] addr; endinterface // Default instance: T=word (16-bit), AWIDTH=64 simple_bus bus_dflt(clk); // Override data to int (32-bit) simple_bus #(.T(int)) bus_wide(clk);
📋 Quick Reference
Parameter forms
| Syntax | What it declares | Notes |
|---|---|---|
| parameter p = val | Untyped value parameter | Type inferred from value (logic, arbitrary size) |
| parameter int p = val | Typed value parameter | Type-checked at override; preferred style |
| parameter type T = int | Type parameter | Override with any data type |
| localparam int K = expr | Non-overridable constant | In module, generate, package, compilation-unit |
| parameter $ | Unbounded integer parameter | Use only where $ is legal (range bounds) |
Key rules
- Modules, interfaces, programs, and classes all support parameters with the same syntax.
- In packages and compilation-unit scope,
parameteris a synonym forlocalparam— cannot be overridden. localparamis now legal inside generate blocks.- In a parameter port list, the
parameterkeyword on the first entry covers subsequent entries — can be omitted after the first. - A parameter can depend on earlier parameters in the same list.
$assigned to a parameter means unbounded; only valid as a range bound.$isunbounded(param)returns true ifparam === $; use ingenerate ifto select different logic.defparamis deprecated — use named parameter overrides instead.
