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.