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 extends builds 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:

ExtPacket extends Packet: inherited, new and overridden members

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

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:

  • extends creates 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
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5