SystemVerilog · Module 9
The super Keyword
Reaching the parent's constructor and overridden methods, the static-resolution rule that prevents recursion, UVM phase chaining, and the do_copy / do_compare hook discipline.
Module 9 · Page 9.9
What super Actually Refers To
super is a reference to the immediate parent class's scope. It gives you a way to reach into the parent layer and call something there — specifically the parent's constructor or the parent's version of a method you have overridden.
The key word is immediate. super always refers to the direct parent — one level up. If you are in a grandchild class, super points to the child (your immediate parent), not the original base class. To reach the base from a grandchild, the child's super.method() call triggers the base via the chain.
super.new() — The Constructor Chain
When a child class has its own constructor, it must call the parent's constructor using super.new(). This ensures the parent's properties are properly initialised before the child's constructor body runs.
The rule is strict: super.new() must be the very first statement in the child constructor body. The compiler enforces this. You cannot put anything before it.
class BaseTxn;
int txn_id;
string kind;
function new(string k = "base", int id = 0);
kind = k;
txn_id = id;
$display("BaseTxn::new() — kind=%s id=%0d", kind, txn_id);
endfunction
endclass
// ── CORRECT: super.new() is first, passes args to parent ──────
class WriteTxn extends BaseTxn;
rand bit [31:0] wdata;
function new(int id = 0);
super.new("write", id); // ← FIRST statement, passes "write" and id
wdata = 32'h0; // ← child init runs after parent is ready
endfunction
endclass
// ── WRONG: cannot put code before super.new() ─────────────────
// class BadTxn extends BaseTxn;
// function new();
// $display("hello"); ← COMPILE ERROR: must be after super.new()
// super.new("bad"); ← too late
// endfunction
// endclass
module tb;
initial begin
WriteTxn wt = new(5);
// Prints: BaseTxn::new() — kind=write id=5
// Then child body continues: wdata = 0
$display("wt.kind=%s wt.txn_id=%0d wt.wdata=0x%08h",
wt.kind, wt.txn_id, wt.wdata);
end
endmoduleWhat Happens If You Forget super.new()
If the parent class has a default (no-arg) constructor and you do not write super.new(), SystemVerilog calls the default constructor for you silently. This can mask the problem — the parent is initialised but with zero values, not the intended ones.
If the parent class has a required argument in its constructor and you forget super.new(), you get a compile error — the compiler knows the parent needs arguments that were never provided. This is actually the safer situation because you catch it immediately.
// ── Parent with DEFAULT constructor ──────────────────────────
class BaseA;
string name = "base"; // default via inline init
function new(); endfunction
endclass
class ChildA extends BaseA;
function new();
// super.new() omitted — silently calls default
// BaseA.name gets its inline default "base"
// Works but may not be what you intended
endfunction
endclass
// ── Parent with REQUIRED argument ────────────────────────────
class BaseB;
string name;
function new(string n); // argument required — no default
name = n;
endfunction
endclass
// class ChildB extends BaseB;
// function new();
// // No super.new() here
// endfunction ← COMPILE ERROR: BaseB constructor needs argument
// endclass
// ── Correct fix ───────────────────────────────────────────────
class ChildB extends BaseB;
function new(string n = "child_b");
super.new(n); // passes the argument up to BaseB
endfunction
endclasssuper.method() — Calling the Parent's Version
When a child overrides a method, it can optionally call the parent's version of that same method using super.method_name(). This lets you extend the parent's behaviour rather than completely replacing it.
The most common pattern: call super.display() at the start of the child's display() so the parent's fields print first, then the child adds its own.
class BaseTxn;
rand bit [31:0] addr;
int txn_id;
virtual function void display();
$display(" [BASE] txn_id=%0d addr=0x%08h", txn_id, addr);
endfunction
endclass
class Axi4Txn extends BaseTxn;
rand bit [7:0] len;
rand bit [2:0] size;
rand bit [1:0] burst;
function new(); super.new(); endfunction
virtual function void display();
super.display(); // ← print base fields first
// then add AXI-specific fields below
$display(" [AXI4] len=%0d size=%0d burst=%02b",
len, size, burst);
endfunction
endclass
class Axi4LiteTxn extends BaseTxn;
rand bit [31:0] wdata;
function new(); super.new(); endfunction
// Full replace — no super.display() call
// Used when parent output is not relevant
virtual function void display();
$display(" [AXI-LITE] id=%0d addr=0x%08h data=0x%08h",
txn_id, addr, wdata);
endfunction
endclass
module tb;
Axi4Txn t1 = new();
Axi4LiteTxn t2 = new();
initial begin
void'(t1.randomize()); t1.txn_id = 1;
void'(t2.randomize()); t2.txn_id = 2;
$display("--- AXI4 (uses super.display) ---");
t1.display();
// [BASE] txn_id=1 addr=0x...
// [AXI4] len=... size=... burst=...
$display("--- AXI-Lite (full replace) ---");
t2.display();
// [AXI-LITE] id=2 addr=0x... data=0x...
end
endmoduleUse super.method() when
- ✓You want parent output/logic plus extra child-specific output
- ✓The parent does validation you want to keep
- ✓The parent increments a counter or logs something important
- ✓Calling
display()— always print parent fields first Skip super.method() when - ✗The parent's logic is completely wrong for the child's purpose
- ✗The child needs a totally different format or algorithm
- ✗Calling the parent would cause a side-effect you do not want
- ✗The method was abstract — there is no parent body to call
super in a Multi-Level Hierarchy
When you have three levels — Base → Child → Grandchild — each level's super points to its immediate parent only. The chain propagates automatically when methods call super.method().
super.display() Call Chain — Three LevelsBurstWriteTxn→ Calls super.display() → triggers WriteTxn.display()WriteTxn→ Its display calls super.display() → triggers BaseTxn.display()BaseTxn→ Prints base fields — no more super levels above
// Level 1
class BaseTxn;
rand bit [31:0] addr;
int txn_id;
function new(int id = 0); txn_id = id; endfunction
virtual function void display();
$display(" addr=0x%08h id=%0d", addr, txn_id);
endfunction
endclass
// Level 2 — super refers to BaseTxn
class WriteTxn extends BaseTxn;
rand bit [31:0] wdata;
function new(int id = 0); super.new(id); endfunction
virtual function void display();
super.display(); // super = BaseTxn
$display(" wdata=0x%08h", wdata);
endfunction
endclass
// Level 3 — super refers to WriteTxn (NOT BaseTxn directly)
class BurstWriteTxn extends WriteTxn;
rand bit [7:0] burst_len;
function new(int id = 0); super.new(id); endfunction
virtual function void display();
super.display(); // super = WriteTxn.display()
// which calls super.display() = BaseTxn.display()
$display(" burst_len=%0d", burst_len);
endfunction
endclass
module tb;
BurstWriteTxn bt = new(7);
initial begin
void'(bt.randomize());
bt.display();
// Three lines print in order:
// addr=0x... id=7 ← BaseTxn (triggered by WriteTxn's super)
// wdata=0x... ← WriteTxn (triggered by BurstWriteTxn's super)
// burst_len=... ← BurstWriteTxn own
end
endmodulesuper vs this — The Clear Difference
| this | super | |
|---|---|---|
| Points to | The current object (the instance in memory) | The immediate parent class scope (not an object) |
| Used for | Disambiguating name conflicts, passing self, builder pattern | Calling parent constructor, calling parent method version |
| Type | A handle — usable wherever a class handle is accepted | Not a handle — only valid as super.new() or super.method() |
| Can you assign it? | Yes: other.register(this) | No: BaseTxn b = super — illegal |
| Works in static method? | No — no object context | No — no object context |
class ChildTxn extends BaseTxn;
string tag;
Scoreboard sb;
// 'tag' argument same name as property — use 'this'
function new(int id, string tag, Scoreboard sb);
super.new(id); // super: call parent constructor
this.tag = tag; // this: disambiguate arg vs property
this.sb = sb;
endfunction
virtual function void display();
super.display(); // super: reuse parent display
$display(" tag=%s", this.tag); // this: explicit property access
endfunction
function void register();
sb.add(this); // this: pass self to scoreboard
endfunction
endclassFull Working Example — Complete I2C Transaction Hierarchy
A complete three-class hierarchy showing super.new() and super.display() at every level in a practical I2C verification context.
// ── Level 1: Base ─────────────────────────────────────────────
class I2cBaseTxn;
static int next_id = 1;
rand bit [6:0] slave_addr;
int txn_id;
bit ack; // DUT response
function new();
txn_id = next_id++;
ack = 1; // assume ACK by default
endfunction
virtual function void display();
$display("[I2C #%0d] slave=0x%02h ack=%0b",
txn_id, slave_addr, ack);
endfunction
endclass
// ── Level 2: Write ────────────────────────────────────────────
class I2cWriteTxn extends I2cBaseTxn;
rand bit [7:0] reg_addr;
rand bit [7:0] wdata;
function new();
super.new(); // ← initialises txn_id and ack
endfunction
virtual function void display();
super.display(); // ← print slave_addr and ack first
$display(" WR reg=0x%02h data=0x%02h",
reg_addr, wdata);
endfunction
endclass
// ── Level 3: Burst Write ──────────────────────────────────────
class I2cBurstWriteTxn extends I2cWriteTxn;
rand bit [7:0] extra_bytes[]; // additional bytes after the first
constraint c_burst { extra_bytes.size() inside {[1:8]}; }
function new();
super.new(); // ← triggers I2cWriteTxn which triggers I2cBaseTxn
endfunction
virtual function void display();
super.display(); // ← prints slave + reg + first byte
$display(" BURST extra_bytes=%0d", extra_bytes.size());
foreach (extra_bytes[i])
$display(" [%0d] 0x%02h", i, extra_bytes[i]);
endfunction
endclass
// ── Testbench ─────────────────────────────────────────────────
module tb;
initial begin
I2cWriteTxn wt = new();
void'(wt.randomize());
$display("=== Single Write ===");
wt.display();
I2cBurstWriteTxn bt = new();
void'(bt.randomize());
$display("\n=== Burst Write ===");
bt.display();
end
endmoduleCommon Mistakes with super
| Mistake | What Goes Wrong | Fix |
|---|---|---|
Forgetting super.new() | Parent properties not initialised; in UVM, factory and reporting broken | Always put super.new() as the very first line of the child constructor |
Putting code before super.new() | Compile error — SystemVerilog enforces it must be first | Move all code to after the super.new() call |
Calling super.display() without virtual on parent | Compiles but behaviour is unpredictable — override may not work correctly | Mark parent method virtual before overriding it |
Not passing required args through super.new() | Parent gets default or zero values instead of intended ones | Pass all required args: super.new(name, parent) |
Using super in a static method | Compile error — static methods have no object context | Move the call to a non-static method |
Thinking super skips to grandparent | Logic error — super only goes one level up | Each level calls super.method(); the chain propagates automatically |
Verification Usage — How super Holds a UVM Class Hierarchy Together
Open any production UVM environment and grep for super. — you will find it in every constructor, in every build_phase, in every do_copy / do_compare / do_print hook. The keyword is not optional ornament; it is the contract that keeps inherited behaviour intact.
Constructor chaining (every UVM class)
Every uvm_component takes (string name, uvm_component parent). Every child must forward those arguments up the chain — otherwise the parent's name and parent registration never happen, and the entire phasing system fails silently.
<code>class apb_driver extends uvm_driver #(apb_xact);
`uvm_component_utils(apb_driver)
function new(string name, uvm_component parent);
super.new(name, parent); // mandatory — registers with UVM tree
endfunction
endclass</code>Phase chaining (build / connect / run)
Every UVM phase callback must call super.phase_name(phase) before doing its own work. Skip it, and the parent's setup never happens — which means analysis ports go unbound, factory overrides don't apply, and configuration objects vanish.
<code>function void apb_agent::build_phase(uvm_phase phase);
super.build_phase(phase); // parent builds its sub-components first
drv = apb_driver::type_id::create("drv", this);
mon = apb_monitor::type_id::create("mon", this);
if (is_active == UVM_ACTIVE)
sqr = uvm_sequencer#(apb_xact)::type_id::create("sqr", this);
endfunction</code>Hook methods (do_copy, do_compare, do_print)
UVM provides "do_*" hooks that are called by the public versions (copy(), compare(), print()). The base implementations handle the universal fields; your override calls super first to keep those working, then handles the child's own fields.
<code>function void apb_write_xact::do_copy(uvm_object rhs);
apb_write_xact rhs_;
super.do_copy(rhs); // copies base-class fields
if (!$cast(rhs_, rhs)) begin
`uvm_error("COPY", "type mismatch")
return;
end
this.pwdata = rhs_.pwdata; // copy child-only fields
this.pstrb = rhs_.pstrb;
endfunction</code>Simulation Behavior — What the Simulator Actually Does With super
super is resolved at compile time, not runtime
Unlike this.method() (which dispatches through the vtable to the object's runtime type), super.method() is rewritten by the compiler into a direct call to the parent class's named version. There is no lookup, no indirection, no dispatch — the call site knows exactly which function it is calling before simulation begins.
No recursion when an override calls super.method()
Because super bypasses dynamic dispatch, a child's display() can safely call super.display() without triggering infinite recursion. If the call went through the vtable, the child's vtable slot would re-route back to the child's own display, recursing forever. The static resolution is what makes the pattern safe.
Constructor chaining order
When you construct a child, the simulator executes constructors top-down: the topmost ancestor's body runs first, then each descendant in order down to the leaf class. Field initialisers (int x = 5;) run as part of each class's own constructor, immediately after its super.new() call returns. This is why the parent's fields are guaranteed valid by the time the child constructor's body executes.
No super.super in SystemVerilog
In a 3-level hierarchy A → B → C, code inside class C can write super.method() to reach B's version, but there is no syntax to skip over B and reach A's version directly. If you need A's behaviour from C, the correct path is for B's override to call super.method() itself — chaining one level at a time.
<code>class A; virtual function void f();
$display("A");
endfunction endclass
class B extends A; virtual function void f();
super.f(); // → A::f
$display("B");
endfunction endclass
class C extends B; virtual function void f();
super.f(); // → B::f, which calls A::f
$display("C");
endfunction endclass
// C-handle.f() prints: A, B, C</code><code>class C extends B;
virtual function void f();
super.super.f(); // SYNTAX ERROR
$display("C");
endfunction
endclass
// SystemVerilog has no syntax to skip
// a level. If C needs A::f directly,
// either factor it into a separate
// helper or refactor the hierarchy.</code>Waveform Analysis — Tracing super Calls in Debug
Because super calls are direct (not virtual), they do not show up as separate frames in some waveform-correlated call stacks. To make multi-level overrides visible, instrument each level with a brief $display that includes $typename(this) and the class name doing the work.
The trace pattern
<code>virtual function void BaseTxn::display();
$display("[%0t] BaseTxn::display this=%s id=%0d",
$time, $typename(this), txn_id);
endfunction
virtual function void WriteTxn::display();
super.display(); // logs BaseTxn line first
$display("[%0t] WriteTxn::display this=%s data=0x%h",
$time, $typename(this), wdata);
endfunction</code>ASCII view of the resulting log
<code> 100ns BaseTxn::display this=WriteTxn id=1
100ns WriteTxn::display this=WriteTxn data=0xDEADBEEF
↑ ↑
runtime type child-specific data
visible at every added on top of base
level of the chain output via super</code>The runtime type stays constant across all chained lines because this is the same object throughout — super only changes which method runs, not which object this refers to. This is the single most useful invariant to remember when reading multi-level override logs.
Industry Insights — Hard-Won Lessons About super
Debugging Academy — Five Real Bugs Caused by Misuse of super
"Compile error: super.new() must be the first statement"
DEBUGThe compiler rejects the child constructor with "super.new() must be the first statement of a constructor".
<code>class WriteTxn extends BaseTxn;
rand bit [31:0] wdata;
function new();
$display("Building WriteTxn..."); // BUG: precedes super.new()
super.new("write");
wdata = 0;
endfunction
endclass</code>SystemVerilog mandates that the parent constructor run before any child code touches this — even a benign $display implies access to instance state that doesn't yet logically exist.
Move the $display (and any other code) after the super.new call: function new(); super.new("write"); $display("Building..."); wdata = 0; endfunction.
"Infinite recursion: stack overflow in override"
DEBUGSimulation dies with stack overflow inside a virtual override.
<code>class WriteTxn extends BaseTxn;
virtual function void display();
this.display(); // BUG: dynamic dispatch → calls self again
$display(" data=0x%h", wdata);
endfunction
endclass</code>this.display() goes through the vtable, which for a WriteTxn object dispatches back to WriteTxn::display — itself. Infinite recursion. The intent was clearly to call the parent's version.
Use super for the parent call: super.display(); $display(" data=0x%h", wdata);. super resolves statically to BaseTxn::display, no recursion possible.
"My UVM agent's build_phase silently fails — drv and mon are null"
DEBUGA custom apb_agent compiles cleanly, but at runtime the driver and monitor handles are null and the test crashes.
<code>class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_driver drv;
apb_monitor mon;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
// BUG: missing super.build_phase(phase)
drv = apb_driver::type_id::create("drv", this);
mon = apb_monitor::type_id::create("mon", this);
endfunction
endclass</code>uvm_agent::build_phase is where UVM's own setup (including reading is_active from the config DB) happens. Skipping super.build_phase(phase) means the agent's internal state is half-initialised, and subsequent factory calls misbehave.
The very first line of every UVM phase override is super.<phase>(phase);. Make it a reflex.
"do_copy works but the cloned object is missing the base-class address"
DEBUGtxn.clone() returns a WriteTxn with the right wdata but paddr is zero — yet the original had paddr = 0x4000.
<code>class apb_write_xact extends apb_base_xact;
rand bit [31:0] pwdata;
function void do_copy(uvm_object rhs);
apb_write_xact rhs_;
// BUG: missing super.do_copy(rhs)
if (!$cast(rhs_, rhs)) return;
this.pwdata = rhs_.pwdata;
endfunction
endclass</code>Without super.do_copy(rhs), the parent's fields (paddr, psel, xact_id, etc.) are never copied. The clone has only the fields the child override touched.
Add super.do_copy(rhs); as the first line. The base copies its own fields; the child copies its own additions. Same pattern for do_compare and convert2string.
"How do I reach the grandparent's method? super.super doesn't work"
DEBUGIn a hierarchy A → B → C, the developer wants C to call A::method() directly, bypassing B's override. The line super.super.method(); is a syntax error.
<code>class A; virtual function void f(); $display("A"); endfunction endclass
class B extends A; virtual function void f();
super.f(); // A::f
$display("B (modified)");
endfunction endclass
class C extends B; virtual function void f();
super.super.f(); // SYNTAX ERROR — no such construct
$display("C wants A's behaviour but not B's");
endfunction endclass</code>SystemVerilog provides only single-level super access. There is no syntax to skip an intermediate ancestor.
Either (a) accept the chain: C calls super.f() which calls super.f() which reaches A::f(); (b) refactor the common logic into a named helper on A (e.g., function void a_specific_logic();) that both B and C can call directly without the override chain; or (c) reconsider the hierarchy — needing to skip a level usually signals an inheritance design problem.
Interview Q&A — Twelve Questions You Will Be Asked
Inside a child class, super is a reference to the immediate parent class's scope. The two primary uses are super.new(args) to call the parent constructor and super.method_name(args) to call the parent's version of a method that the child has overridden. super only exists in a class that extends another class — it has no meaning at the top of an inheritance tree.
Best Practices — Ten Rules for Disciplined Use of super
super.new(args)is the first line of every child constructor. No exceptions, no instrumentation above it, no clever ordering. First. Line.- For every overridable method (
display,copy,compare,convert2string,do_*, every UVM phase), callsuper.<same-method>()first, then add the child's logic. - Never use
this.method()when you mean "the parent's version."thisgoes through the vtable and recurses into the same child override. Always usesuperfor parent calls. - Treat the parent's constructor signature as a contract. If the parent constructor takes
(string name, uvm_component parent), the child constructor must accept and forward exactly that signature unless deliberately specialising. - Don't try to write
super.super.method(). It does not exist. Refactor the hierarchy or chain the call through the intermediate level. - Prefer
extend via superoverfull replacementwhen overriding. Callingsuperfirst and adding your own bits keeps base-class behaviour intact and survives future base-class changes. - Be defensive about UVM phase overrides. Every
build_phase,connect_phase,run_phase, andreport_phaseoverride callssuper.<phase>(phase)as its first line. - Don't shadow parent fields. Avoid declaring a child field with the same name as a parent field; if you must, use
super.fieldto reach the inherited one and document why both versions exist. - Document the inheritance contract at the top of every base class. What hooks must children override? Which hooks must children call
superon? Explicit contracts prevent silent breakages months later. - When in doubt, run with
$typename(this)instrumentation in every override. The runtime type plus the function name in the log tells you exactly which level of the override chain executed and on which object.