A virtual class cannot be constructed directly, but handles of its type work normally. Use one when the base exists only to define a shape, such as a generic stimulus item.
A pure virtual method declares a prototype with no body. Every concrete subclass must implement it, which turns a missing method from a runtime mystery into a compile error.
Kind | Carries | Adopted by | Gives |
|---|---|---|---|
virtual class | Data plus pure and concrete methods. | One extends parent. | Shared implementation with mandatory hooks. |
interface class | Pure virtual prototypes only. | Many implements contracts. | Multiple role promises, no state. |
A class extends exactly one parent but may implement any number of interface classes. That combination recovers multiple inheritance of contract without data-layout ambiguity.
Code environments against the contract, not the concrete class. Tests then inject any driver that satisfies it, which is the hand-rolled ancestor of factory overrides.
mon extends stim_item and implements resettable. A resettable handle r points at a mon whose seen is 3. After r.do_reset(), what does m.check() return?0. do_reset() cleared seen in the same object, and mon's check() returns 1 only when seen is above 0.
Abstract handles with interface contracts, proven through a resettable monitor:
virtual class stim_item;
string item_name;
function new(string name_ = "stim_item");
item_name = name_;
endfunction
pure virtual function bit check();
virtual function void announce();
$display("[%s] sending", item_name);
endfunction
endclass
class eth_frame extends stim_item;
bit [47:0] dst;
function new();
super.new("eth_frame");
endfunction
virtual function bit check();
return dst != 48'h0;
endfunction
endclass
interface class reportable;
pure virtual function string report();
endclass
interface class resettable;
pure virtual task do_reset();
endclass
class mon extends stim_item implements reportable, resettable;
int unsigned seen = 0;
function new();
super.new("mon");
endfunction
virtual function bit check();
return seen > 0;
endfunction
virtual function string report();
return $sformatf("mon: %0d seen", seen);
endfunction
virtual task do_reset();
seen = 0;
endtask
endclass
[mon] sending
PASS: abstract handle dispatches check, reset contract clears stateA queue of base handles holds mixed derived objects, since each element is a reference and every derived object is its base. Consumers should call virtual methods and stay type-blind.
Reach for $cast only for genuinely type-specific side paths, such as sampling AXI-only fields into plain variables. The factory mirror builds concrete types behind a base-handle return.
The factory builds only the requested type. It is declared automatic, so every call constructs a fresh object.
A mixed queue with virtual checks plus a factory that returns base handles, proven over four objects.
class bus_txn;
bit [31:0] addr;
virtual function bit check();
return addr[1:0] == 2'b00;
endfunction
virtual function void print();
$display("BUS addr=%0h", addr);
endfunction
endclass
class axi_txn extends bus_txn;
bit [3:0] qos;
virtual function bit check();
if (!super.check()) return 0;
return qos != 0;
endfunction
virtual function void print();
$display("AXI addr=%0h qos=%0d", addr, qos);
endfunction
endclass
class err_txn extends bus_txn;
virtual function bit check();
return 0;
endfunction
virtual function void print();
$display("ERR addr=%0h", addr);
endfunction
endclass
typedef enum {PLAIN, AXI, ERR} txn_kind_e;
function automatic bus_txn make_txn(txn_kind_e kind);
case (kind) // build only the requested type
AXI: begin axi_txn ax = new(); return ax; end
ERR: begin err_txn er = new(); return er; end
default: begin bus_txn plain = new(); return plain; end
endcase
endfunction
BUS addr=1000
AXI addr=2000 qos=3
ERR addr=0
AXI addr=0 qos=0
PASS: mixed queue dispatches check per type and factory made 4 kindsOne extends plus two implements. The single parent keeps identity while each interface adds a checked role contract.
Move the behavior into a virtual method. Consumers stay type-blind and new transaction flavors need no consumer changes.
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.
A factory function that is automatic builds a new object on every call; in a static function its initialisers would run once (IEEE 1800 6.21) and hand every caller the same object.