Skip to main content

Polymorphism and Virtuality in SystemVerilog

· loading · loading · ·
Course Verification SystemVerilog SystemVerilog OOP Object-Oriented Programming Verification Virtuality
Course Verification SystemVerilog
Axolot Logic
Author
Axolot Logic
Digital Design Engineer
Table of Contents
SystemVerilog Design Series - This article is part of a series.
Part 28: This Article

Polymorphism and Virtuality in SystemVerilog
#

This section explores polymorphism and virtuality in SystemVerilog—key concepts for writing flexible, reusable, and maintainable testbenches. We’ll also cover practical tips on casting, method resolution, and abstract classes.


1️⃣ Polymorphism and Virtuality
#

Polymorphism allows a single handle of a base class type to refer to different derived class objects at runtime. This enables dynamic method dispatch.

Virtuality is implemented in SystemVerilog using the virtual keyword on class methods. It tells the simulator to resolve the method call dynamically, based on the actual type of the object rather than the static type of the handle.

Example:

// File: tb_polymorphism.sv

`timescale 1ns/1ps

// Virtual class definition
virtual class Packet;
  // Virtual display function
  virtual function void display();
    $display("Packet Display");
  endfunction
endclass

// DataPacket class extending Packet
class DataPacket extends Packet;
  // Overriding the display function
  function void display();
    $display("DataPacket Display");
  endfunction
endclass

// Testbench module
module tb_polymorphism;

  // Packet handle declared at module level
  Packet pkt;
    DataPacket dpkt;

  initial begin
    DataPacket dpkt;
    dpkt = new();
    pkt = dpkt;      // assign derived class object to base handle
    pkt.display();   // Calls DataPacket's display() method
  end

endmodule

2️⃣ Class Member Access in Polymorphism
#

By default, resolution of class member access is always according to the type of the handle, not its contents. This means:

  • Variables are always accessed based on the handle’s static type.
  • Methods can be dynamically dispatched if declared virtual.

Example:

// File: tb_inheritance.sv

`timescale 1ns/1ps

// Base class
class Base;
  int id = 1;
endclass

// Derived class
class Derived extends Base;
  int id = 2;
endclass

// Testbench module
module tb_inheritance;

  Base b;
  Derived d;

  initial begin
    d = new();     // Derived nesnesi oluştur
    b = d;         // Base handle'a atama yap
    $display("ID = %0d", b.id); // Base class'ın id'si yazdırılır
  end

endmodule

3️⃣ Resolving Class Method Access
#

  • Non-virtual methods: Resolved based on the handle’s static type.
  • Virtual methods: Resolved dynamically based on the object’s actual type.

Virtual methods allow overriding in derived classes. Without virtual, the base class method is always called.


4️⃣ Why Use virtual Methods?
#

Because by default, method resolution uses the handle’s type, not the object’s contents. To allow dynamic dispatch based on the object’s type, declare the base class method as virtual.

  • Static Resolution:
// File: tb_static_resolution.sv

`timescale 1ns/1ps

// Base class
class Packet;
  function void display();
    $display("Packet Display");
  endfunction
endclass

// Derived class
class DataPacket extends Packet;
  function void display();
    $display("DataPacket Display");
  endfunction
endclass

// Testbench module
module tb_static_resolution;

  Packet pkt;
  DataPacket dpkt;

  initial begin
    dpkt = new();
    pkt = dpkt;
    pkt.display(); // Static resolution: calls Packet's display()
  end

endmodule

Now, pkt.display() calls the correct derived class method at runtime.


5️⃣ Copying a Sub-Class Instance to a Parent Handle
#

  • A sub-class instance can always be directly assigned to a parent class handle.
  • However, by default, only the parent class members are accessible through the handle:
    • This is true even though the handle contains a sub-class instance.

This is straightforward:

// File: tb_copy_subclass.sv

`timescale 1ns/1ps

// Base class
class Packet;
  function void display();
    $display("Packet Display");
  endfunction
endclass

// Derived class
class DataPacket extends Packet;
  function void display();
    $display("DataPacket Display");
  endfunction
endclass

// Testbench module
module tb_copy_subclass;

  DataPacket dp;
  Packet pkt;

  initial begin
    dp = new();
    pkt = dp;        // Sub-class instance assigned to parent handle (upcasting)
    pkt.display();   // Calls Packet's display() (static resolution)
  end

endmodule

Here, pkt points to the same object as dp, but only base class members are visible via pkt.


6️⃣ Copying a Parent Instance to a Sub-Class Handle
#

  • Directly assigning a parent class handle to a handle of one of its sub-classes is never allowed.
  • Such an assignment is only valid if the parent handle actually refers to an object of the target sub-class.

This is not directly allowed, because a base class might not contain all the information a subclass needs. To do this safely, use $cast.

Example:

// File: tb_downcasting.sv

