Skip to content

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.

super.new() — Correct and Incorrect Usage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

What 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.

Forgetting super.new() — Silent Default vs Hard Error
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
endclass

super.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.

super.display() — Extend Parent Output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

Use 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

Three-Level super Chain in Action
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

super vs this — The Clear Difference

thissuper
Points toThe current object (the instance in memory)The immediate parent class scope (not an object)
Used forDisambiguating name conflicts, passing self, builder patternCalling parent constructor, calling parent method version
TypeA handle — usable wherever a class handle is acceptedNot 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 contextNo — no object context
super and this Used Together
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
 
endclass

Full 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.

Full Example — I2C Transaction with super at Every Level
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
endmodule

Common Mistakes with super

MistakeWhat Goes WrongFix
Forgetting super.new()Parent properties not initialised; in UVM, factory and reporting brokenAlways put super.new() as the very first line of the child constructor
Putting code before super.new()Compile error — SystemVerilog enforces it must be firstMove all code to after the super.new() call
Calling super.display() without virtual on parentCompiles but behaviour is unpredictable — override may not work correctlyMark parent method virtual before overriding it
Not passing required args through super.new()Parent gets default or zero values instead of intended onesPass all required args: super.new(name, parent)
Using super in a static methodCompile error — static methods have no object contextMove the call to a non-static method
Thinking super skips to grandparentLogic error — super only goes one level upEach 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

✅ Correct: each level calls super once
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
❌ Illegal: super.super.f()
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

1

"Compile error: super.new() must be the first statement"

DEBUG
Symptom

The compiler rejects the child constructor with "super.new() must be the first statement of a constructor".

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

Move the $display (and any other code) after the super.new call: function new(); super.new("write"); $display("Building..."); wdata = 0; endfunction.

2

"Infinite recursion: stack overflow in override"

DEBUG
Symptom

Simulation dies with stack overflow inside a virtual override.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

Use super for the parent call: super.display(); $display(" data=0x%h", wdata);. super resolves statically to BaseTxn::display, no recursion possible.

3

"My UVM agent's build_phase silently fails — drv and mon are null"

DEBUG
Symptom

A custom apb_agent compiles cleanly, but at runtime the driver and monitor handles are null and the test crashes.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

The very first line of every UVM phase override is super.<phase>(phase);. Make it a reflex.

4

"do_copy works but the cloned object is missing the base-class address"

DEBUG
Symptom

txn.clone() returns a WriteTxn with the right wdata but paddr is zero — yet the original had paddr = 0x4000.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

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.

5

"How do I reach the grandparent's method? super.super doesn't work"

DEBUG
Symptom

In 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.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

SystemVerilog provides only single-level super access. There is no syntax to skip an intermediate ancestor.

Fix

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

  1. super.new(args) is the first line of every child constructor. No exceptions, no instrumentation above it, no clever ordering. First. Line.
  2. For every overridable method (display, copy, compare, convert2string, do_*, every UVM phase), call super.<same-method>() first, then add the child's logic.
  3. Never use this.method() when you mean "the parent's version." this goes through the vtable and recurses into the same child override. Always use super for parent calls.
  4. 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.
  5. Don't try to write super.super.method(). It does not exist. Refactor the hierarchy or chain the call through the intermediate level.
  6. Prefer extend via super over full replacement when overriding. Calling super first and adding your own bits keeps base-class behaviour intact and survives future base-class changes.
  7. Be defensive about UVM phase overrides. Every build_phase, connect_phase, run_phase, and report_phase override calls super.<phase>(phase) as its first line.
  8. Don't shadow parent fields. Avoid declaring a child field with the same name as a parent field; if you must, use super.field to reach the inherited one and document why both versions exist.
  9. Document the inheritance contract at the top of every base class. What hooks must children override? Which hooks must children call super on? Explicit contracts prevent silent breakages months later.
  10. 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.