Part 2 · OOP for Verification · Intermediate
extends & super
Derived classes, method overriding, super.method(), and the base bus_txn → axi_txn pattern.
What extends gives you
Writing class axi_txn extends bus_txn; makes axi_txn a specialized version of bus_txn: every property, method, and constraint of the base is part of the derived class automatically. The derived class then adds new members and overrides methods whose behavior must change. In memory, a derived object is one contiguous object containing the base part plus the derived extensions — not two linked objects. SystemVerilog supports single inheritance only: one parent per class (interface classes, covered later, recover multiple contracts).
ONE OBJECT, LAYERED LAYOUT
axi_txn t = new();
┌──────────────────────────┐
│ bus_txn part │ inherited: addr, data, write,
│ addr / data / write │ print(), compare(), c_align
├──────────────────────────┤
│ axi_txn part │ added: id, burst, qos
│ id / burst / qos │ overridden: print()
└──────────────────────────┘
Single inheritance chain: axi_txn ─extends─► bus_txnBase bus_txn → derived axi_txn
class bus_txn;
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit write;
constraint c_align { addr[1:0] == 2'b00; }
function new(string name = "bus_txn");
endfunction
virtual function void print();
$display("BUS %s addr=%0h data=%0h",
write ? "WR" : "RD", addr, data);
endfunction
virtual function bit compare(bus_txn rhs);
return (addr == rhs.addr) && (data == rhs.data)
&& (write == rhs.write);
endfunction
endclass
class axi_txn extends bus_txn;
rand bit [3:0] id;
rand bit [1:0] burst; // FIXED / INCR / WRAP
rand bit [3:0] qos;
constraint c_burst { burst inside {[0:2]}; } // ADDS to c_align
function new(string name = "axi_txn");
super.new(name);
endfunction
// OVERRIDE: extend, don't replace — reuse base via super
virtual function void print();
super.print(); // base fields first
$display(" AXI id=%0d burst=%0d qos=%0d", id, burst, qos);
endfunction
virtual function bit compare(bus_txn rhs);
axi_txn arhs;
if (!super.compare(rhs)) return 0; // base fields must match
if (!$cast(arhs, rhs)) return 0; // rhs not an axi_txn
return (id == arhs.id) && (burst == arhs.burst);
endfunction
endclassWhat is inherited and what combines
Properties and methods — all inherited; derived code uses them as its own.
Constraints — inherited AND combined: axi_txn::randomize() solves c_align and c_burst together. A same-named constraint in the derived class replaces the base one.
Constructors — NOT inherited; each class defines new() and chains with super.new() (see Constructors lesson).
super — extending instead of replacing
super.method() calls the immediate parent's version of a method from inside an override. The dominant testbench idiom is extend-then-add : call super first so base behavior (printing base fields, comparing base fields, base initialization) always runs, then layer the derived additions. Overrides that forget super silently drop base behavior — a derived compare() that skips super.compare(rhs) stops checking addr/data entirely, and the scoreboard goes blind to base-field mismatches while appearing to work.
Override signature rules
An override must match the base signature (name, arguments, return type) — for virtual methods the LRM requires it.
Matching signatures keep base-handle calls valid: callers compiled against the base prototype work on any derived object.
You cannot reduce visibility in an override (a base-visible method must stay callable through the base).
Interview angle
'What does a derived class inherit?' — everything except constructors; constraints merge, same-named constraints override.
'Why call super.print() in an override?' — extend-not-replace; otherwise base fields vanish from logs and compares.
'How many parents can a class have?' — one (single inheritance); interface classes provide multiple contracts.
Key takeaways
extends builds one object with a base layer plus derived extensions — single inheritance only.
Constraints from base and derived are solved together; same-named constraints override.
super.method() reaches the parent's version — the extend-then-add idiom keeps base behavior alive in overrides.
Overrides must match the base signature so base-handle callers stay valid.
Common pitfalls
Overriding compare()/print() without calling super — base fields silently dropped from checks and logs.
Reusing a base constraint's name unintentionally — replaces the base constraint instead of adding to it.
Expecting constructors to be inherited — every derived class must define new() and chain super.new().
Changing argument lists in an override — breaks polymorphic calls through base handles.