`timescale 1ns/1ps

// Base class
class Packet;
  function void display();
    $display("Packet Display");
  endfunction
endclass

// Derived class
class DataPacket extends Packet;
  function void display();
    $display("DataPacket Display");
  endfunction
endclass

// Testbench module
module tb_downcasting;

  Packet pkt;
  DataPacket dp;

  initial begin
    dp = new();
    pkt = dp; // only parent method visible
    if (!$cast(dp, pkt)) begin
      $display("Cast failed");
    end
    else begin
      dp.display();
    end
  end

endmodule

7️⃣ Using $cast
#

$cast safely attempts to cast a parent handle to a child handle. If successful, it returns 1; otherwise, it returns 0. This prevents simulation errors.

Syntax:

if (!$cast(child_handle, parent_handle)) begin
  // Handle cast failure
end

8️⃣ Advantages of Polymorphism
#

✅ Enables flexible and reusable verification architectures.
✅ Allows testbench components to work with different implementations transparently.
✅ Reduces code duplication by enabling dynamic behavior selection.
✅ Essential for frameworks like UVM.


9️⃣ Virtual Method Resolution
#

In SystemVerilog, virtual methods are resolved dynamically at runtime based on the actual object type rather than the static type of the handle.

🔍 Resolution Rules:

  • The simulator uses the actual object type (the class that was instantiated) to determine which display() function to call.
  • The handle type is irrelevant if the function is declared virtual.
  • If a derived class overrides a virtual function, that override is called when the function is invoked via a base class handle.

Example:

// File: tb_virtual_dispatch.sv

`timescale 1ns/1ps

// Base class
class Packet;
  virtual function void display();
    $display("Packet Display");
  endfunction
endclass

// Derived class
class DataPacket extends Packet;
  function void display();
    $display("DataPacket Display");
  endfunction
endclass

// Testbench module
module tb_virtual_dispatch;

  Packet pkt;
  DataPacket dpkt;

  initial begin
    dpkt = new();
    pkt = dpkt;     // Base handle assigned to derived object
    pkt.display();  // Calls DataPacket's display() because display() is virtual
  end

endmodule

1️⃣0️⃣ Abstract Classes and Pure Virtual Methods
#

An abstract class cannot be instantiated directly. It often defines pure virtual methods, which must be overridden in derived classes.

  • May have pure virtual methods, prototype only, no implementation
  • Subclass must provide implementation

Example:

virtual class Packet;
  pure virtual function void display();
endclass

class DataPacket extends Packet;
  function void display();
    $display("DataPacket Display");
  endfunction
endclass

Here, Packet is abstract and cannot be instantiated. Any derived class must implement display().


1️⃣1️⃣ Shallow Copy and Deep Copy
#

In SystemVerilog, copying class objects can be subtle because classes are always reference types. This means that assignment copies the handle, not the entire object—this is called a shallow copy. If you need to copy all internal properties, including nested objects, that’s known as a deep copy.


🔄 Shallow Copy
#

A shallow copy duplicates the handle so that both references point to the same object in memory. Modifying the object via one handle also affects the other.

Example:

// File: tb_shallow_copy.sv

`timescale 1ns/1ps

// Base class
class Packet;
  bit [7:0] data;
endclass

// Testbench module
module tb_shallow_copy;

  Packet pkt1;
  Packet pkt2;

  initial begin
    pkt1 = new();
    pkt1.data = 8'hAB;

    pkt2 = pkt1;           // Shallow copy
    pkt2.data = 8'hCD;     // Actually changes pkt1.data too

    $display("pkt1.data = %0h", pkt1.data); // Prints CD
  end

endmodule

Both pkt1 and pkt2 refer to the same object, so changes via pkt2 also affect pkt1.


📦 Deep Copy
#

A deep copy creates a new object and then copies each property (and any nested objects) from the original to the new object. This way, the copies are completely independent.

SystemVerilog does not provide a built-in deep copy; you must code it manually.

Example:

// File: tb_deep_copy.sv

`timescale 1ns/1ps

// Base class
class Packet;
  bit [7:0] data;

  // Deep copy function
  function Packet deepCopy();
    Packet copy = new();
    copy.data = this.data;
    return copy;
  endfunction
endclass

// Testbench module
module tb_deep_copy;

  Packet pkt1;
  Packet pkt2;

  initial begin
    pkt1 = new();
    pkt1.data = 8'hAB;

    pkt2 = pkt1.deepCopy(); // Deep copy
    pkt2.data = 8'hCD;

    $display("pkt1.data = %0h", pkt1.data); // Prints AB
    $display("pkt2.data = %0h", pkt2.data); // Prints CD
  end

endmodule

📖 Conclusion
#

Polymorphism and virtuality in SystemVerilog are essential for building modular and dynamic testbenches. Understanding casting, dynamic method resolution, and abstract classes will make your verification environment more powerful and flexible.


SystemVerilog Design Series - This article is part of a series.
Part 28: This Article

Related

Advanced OOP in SystemVerilog: Aggregation, Inheritance, and More
· loading · loading
Course Verification SystemVerilog SystemVerilog OOP Object-Oriented Programming Verification Classes
Course Verification SystemVerilog
Advanced OOP in SystemVerilog: Constructors, Handles, and Static Members
· loading · loading
Course Verification SystemVerilog SystemVerilog OOP Object-Oriented Programming Verification Classes
Course Verification SystemVerilog
Object-Oriented Programming in SystemVerilog
· loading · loading
Course Verification SystemVerilog SystemVerilog OOP Object-Oriented Programming Verification Classes
Course Verification SystemVerilog
Mailboxes in SystemVerilog
· loading · loading
Course Verification SystemVerilog SystemVerilog IPC Mailboxes Verification Synchronization
Course Verification SystemVerilog
Named Events in SystemVerilog
· loading · loading
Course Verification SystemVerilog SystemVerilog IPC Named Events Verification Synchronization
Course Verification SystemVerilog
Semaphores in SystemVerilog
· loading · loading
Course Verification SystemVerilog SystemVerilog IPC Semaphores Verification Synchronization
Course Verification SystemVerilog