A const property is write-once. A static const is a class-level constant initialized at its declaration, identical for every object with no per-object storage.
An instance const is instead assigned once in the constructor. Each object carries its own frozen value, which models attributes fixed at birth such as a port number.
// bad-example: instance const has exactly one legal assignment site
const int port_id;
function new(int port);
port_id = port; // legal: the one allowed assignment
endfunction
function void set_port(int x);
// port_id = x; // compile error: const after construction
endfunctionA kind field as raw bits accepts encodings that no protocol defines. An enum field rejects bare integers at compile time, randomizes only over named values and logs them with name().
Define the shared enum once in a package so drivers, monitors and scoreboards agree. Prefixed literals such as BUS_READ keep the package namespace clean.
Raw bits | Enum field |
|---|---|
kind = 3 compiles silently | kind = 3 is a compile error without a cast |
kind = 1 needs a comment | kind = BUS_WRITE documents itself |
Display prints 1 | kind.name() prints BUS_WRITE |
An enum field also keeps randomization honest: the solver picks only named values. Weighting them with dist is taught in dist: Weighted Distributions.
t0 is a BUS_WRITE and t1 a BUS_READ. What do t0.expected_latency() and bus_txn::latency_of(BUS_IDLE) return?2 and 1. Both read the same per-kind facts: the table maps BUS_WRITE to 2, and the function's case maps BUS_IDLE to 1.
Frozen port ids, a class-wide burst limit, directed enum values, and the per-kind latency written two ways: a static function and a static table.
package bus_pkg;
typedef enum bit [1:0] { BUS_READ, BUS_WRITE, BUS_IDLE } kind_e;
parameter int MAX_BURST = 8;
endpackage
class bus_txn;
// No import inside the class: IEEE 1800 (26.3) makes an import directly
// in a class scope illegal, so package names use bus_pkg:: instead.
const int port_id; // per-object: set once in new(), then frozen
bus_pkg::kind_e kind;
bit [31:0] addr;
function new(int port, bus_pkg::kind_e k, bit [31:0] a);
port_id = port;
kind = k;
addr = a;
endfunction
// One shared table as data: enum value to expected-latency cycles.
static const int latency_tbl[bus_pkg::kind_e] = '{
bus_pkg::BUS_READ : 4,
bus_pkg::BUS_WRITE : 2,
bus_pkg::BUS_IDLE : 1
};
function int expected_latency();
return latency_tbl[kind]; // replaces a case statement
endfunction
// The same table as code: one function instead of a case in every method.
static function int latency_of(bus_pkg::kind_e k);
case (k)
bus_pkg::BUS_READ: return 4;
bus_pkg::BUS_WRITE: return 2;
bus_pkg::BUS_IDLE: return 1;
default: return 1;
endcase
endfunction
endclass
module top;
import bus_pkg::*;
bus_txn t0;
bus_txn t1;
int errors = 0;
initial begin
t0 = new(0, BUS_WRITE, 32'h1000);
t1 = new(1, BUS_READ, 32'h2003);
if (t0.port_id !== 0) errors++;
if (t1.port_id !== 1) errors++;
// t0.port_id = 9; // COMPILE ERROR: const after construction
if (bus_pkg::MAX_BURST !== 8) errors++;
if ({1'b0, t1.addr[3:0]} >= bus_pkg::MAX_BURST) errors++; // 3 < 8: inside limit
// t0.kind = 3; // COMPILE ERROR: enum takes literals or kind_e'(3)
if (bus_txn::latency_of(t0.kind) !== 2) errors++;
if (bus_txn::latency_of(BUS_READ) !== 4) errors++;
if (bus_txn::latency_of(BUS_IDLE) !== 1) errors++;
if (t0.expected_latency() !== 2) errors++;
if (t1.expected_latency() !== 4) errors++;
if (t1.kind.name() != "BUS_READ") errors++;
if (errors == 0) $display("PASS: ports froze at 0 and 1 with latencies 2, 4 and 1 by kind");
else $fatal(1, "FAIL: frozen ports, burst limit or kind latencies differ from plan");
$finish;
end
endmodulePASS: ports froze at 0 and 1 with latencies 2, 4 and 1 by kindAssigning an instance const anywhere but the constructor is a compile error. Initializing a non-static const at its declaration freezes one value pattern for shapes that needed per-object values.
Declaring the enum inside one class forks the definition across components. SystemVerilog also has no const-qualified methods, so read-only behavior stays a team convention: query methods must not mutate state.
Instance const assigned once in new(). A static const holds one value for every object and cannot vary per port.
The enum type itself. Bare-integer assignment to an enum variable is a compile error unless written as an explicit cast.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.
Imports cannot appear directly inside a class (IEEE 1800 26.3), which is why the bench writes bus_pkg:: inside bus_txn.