Inheritance is a concept in OOP that lets you extend a class to create another class, gaining access to all the properties and methods of the original parent class through the new class's own object handle. The idea is to let you add new properties and methods into the new class while still keeping access to the original class members — and to make modifications without touching the base class at all.
8 min read | Intermediate Level
What You'll Learn
- How
extendsbuilds a child class from a parent, inheriting its properties and methods - How a child class can override a parent's method with its own version
- Why calling an overridden method through a parent-typed handle uses the parent's version, not the child's, unless the method is declared
virtual - How to call a parent's own version of an overridden method from inside the child, using
super
A Basic Example
ExtPacket is extended from, and is therefore a child class of, Packet. Being a child class, it inherits properties and methods from its parent. Both classes define a method called display() — when called through a child class handle, the child's own version runs; when called through a parent class handle, the parent's version runs:
class Packet;
int addr;
function new (int addr);
this.addr = addr;
endfunction
function display ();
$display ("[Base] addr=0x%0h", addr);
endfunction
endclass
// A subclass called 'ExtPacket' is derived from the base class 'Packet' using
// 'extends' keyword which makes 'ExtPacket' a child of the parent class 'Packet'
// The child class inherits all variables and methods from the parent class
class ExtPacket extends Packet;
// This is a new variable only available in child class
int data;
function new (int addr, data);
super.new (addr); // Calls 'new' method of parent class
this.data = data;
endfunction
function display ();
$display ("[Child] addr=0x%0h data=0x%0h", addr, data);
endfunction
endclass
module tb;
Packet bc; // bc stands for BaseClass
ExtPacket sc; // sc stands for SubClass
initial begin
bc = new (32'hface_cafe);
bc.display ();
sc = new (32'hfeed_feed, 32'h1234_5678);
sc.display ();
end
endmodule
Output
ncsim> run [Base] addr=0xfacecafe [Child] addr=0xfeedfeed data=0x12345678 ncsim: *W,RNQUIE: Simulation is complete.
sc's constructor calls super.new(addr) to run Packet's own constructor first (setting addr), then sets its own data property — exactly the constructor-chaining pattern covered in SystemVerilog Class Constructor. ExtPacket's own display() completely replaces Packet's version rather than adding to it, which is why bc and sc print in different formats.
Quick Check
Q: If ExtPacket didn't define its own display() at all, what would sc.display() print?
Show Answer
[Base] addr=0xfeedfeed — with no override, ExtPacket simply inherits Packet's display() unchanged, and that version has no idea data even exists, so it would only print addr.
Why the Handle's Type Matters (Without virtual)
It gets more interesting once a base-class-typed handle ends up pointing to a child class object — something that's perfectly legal, since a child class object is also a valid kind of its parent class:
module tb;
Packet p;
ExtPacket sc;
initial begin
sc = new (32'hfeed_feed, 32'h1234_5678);
// p is declared as type Packet, but now points to the SAME
// ExtPacket object that sc points to
p = sc;
p.display ();
end
endmodule
Since neither display() here is declared virtual, p.display() is resolved based on p's declared type (Packet), not the actual object it points to (an ExtPacket). The result: this prints [Base] addr=0xfeedfeed — Packet's version of display(), even though p is actually pointing at the exact same object as sc, which has a data field this call never shows.
This is exactly the kind of surprising behavior virtual methods exist to fix — with virtual, the call would instead dispatch based on the object's actual type, not the handle's declared type. See SystemVerilog Virtual Methods and SystemVerilog Polymorphism for the full picture.
Accessing the Parent's Version With super
Overriding a method doesn't have to mean completely replacing the parent's behavior — from inside the child class, super.method_name() calls the parent's own version explicitly, the same way super.new() calls the parent's constructor:
class ExtPacket extends Packet;
int data;
function new (int addr, data);
super.new (addr);
this.data = data;
endfunction
function display ();
// Run the parent's own display() first, then add the child's own line
super.display ();
$display (" data=0x%0h", data);
endfunction
endclass
Calling sc.display() with this version would print both lines — Packet's own "[Base] addr=..." line first (via super.display()), followed by the child's own "data=..." line — instead of the child's version having to duplicate the parent's formatting itself.
Where Is Inheritance Used?
UVM is built almost entirely on inheritance — uvm_driver, uvm_monitor, and every other component base class exist so that a project's own driver, monitor, and scoreboard classes can extend them, inheriting shared infrastructure while overriding only the parts that are actually specific to that test.
Common Beginner Mistakes
Mistake #1: Assuming a base-typed handle always calls the "most derived" method
As the second example shows, calling a method through a base-class-typed handle uses the base class's own version, even if the handle actually points to a child object. Why? This only changes once the method is declared virtual — without it, dispatch is based on the handle's declared type, not the object's actual type.
Mistake #2: Forgetting super.new() when the base constructor requires arguments
If the parent's constructor requires an argument with no default, the child's constructor must explicitly call super.new(...) with it. Why? SystemVerilog can only call the base constructor implicitly if it takes no required arguments — otherwise, omitting the explicit call is a compile-time error.
Mistake #3: Fully overriding a method when you meant to extend it
A child class's override completely replaces the parent's version unless the child explicitly calls super.method_name() somewhere inside it. Why? It's easy to rewrite a method from scratch in the child class and forget that the parent's own version is still available — often what you actually want is to run the parent's version and then add to it.
Mistake #4: Redeclaring inherited properties in the child class
A child class automatically has access to every property and method its parent declares — it never needs to redeclare them. Why? Only genuinely new properties, like data in ExtPacket, need their own declaration in the child; everything inherited is already there.
Practice Exercise
Challenge: Chain super.display() Across Three Levels
Extend ExtPacket with a third class, ExtPacket2, adding its own property crc. Give it a display() that calls super.display() first, then prints crc. What gets printed when you call display() on an ExtPacket2 object?
Hint
Each level's super.display() call triggers the level above it, which (if it also uses super.display()) triggers the level above that — the calls chain all the way back to Packet.
Solution
class ExtPacket2 extends ExtPacket;
int crc;
function new (int addr, data, crc);
super.new (addr, data);
this.crc = crc;
endfunction
function display ();
super.display ();
$display (" crc=0x%0h", crc);
endfunction
endclass
Calling display() on an ExtPacket2 object prints three lines: Packet's "[Base] addr=..." line, ExtPacket's "data=..." line, and finally ExtPacket2's own "crc=..." line — each level's super.display() call reaches one level further back, all the way up the chain.
Summary
In this tutorial, you learned:
extendscreates a child class that inherits all properties and methods from its parent, without modifying the parent itself- A child class can override a parent's method with its own version
- Without
virtual, calling a method through a handle uses whatever version matches the handle's declared type, not the actual object's type super.method_name()calls the parent's own version of a method from inside a child class's override, letting you extend behavior instead of fully replacing it- A child class only needs to declare properties and methods that are genuinely new — everything inherited is already accessible