SystemVerilog Polymorphism

We discussed in the previous post i.e. “SystemVerilog Inheritance” about Up-Casting & Down-Casting. But the question is – Why do we want to downcast an Object which is hold by a Base Class variable? That’s where SystemVerilog Polymorphism comes into play. We’ve seen that we can use Inheritance to reuse existing Class definitions and Extend their behavior. But so far, we have to know that we’re dealing with an Extending Class to access that behavior.

Polymorphism is the ability  to have the same code act differently based on the type of the Object that its being working with. This is a key topic of any Object Oriented Programming language. SystemVerilog enables Polymorphism in two ways: Dynamic (Run-Time) and Static (Compile-Time) Polymorphism. Here we’ll discuss about Dynamic mode of Polymorphism which is supported via “Virtual Methods“.

Lets see what happens when we declare a Method as Virtual :


typedef enum {IDLE, RUN, P0, P1} cmd_t;

///// Base Class Declaration
class Packet;
 
 /// Properties
 cmd_t cmd;
 int status;
 bit [7:0] data [0:255];
 
 /// Method
 virtual function void SetStatus (input int y);
 status = y;
 endfunction: SetStatus
 
endclass: Packet

///// Extended Class Declaration
class myPacket extends Packet;
 
 /// Added Properties
 bit errBit;
 
 /// Newly Added Method
 function bit ShowError();
   return(errBit);
 endfunction: ShowError
 
 /// Overriding Method
 virtual function void SetStatus (input int y);
   status = y + 2;
 endfunction: SetStatus
 
endclass: myPacket

module top;

 Packet pkt = new;
 myPacket m_pkt = new;
 
 task my_run (Packet PKT);

   PKT.SetStatus(2);
   $display("Status value is = %0d", PKT.status); 
   endtask: my_run
 
 initial begin
   my_run(pkt);
   my_run(m_pkt);
 end
 
endmodule: top

In the example being shown above, we’ve the same Packet Class which is also being used in previous posts. The Methods defined is also same i.e. SetStatus(). The only difference here is that the function SetStatus() is declared along with a keyword “virtual“. Now compiler resolve the Method call not solely based on type of the Class variable, it looks up the Virtual Method to call based on the type of Handle stored inside the Class variable.

In the above code, we can see that we’ve an Extended Class named “myPacket” with the same name of function “SetStatus()” with updated functionality. Both the Classes are instantiated & constructed. We’ve a task called “my_run” which takes an argument of Packet Class type as input. We call “SetStatus(2)” inside the my_run task. Here we can see that we called my_run task two times and each time with different arguments i.e. “pkt” & “”. Using the $display, we can observe that each time, SetStatus() function is called from the Base Class and the Extended Class respectively.

An important thing to note, to make this work, a Virtual Method needs to have the same prototype means to pass the same argument types in all derived Methods. One more thing to note down is that once we declared a Method as Virtual, its always Virtual in all derived Classes. It means, we can not change the nature of the Method from Virtual to Non-Virtual in any of the derived Classes.

Application of Polymorphism:

Here is one of the common use of Polymorphism with Virtual and Non-Virtual Methods. In SystemVerilog OOP – Part 2, we discussed about the Deep Copy of an Object. Here we’ll discuss about combining Inheritance with Deep Copy along with the construction of a new Object what is known as “Clone”. Clone is a Virtual Method that returns a Handle of a new Object that is a Deep copy of the calling Object. Because it is Virtual, it does this without the knowledge of weather its dealing with Base Class Object or one of its Derivatives.

Lets see the example code provided below to understand this:


class read;
 
 /// Properties
 rand int unsigned status;
 rand bit [7:0] addr;
 
 /// Constraints
 constraint c_s {
  status <= 5;
 addr inside {[8'h0:8'hF]};
 }
 
 /// Virtual Method
 virtual function read clone();
  read h = new;
  h.copy(this);
 $display("\nBase Class Clone Method is Called");
  return h;
 endfunction: clone
 /// Non-Virtual Method
 function void copy(read rd);
  this.status = rd.status;
  this.addr = rd.addr;
 endfunction: copy
 /// Virtual Method
 virtual function void print;
  $display("Read Transaction: Address = %0h, status = %0d", addr, status);
 endfunction: print
 
endclass: read


class my_read extends read;
 
 rand bit rw = 1;;
 
 virtual function read clone();
  my_read p = new;
  p.copy(this);
  $display("\nExtended Class Clone Method is Called");
  return p;
 endfunction: clone
 
 function void copy (my_read my_rd);
  this.rw = my_rd.rw;
  super.copy(my_rd);
 endfunction: copy
 
 virtual function void print;
  $display("Read Transaction: Address = %0h, status = %0d, RW bit = %0d", addr, status, 
rw);
 endfunction: print
 
endclass: my_read


module top;
 // Extended Class to be sent
 my_read my_rd_op;
 read my_rd;

 /// Burst Transfer
 task burst (read RD, int N);
  repeat(N) begin
  RD.randomize;
  send(RD.clone());
  end
 endtask: burst
 /// Send task
 task send (read H);
  H.print;
 endtask: send
 
 initial begin
  my_rd_op = new;
  my_rd = new;
  burst(my_rd,10);
  my_rd = my_rd_op;
  burst(my_rd,5);
 end
 
endmodule: top

If we look at the Clone() virtual method of Base Class “read” in the code provided above, it first construct a ‘read’ Object then copy itself by calling the copy() Method and returns a Handle to that newly constructed Object. The copy() method of Base Class “read” is very simple in this example, it just copies the Properties of the Object whose Handle is passed in as the “rd” argument in the example. “rd” is a Handle to the Object that called clone() virtual Method. The target of the copy is newly constructed Object created by clone() i.e. the Object with variable “h” above inside the Clone() Method of read Class.

Looking at the Extension of the Virtual Method “Clone()” inside Extended Class “my_read“, we see that its prototype is the same. The only thing different in each Clone override is the type of Object it constructs. Each override of copy() copies its local Properties & then calls super.copy() to copy the local Properties of the Class it was derived from. We want copy() to be Non-Virtual so that its argument can remain a local Class type which allows direct access to the local Properties without any casting. The Virtual nature of the Clone Method takes us to the proper derived copy() Method because we always call copy() from the correct Class variable type.

Finally we’ve a burst() task that takes the Handle of an “read” type Object, Clones it, and sends it down to the other chain of tasks without ever need to know if its dealing with a “read” Class Object or Class derived from “read” Class. Inside the initial procedural block, we created the handles for both the Classes and called the burst function with the same handle i.e. my_rd. Yet in the simulation log, we may see that different Clone methods i.e from Base and Extended classes are called. The key reason is the Polymorphism application & functions declared as virtual. Its a good trial to remove the “virtual” keyword from the “clone” function inside the “read” base class and run the code again. We’ll notice now only Base Class prints will appear. ReasonPolymorphism.

Base Class library typically filled with Virtual Methods  to make your code much more reusable.


So just as a recap, we discussed in this post about one of the fundamental concept of SystemVerilog OOP i.e. Polymorphism. We saw the application of Polymorphism in terms of Clone which is Inheritance with Deep Copy along with Construction of an Object. We saw the mixing of Virtual and Non-Virtual Methods.

I believe this post would be helpful to you to get fair information about Polymorphism. I’m planning to write about few other topics in SystemVerilog OOP soon. Keep tune in for those upcoming topics. Please feel free to write in comments about any of your suggestion/input.

Thanks! See you again soon..bye!!~ 🙂