Data Declarations & Constants
What data is in SystemVerilog, the declaration syntax, and the three kinds of constant — localparam, specparam, and the new const keyword — with worked examples covering every rule and edge case.
📈 Kinds of Data in SystemVerilog
SystemVerilog defines six categories of named data. Understanding the category determines what you can assign to it, when its storage is allocated, and how long it persists.
8'hFF, 3.14, "hello", 10nsparameter WIDTH = 8;localparam, specparam, const — the focus of this articleint, logic, byte, class handles — next articlewire, tri, etc. — next article(* synthesis, keep *) — covered in the Attributes articlereg) and nets could only be driven by continuous assignment (wire). SystemVerilog blurs this line: a logic variable can now be driven by either a single continuous assignment or one or more procedural statements. The keyword logic replaces reg in most RTL code because it accurately describes the intent without the confusing “register” connotation.
📋 Declaration Syntax
A data declaration in SystemVerilog has an optional const qualifier, an optional lifetime qualifier, followed by the type and variable name. This is the full grammar for any data declaration:
[ const ] [ lifetime ] variable_declaration
| type_declaration
| package_import_declaration
| virtual_interface_declaration
lifetime ::= static | automatic
The three optional qualifiers and what they do
| Qualifier | What it does | Default when omitted |
|---|---|---|
| const | The variable is read-only after initialisation. Writing to it is a compile error. | Mutable (can be written) |
| static | Storage allocated once at elaboration, lives for the entire simulation. All instances share one copy (for tasks/functions). | Default everywhere except inside automatic tasks/functions |
| automatic | Storage allocated on each call/activation, freed on return. Each call gets its own copy. Required for recursion. | Default inside automatic tasks, functions, programs, and class methods |
// Examples of data declarations using each qualifier const logic flag = 1; // const, default lifetime (static) static int counter = 0; // explicit static automatic int temp = 0; // automatic: new copy per call const automatic logic once = 1'b1; // const + automatic (set at entry, read-only) // Type declaration (typedef) — also a data_declaration typedef logic[7:0] byte_t; // Package import declaration import my_pkg::*; // Note: automatic cannot appear outside a procedural context // module m; automatic int x; endmodule // ILLEGAL
wire is created — the same rule as Verilog-2001.
🔒 Three Kinds of Constant
SystemVerilog has three different constant mechanisms. They look similar but differ in when they are resolved, where they can be used, and what expressions are legal on the right-hand side.
localparam
- Resolved at elaboration time
- Cannot be overridden from outside the module
- RHS: literals, other params, constant functions
- No hierarchical names on RHS
- Usable anywhere in the module
specparam
- Resolved at elaboration time
- Designed for specify blocks (timing)
- RHS: literals, other specparams
- Not usable outside specify/specparam context
- Unchanged from Verilog-2001
const
- Resolved at simulation time
- New in SystemVerilog
- Static: same as localparam (elaboration)
- Automatic: evaluated on each entry to scope
- Allows hierarchical names on RHS
🟢 localparam
A localparam is an elaboration-time constant that cannot be overridden from outside the module, unlike a plain parameter. It is typically used for derived constants that should not be changed by the instantiating parent.
Basic declaration
// Simple localparam localparam byte COLON = ":"; // byte constant: ASCII 58 localparam int MAX_LEN = 256; localparam logic ACTIVE_H = 1'b1; // Derived localparam — computed from parameters module fifo #( parameter int DEPTH = 16, parameter int WIDTH = 8 ); localparam int ADDR_W = $clog2(DEPTH); // derived — cannot be overridden localparam int WORDS = DEPTH * WIDTH; // also derived logic [ADDR_W-1:0] rd_ptr, wr_ptr; endmodule
Legal right-hand side expressions
parameter int N = 4; localparam int A = 8; // OK — literal localparam int B = N * 2; // OK — parameter expression localparam int C = A + B; // OK — other localparams localparam int D = $clog2(N); // OK — constant function of params // genvar in generate context generate genvar g; for(g=0; g<N; g++) begin localparam int IDX = g; // OK — genvar end endgenerate // ILLEGAL right-hand sides for localparam: // localparam int E = some_signal; // ERROR — runtime value // localparam int F = top.sub.param; // ERROR — hierarchical name
parameter can be overridden at instantiation: fifo #(.DEPTH(32)) u_fifo(...);. A localparam cannot — it is always computed from the module’s own parameters. This is important for derived values like ADDR_W = $clog2(DEPTH): if someone could override ADDR_W independently of DEPTH, the derived relationship would be broken. Use localparam for any constant derived from parameters.
localparam in packages
// In a package, localparams are accessible via scope resolution package bus_pkg; localparam int DATA_W = 32; localparam int ADDR_W = 28; localparam int BYTES = DATA_W / 8; endpackage module top; import bus_pkg::*; // wildcard import logic [DATA_W-1:0] data; // 32-bit vector // Or access explicitly without import: logic [bus_pkg::ADDR_W-1:0] addr; endmodule
🟠 specparam
A specparam is a constant designed specifically for use in specify blocks — the Verilog timing specification mechanism. SystemVerilog does not change specparam behaviour from Verilog-2001.
module my_gate ( input logic a, b, output logic y ); specparam int tRise = 10; // rise time in time units specparam int tFall = 8; // fall time specparam int tHold = tRise + 2; // derived from another specparam — OK assign y = a & b; specify (a => y) = (tRise, tFall); // use specparams in timing paths (b => y) = (tRise, tFall); endspecify endmodule
specparam can only appear in the body of a module (not in a package, interface, or class). Its right-hand side may only include literals and other specparam values — not regular parameter or localparam values. It cannot be used as an index, array size, or port connection width — those require a proper parameter or localparam. In practice, specparam is almost exclusively used in gate-level netlists generated by synthesis tools.
✅ const — The New SystemVerilog Keyword
The const keyword declares a variable that is initialised once and then read-only. Unlike localparam, a const is calculated at simulation time rather than elaboration time — which means its initialiser can reference runtime values (in an automatic context) and hierarchical names (in a static context).
Static const — resolved after elaboration
// Static const: like localparam but can reference hierarchical names const logic flag = 1; // literal — same as localparam const logic option = a.b.c; // OK — hierarchical name allowed const int MAX = top.u1.WIDTH; // OK — hierarchical param const byte COLON = ":"; // typed const from a string literal // Writing to a const after declaration is a compile error: // flag = 0; // ERROR — const cannot be written
Automatic const — evaluated on every scope entry
task automatic compute(int data); // This const is evaluated from data on each call to compute() // It acts like a read-only alias for the expression const int DOUBLED = data * 2; // legal — automatic context $display("DOUBLED = %0d", DOUBLED); // DOUBLED = data; // ERROR — cannot assign to const endtask compute(5); // prints DOUBLED = 10 compute(3); // prints DOUBLED = 6 (new const value on new call)
localparam must be calculated entirely from other parameters, literals, and constant functions before simulation starts. A const is calculated after elaboration is complete, so it can reference hierarchical names (like another module’s parameter via top.sub.WIDTH) and simulation-computed values. The tradeoff is that tools cannot use a const to derive elaboration-time information (like array sizes); for that, you still need localparam.
const in different scope positions
module msl; // Module scope — static lifetime (even without 'static' keyword) const int MOD_CONST = 42; // static, elaboration-time value initial begin // Unnamed block — const here is static by default const int BLK_C = 100; // static automatic int auto1 = 0; // automatic (explicitly) end task automatic t1(); // Inside automatic task — variables are automatic by default int auto2; // automatic (task default) static int st3; // explicitly static const int auto_c = auto2+1; // automatic const (evaluated at call) endtask endmodule
What the const keyword prevents
const int LIMIT = 100; // Reading: always legal int x = LIMIT; // OK if(x > LIMIT) ... // OK logic[7:0] arr[LIMIT]; // OK — as array size if localparam-compatible // Writing: compile error // LIMIT = 200; // ERROR // LIMIT++; // ERROR // LIMIT += 5; // ERROR // force LIMIT = 50; // ERROR — force also illegal on const
📚 const with Class Objects
A class object handle can be declared const. This makes the handle constant — you cannot point it to a different object — but the members of the object can still be written (unless those members are themselves declared const).
class Config; int width = 8; const int max_v = 255; // const member — cannot be written string name = "default"; endclass // const class_name object = new(args); // Arguments to new() must be constant expressions. const Config cfg = new(); // Legal: reading the const handle $display(cfg.width); // OK // Legal: modifying non-const MEMBERS of the object cfg.width = 16; // OK — width member is not const cfg.name = "custom"; // OK // Illegal: writing to const MEMBERS // cfg.max_v = 100; // ERROR — max_v is const // Illegal: reassigning the handle itself // cfg = new(); // ERROR — cfg handle is const // cfg = null; // ERROR
const declaration must be calculable at a known point (elaboration for static, scope-entry for automatic), the arguments passed to new() must themselves be constant expressions — literals, localparam values, or other const values. You cannot pass a runtime variable to new() of a const object declaration.
🔄 Choosing the Right Constant
Here is a practical decision guide for choosing between the three constant kinds:
Use localparam when…
- The value is computed from module parameters
- It will be used as an array size, port width, or loop bound (elaboration-time needed)
- You want synthesis tools to see it as a parameter value
- Classic examples:
ADDR_W = $clog2(DEPTH),BYTES = WIDTH/8
Use const when…
- The value references a hierarchical name
- You need a simulation-time computed value that should not change after that point
- Inside an automatic task/function, where it should be a read-only alias for an argument expression
- Declaring a class object that should not be re-pointed
| Question | localparam | specparam | const |
|---|---|---|---|
| Computed at elaboration time? | ✅ | ✅ | Static: ✅ Auto: ❌ |
| Hierarchical name on RHS legal? | ❌ | ❌ | ✅ |
| Runtime expression on RHS legal? | ❌ | ❌ | Automatic only |
| Can be used as array size/port width? | ✅ | ❌ | Only if static |
| Can be used as a class object? | ❌ | ❌ | ✅ |
| Overridable at instantiation? | ❌ | ❌ | ❌ |
| Where can it appear? | Module, interface, package | Module only | Anywhere data is legal |
📋 Quick Reference
Constant declaration syntax cheatsheet
// localparam — elaboration constant, no override localparam int WIDTH = 8; localparam int ADDR_W = $clog2(256); localparam logic ACTIVE = 1'b1; // specparam — for timing specify blocks only specparam int tRise = 10; specparam int tFall = 8; specparam int tHold = tRise + 2; // const — simulation-time read-only variable const logic FLAG = 1; // static const (no keyword needed) const logic OPT = a.b.c; // hierarchical name allowed const MyClass obj = new(); // const class object handle // const in automatic context task automatic f(int x); const int TWICE = x * 2; // auto const: evaluated at call endtask
Rules at a glance
- A
localparamis an unoverridableparameter— use it for values derived from parameters. - A
specparamis only for timing specify blocks — don’t use it elsewhere. - A static
constis calculated after elaboration and can see hierarchical names. - An automatic
constis evaluated every time its enclosing scope is entered — useful for read-only aliases inside tasks and functions. - A
constclass object means the handle is fixed, not the object’s members. - Writing to any
const, includingforce, is a compile error.
logic and wire, static vs automatic lifetime rules, signal aliasing, and type compatibility.
