Polymorphism:

In short: Polymorphism lets the same call run different code depending on the actual object type behind a handle. In SystemVerilog the run-time form uses virtual methods. If a base-class method is virtual, calling it through a base handle that points at a derived object runs the derived version. Once a method is virtual it stays virtual in every derived class, and you reach it through the base handle only if it also exists in the base class.

Polymorphism is one of the pillars of object-oriented programming, and it is the mechanism that makes the UVM factory and callbacks work. The idea is simple to state: the same line of code behaves differently based on the type of object it is working with. This post explains the two forms SystemVerilog supports, focuses on the run-time form driven by virtual methods, shows the difference with and without virtual, and answers the common questions.

Two kinds of polymorphism

SystemVerilog supports polymorphism in two ways. Static, or compile-time, polymorphism is resolved by the compiler, for example choosing between overloaded operators or parameterized types. Dynamic, or run-time, polymorphism is resolved while the simulation runs, based on the real object a handle points at, and it is provided through virtual methods. The rest of this post focuses on the dynamic form, because that is the one that shapes how verification code is written.

A simple way to picture it

Think of a TV remote with a single power button. The button is the same no matter what sits in front of it, but pressing it does the right thing for whichever device it is paired with: a TV turns the screen on, a sound bar wakes the speakers, a projector fires up the lamp. You press one button; the behaviour depends on the actual device.

A virtual method is that power button. You call the same method through a base handle, and the object it currently points at decides which version actually runs. Without virtual, the remote would ignore the real device and always do whatever the base type said, which is usually not what you want.

Example without virtual

Here the base method is not virtual. When a base handle points at a derived object, the call still runs the base version, because the choice is made from the handle type, not the object type.

class Base;
  task print();
    $display("This is Base Class");
  endtask
endclass

class Extended extends Base;
  task print();
    $display("This is Extended Class");
  endtask
endclass

program main;
  Extended ext;
  Base      ba;
  initial begin
    ba = new();
    ba.print();     // prints: This is Base Class

    ext = new();
    ba  = ext;      // base handle now points at a derived object
    ba.print();     // still prints: This is Base Class (no virtual)
  end
endprogram : main

Because print is not virtual, the second call is decided from the handle type Base, so it prints the base message even though the object is an Extended. The screenshot below shows this output.

Simulation output without a virtual method: both calls print the base class message because the call is resolved from the handle type.

Example with virtual

Now mark the base method virtual. The second call now runs the derived version, because a virtual call is resolved from the real object the handle points at.

class Base;
  virtual task print();          // now virtual
    $display("This is Base Class");
  endtask
endclass

class Extended extends Base;
  task print();                  // stays virtual automatically
    $display("This is Extended Class");
  endtask
endclass

program main;
  Extended ext;
  Base      ba;
  initial begin
    ba = new();
    ba.print();     // prints: This is Base Class

    ext = new();
    ba  = ext;      // base handle points at a derived object
    ba.print();     // prints: This is Extended Class (virtual)
  end
endprogram : main

With virtual, the second call follows the real object type Extended, so it prints the extended message. The screenshot below shows the changed output.

Simulation output with a virtual method: the second call prints the extended class message because the call follows the real object type.

Rules worth remembering

  • Once a method is declared virtual in the base class, it is virtual in every derived class. You cannot turn it back into a non-virtual method further down the chain.
  • A method that exists only in the derived class, not in the base class, cannot be called through a base handle. The compiler checks against the base class definition and reports an error.
  • A virtual call is resolved from the actual object, a non-virtual call from the handle type.
  • The signature of the overriding method must match the base method for the override to take effect.

A short exercise

Try this yourself: declare print as virtual in both the base and the derived class and run the same program. Think about what you expect before you run it. Because the base method is virtual, the behaviour is the same as the second example: the call follows the real object. Marking the derived method virtual again does not change anything, since it was already virtual by inheritance. That is exactly the rule from the previous section in action.

Why this matters in verification

Run-time polymorphism is what lets a testbench hold objects through base handles and still get type-correct behaviour. It is the basis of the UVM factory (create by type, override with a derived type) and of callbacks. Declaring the right methods virtual is what makes those overrides actually take effect.

Common mistakes

  • Forgetting virtual on the base method and wondering why the override never runs through a base handle.
  • Expecting to call a derived-only method through a base handle. Add it to the base class (often as a virtual method) if you need that.
  • Changing the method signature in the derived class, so it does not actually override the base method.
  • Assuming you can make a method non-virtual again in a derived class. Once virtual, always virtual.
  • Mixing up the two forms: overloading is compile-time, virtual methods are run-time.

Key takeaways

  • Polymorphism means the same call behaves differently based on the real object type.
  • SystemVerilog has static (compile-time) and dynamic (run-time) polymorphism; the run-time form uses virtual methods.
  • A virtual call follows the object; a non-virtual call follows the handle type.
  • Once virtual, a method stays virtual in all derived classes.
  • Virtual methods are the basis of the UVM factory and callbacks.

Honesty note: the code follows a plain reading of the SystemVerilog LRM (IEEE 1800) and is written to show the idea. Exact output depends on your simulator, so confirm on your own tool or on EDA Playground before relying on it.

Related reading

What is polymorphism in SystemVerilog?

Polymorphism is the ability for the same call to run different code depending on the actual object type behind a handle. It is a core object-oriented concept used heavily in class-based verification.

What are the two types of polymorphism in SystemVerilog?

Static (compile-time) polymorphism, resolved by the compiler, and dynamic (run-time) polymorphism, resolved while the simulation runs based on the real object. The run-time form uses virtual methods.

What is a virtual method?

A virtual method is one whose call is resolved from the actual object a handle points at, not from the handle type. It is how run-time polymorphism is achieved in SystemVerilog.

What is the difference between a virtual and a non-virtual method?

A virtual method call follows the real object, so a base handle pointing at a derived object runs the derived version. A non-virtual method call follows the handle type and runs the base version.

Can I make a virtual method non-virtual in a derived class?

No. Once a method is declared virtual in the base class, it stays virtual in every derived class. You cannot change it back to non-virtual.

Can I call a derived-only method through a base handle?

No. A method that exists only in the derived class cannot be called through a base handle. The compiler checks against the base class definition and reports an error.

Why does virtual matter for the UVM factory?

The factory creates objects by type and lets a test override one type with a derived type. Virtual methods are what make the derived version actually run when called through a base handle.

Does re-declaring virtual in the derived class change anything?

No. If the base method is already virtual, the derived override is virtual by inheritance. Adding the virtual keyword again in the derived class has no extra effect.

Similar Posts

3 Comments

  1. I guess in the above example, if we declared method as Virtual in Base Class as well as in Derived Class, the Base class print method will still point to the print method of Extended class, but I’m not completely sure about the reason behind it. Could you elaborate?

      1. Hi Hardik,
        Could you please explain the difference between declaring method in both base class and child class as virtual , v/s just declaring base class method virtual.

Comments are closed.