Part 2 · OOP for Verification · Intermediate
Parameterized Classes
Value and type parameters, specializations as distinct types, the static-per-specialization trap, and parameterized scoreboards.
Value and type parameters
A parameterized class is a template : one source definition that the compiler stamps out into concrete classes when you supply parameters. Parameters come in two kinds — value parameters (#(int WIDTH = 8)) that set sizes and constants, and type parameters (#(type T = int)) that let one container class hold any payload type. This is how SystemVerilog gets generic containers without dynamic typing: a fifo#(bus_txn) and a fifo#(pkt) share source code but are fully type-checked against their own payload.
class fifo #(type T = int, int DEPTH = 8);
local T q[$];
function bit try_put(T item);
if (q.size() >= DEPTH) return 0;
q.push_back(item);
return 1;
endfunction
function bit try_get(ref T item);
if (q.size() == 0) return 0;
item = q.pop_front();
return 1;
endfunction
function int level();
return q.size();
endfunction
endclass
// Specializations — supply parameters at use site
fifo #(bus_txn, 16) txn_fifo = new();
fifo #(byte) byte_fifo = new(); // DEPTH defaults to 8Specializations are distinct types
This is the semantic point most engineers learn the hard way: fifo#(byte) and fifo#(int) are unrelated types . You cannot assign a handle of one to the other, they do not share a base class (unless you give them one explicitly), and — the classic trap — each specialization gets its own copy of every static member . A static counter in a parameterized class does not count across all specializations; it counts per specialization.
STATIC-PER-SPECIALIZATION TRAP
class item #(int W = 8);
static int count = 0;
endclass
one source template
│
┌──────────┼──────────┐
▼ ▼ ▼
item#(8) item#(16) item#(32) ◄── three DISTINCT classes
│ │ │
count = 5 count = 2 count = 0 ◄── three SEPARATE statics!
item#(8)::count → 5
item#(16)::count → 2 no single "total across widths" exists
item#(8) h8; item#(16) h16;
h8 = h16; ◄── COMPILE ERROR: unrelated typestypedef your specializations
Because the full specialization name is verbose and must match exactly everywhere (including default parameters — fifo#(byte) and fifo#(byte, 8) are the same type, but it is easy to mistype one site), the standard practice is one typedef per specialization in a shared package. Every declaration then uses the typedef, so a width change is a one-line edit.
package tb_types_pkg;
typedef fifo #(bus_txn, 16) txn_fifo_t;
typedef fifo #(byte, 64) byte_fifo_t;
endpackage
// Everywhere else:
import tb_types_pkg::*;
txn_fifo_t req_q = new();
txn_fifo_t resp_q = new(); // guaranteed the same type as req_qA parameterized scoreboard
The payoff: one scoreboard implementation reused for every transaction type in the testbench. The only requirement is a contract on T — here, that it provides compare(). This is the same idea behind the built-in mailbox #(T), which is itself a parameterized class: mailbox #(bus_txn) gives compile-time type checking on put/get instead of the type-unsafe unparameterized mailbox. Interviews often ask exactly that comparison: why parameterize a mailbox? Answer: a typed mailbox rejects wrong-type puts at compile time.
class scoreboard #(type T = bus_txn);
local T expected_q[$];
local int mismatches;
function void add_expected(T t);
expected_q.push_back(t);
endfunction
function void check(T actual);
T exp;
if (expected_q.size() == 0) begin
$error("unexpected item");
mismatches++;
return;
end
exp = expected_q.pop_front();
if (!exp.compare(actual)) begin // contract: T has compare()
mismatches++;
$error("scoreboard mismatch");
end
endfunction
endclass
// Reused across protocols, each fully type-checked:
scoreboard #(bus_txn) bus_sb = new();
scoreboard #(eth_pkt) eth_sb = new();Key takeaways
Value parameters set sizes; type parameters make generic, type-checked containers.
Each specialization is a distinct, unrelated type — handles do not cross-assign.
Every specialization owns its own static members — no counter spans specializations.
typedef each specialization once in a package so all use sites agree by construction.
Common pitfalls
Expecting one static counter across all specializations — each gets its own copy.
Assigning between different specializations' handles — compile error, unrelated types.
Spelling the specialization slightly differently at two sites — two distinct types, confusing errors.
Calling a method on T that some payload type lacks — the contract on T is implicit; document it.