Part 2 · OOP for Verification · Intermediate
Constructors & super.new()
Constructor arguments and defaults, chaining to the parent, missing super.new() failures, and construction order.
The constructor contract
Every class has exactly one constructor: a function named new. You cannot overload it, but you can give arguments default values, which covers most overloading use cases. If you write no constructor, the compiler supplies an implicit new() with no arguments. The constructor's job is to put the object into a valid initial state: store identity (a name), build sub-objects the class owns, size arrays, and capture references passed in (like a virtual interface).
class driver;
string name;
virtual bus_if vif;
int unsigned timeout_cycles;
// defaults make most arguments optional at the call site
function new(string name = "driver",
virtual bus_if vif = null,
int unsigned timeout_cycles = 1000);
this.name = name;
this.vif = vif;
this.timeout_cycles = timeout_cycles;
endfunction
endclass
driver d0 = new("d0", top_vif); // timeout takes default
driver d1 = new(.name("d1"), .vif(top_vif), .timeout_cycles(50));Constructor chaining with super.new()
When class B extends class A, constructing a B object must also initialize the A part of it. The derived constructor therefore calls super.new(...) as its first statement . If you omit the call, the compiler inserts an implicit super.new() with no arguments . That implicit call works only if the parent constructor can be called with no arguments — i.e. it has none, or all of them have defaults.
What happens when you forget super.new() with args
If the parent constructor has a required argument and the child omits super.new(arg), the implicit zero-argument call cannot satisfy it and you get a compile-time error (most tools report 'too few arguments to new' or 'super.new call required'). Subtler failure: the parent has defaults, the child forgets to forward real values, and the parent silently initializes from defaults — the object 'works' but carries the wrong name, null vif, or wrong config. This silent variant is the one that costs debug days.
class base_txn;
string name;
function new(string name); // REQUIRED argument — no default
this.name = name;
endfunction
endclass
class axi_txn extends base_txn;
rand bit [3:0] qos;
// COMPILE ERROR if omitted: implicit super.new() supplies no args
function new(string name = "axi_txn");
super.new(name); // must be the first statement
qos = 0;
endfunction
endclass
// Silent variant: parent arg HAS a default
class base2;
string name;
function new(string name = "UNNAMED");
this.name = name;
endfunction
endclass
class child2 extends base2;
function new(string name = "child2");
// forgot super.new(name) → implicit super.new() runs
// object is built, but base2.name == "UNNAMED" ← silent bug
endfunction
endclassConstruction order — base first, derived second
Construction always runs base-to-derived: the parent's property initializers and constructor body complete before the child's constructor body runs. This guarantees that by the time child code executes, every inherited property is already in a valid state. The full sequence for axi_txn t = new("t0"); is shown below.
CONSTRUCTION ORDER: axi_txn t = new("t0");
1. allocate ONE object big enough for base_txn + axi_txn parts
┌───────────────────────────┐
│ base part : name = "" │
│ axi part : qos = x │
└───────────────────────────┘
2. axi_txn::new body starts
│
├─ 2a. super.new("t0") ──► base_txn::new body runs
│ name = "t0" (base FULLY initialized first)
│
└─ 2b. rest of axi_txn::new body
qos = 0;
3. reference returned ──► t
Rule: base completes before derived code runs.
Calling virtual methods from a constructor is therefore risky —
the derived part may not be initialized yet (see Inheritance traps).Interview angle
'Can constructors be virtual?' — no; new() is statically bound. UVM works around this with the factory (create()).
'What happens if I forget super.new()?' — implicit no-arg call; compile error if the parent needs args, silent defaults if it doesn't.
'In what order do constructors run in a 3-level hierarchy?' — grandparent, parent, child — always base first.
Key takeaways
One constructor per class, named new(); argument defaults replace overloading.
super.new() must be the first statement in a derived constructor; the compiler inserts a no-arg call if you omit it.
Required parent args + missing super.new() = compile error; defaulted parent args = silent wrong initialization.
Construction runs base-to-derived — inherited state is valid before child constructor code executes.
Common pitfalls
Forgetting to forward the name/parent args to super.new() — components report under wrong names, configs miss.
Doing heavy work (file I/O, randomize) in constructors — runs before the test can configure anything.
Calling a virtual method inside new() — dispatches to the derived override before the derived part is initialized.
Assuming new() can be overloaded — it cannot; use default arguments or static factory functions instead.