Part 2 · OOP for Verification · Intermediate
Abstract Classes & Interface Classes
virtual class, pure virtual methods, interface class implements, and the API-contract pattern for drivers.
virtual class — a base that cannot be instantiated
Declaring virtual class stim_item; makes the class abstract : calling new() on it is a compile error. You can declare handles of the type, extend it, and pass derived objects around through it — you just cannot build a bare instance. Use it when the base exists only to define a shape for derived classes: a generic 'stimulus item' is meaningless on its own, so making it abstract turns 'someone instantiated the placeholder base' from a runtime mystery into a compile error.
Inside a virtual class, a pure virtual method — pure virtual function bit check(); — declares a prototype with no body. Every concrete (non-abstract) subclass MUST implement it, enforced at compile time. This is how a base class makes a method mandatory rather than optional-with-default.
virtual class stim_item; // abstract — cannot new()
string name;
function new(string name = "stim_item");
this.name = name;
endfunction
pure virtual function void pack(ref bit [7:0] bytes[$]);
pure virtual function bit check();
// abstract classes CAN have concrete methods too
virtual function void announce();
$display("[%s] sending", name);
endfunction
endclass
class eth_frame extends stim_item;
rand bit [47:0] dst, src;
function new(string name = "eth_frame");
super.new(name);
endfunction
// compile error if either pure virtual is left unimplemented
virtual function void pack(ref bit [7:0] bytes[$]);
bytes = {>>{dst, src}};
endfunction
virtual function bit check();
return dst != 48'h0;
endfunction
endclass
stim_item s;
s = new(); // COMPILE ERROR — abstract
eth_frame f = new(); // OK
s = f; // OK — abstract handle, concrete objectinterface class — multiple inheritance of contract
SystemVerilog allows only one extends parent, but a class may implements any number of interface classes . An interface class is a pure contract: pure virtual method prototypes (plus typedefs/parameters), no data, no implementation. Implementing one promises 'I provide these methods', checked at compile time. This gives you multiple inheritance of contract without the data-layout ambiguities of full multiple inheritance — a component can be simultaneously 'reportable' and 'resettable' regardless of what it extends. Note the unfortunate name clash: an interface class is a class construct, unrelated to the signal-bundle interface used with DUT pins.
interface class reportable;
pure virtual function string report();
endclass
interface class resettable;
pure virtual task do_reset();
endclass
// ONE extends parent + MANY implements contracts
class axi_driver extends base_component
implements reportable, resettable;
int txn_count;
virtual function string report();
return $sformatf("axi_driver: %0d txns", txn_count);
endfunction
virtual task do_reset();
txn_count = 0;
endtask
endclass
// Generic services work on the CONTRACT, not the class tree:
task reset_all(resettable items[$]);
foreach (items[i]) items[i].do_reset();
endtaskCONTRACT vs IMPLEMENTATION INHERITANCE
virtual class (abstract) interface class
───────────────────────── ─────────────────────────
data + methods + pure protos pure virtual protos ONLY
extends — ONE parent implements — MANY allowed
shares implementation shares only a promise
base_component reportable resettable
│ │ │
└─ extends ─┐ └─implements─┤
▼ ▼
axi_driver ─────────┘
one implementation parent, two contractsThe API-contract pattern for drivers
A practical use in verification: define the driver's API as an abstract class (or interface class), and let the environment depend only on that contract. The env stores an abstract handle; tests inject any concrete driver — real protocol driver, error-injecting driver, no-op stub for bring-up — without the env changing. The abstract base makes the dependency direction explicit: the env owns the contract, concrete drivers conform to it. This is dependency inversion, and it is the hand-rolled ancestor of the UVM factory override mechanism.
virtual class driver_api;
pure virtual task start();
pure virtual task drive(bus_txn t);
pure virtual task wait_idle();
endclass
class env;
driver_api drv; // env depends ONLY on the contract
function void set_driver(driver_api d);
drv = d;
endfunction
task run(mailbox #(bus_txn) mb);
bus_txn t;
drv.start();
forever begin
mb.get(t);
drv.drive(t); // dispatches to whatever was injected
end
endtask
endclass
// test selects the concrete behavior:
// env.set_driver(real_axi_driver_inst); // normal test
// env.set_driver(error_driver_inst); // error-injection test
// env.set_driver(stub_driver_inst); // env bring-upInterview angle
'virtual class vs interface class?' — abstract class: data + partial implementation, single extends; interface class: pure contract, multiple implements.
'What does pure virtual buy you?' — compile-time enforcement that every concrete subclass implements the method; no risky empty default.
'How do you get multiple inheritance in SV?' — you don't for implementation; interface classes give multiple inheritance of contract.
Key takeaways
virtual class = abstract: cannot be instantiated, exists to be extended; handles of it are fine.
pure virtual methods make implementation mandatory in concrete subclasses, checked at compile time.
interface class + implements gives multiple contracts per class without multiple implementation parents.
Code environments against abstract contracts; inject concrete drivers per test — the manual factory pattern.
Common pitfalls
new() on a virtual class — compile error; build a concrete subclass and assign it to the abstract handle.
Empty default bodies instead of pure virtual — forgotten overrides run silent no-ops instead of failing the build.
Putting data or method bodies in an interface class — illegal; contracts are prototypes only.
Confusing interface class with the signal interface construct — same keyword, completely different feature.