Inheritance in SystemVerilog OOPs:
Inheritance in SystemVerilog is the most commonly used principle of Object Oriented Programming (OOP) that facilitates reuse. It’s called Inheritance because it creates new classes taking all the existing Properties and Methods from the Base Class or Super Class. The new Class is called Extended Class or Derived Class. The Extended Class contains everything declared in the Base Class or Super Class. In addition, we may choose to add additional Properties and/or Methods. We can also override the existing Methods. This way we can take the Original Base Classes and modify them as we like without disturbing the existing functionality of the Base Class or Super Class.
Extending The Class Properties: Let’s understand the beauty of the Inheritance concept in terms of Extending the class properties using the same System Verilog code.
class Packet;
// Properties
cmd_t command;
int status;
logic [7:0] data [0:255];
endclass: Packet
class myPacket extends Packet;
bit errBit;
endclass: myPacket
In the above piece of code, we defined a class called Packet which is having some properties named as a command, status, and data. Later we derived another class named as myPacket so as per definition we can call myPacket class a child class/derived class or subclass of the Superclass Packet. You may notice that we added another property in the derived class or subclass i.e. errBit without disturbing the Base Class/Super Class.
So, the effective class of the above example will look like the following:
class myPacket;
// Properties
cmd_t command;
int status;
logic [7:0] data [0:255];
bit errBit;
endclass: myPacket
It means all the properties from the Base Class/Super Class i.e Packet Class are available in the Extended Class i.e myPacket Class along with the newly added Properties i.e errBit. A big advantage of Inheritance is that any change made inside the Base Class/Super Class will be automatically reflected or propagated in all the Derived Classes effectively.
Extending The Class Methods:
We can add Methods to a Derived Class/Extended Class in the same way as we did with Properties.
Let’s see the behavior of the Extending the class methods through the below example:
typedef enum [IDLE, RESET, P1, ...] cmd_t;
class Packet;
cmd_t command;
int status;
logic [7:0] data [0:255];
// Methods
function void SetStatus (input int Y);
status = Y;
endfunction: SetStatus
endclass: Packet
class myPacket extends Packet;
bit errBit;
// Overriding SetStatus Function
function void SetStatus (input int Y);
status = Y + 3;
endfunction: SetStatus
// Adding a New Method
function int errStatus();
return(errBit);
endfunction: errStatus
endclass: myPacket
In the above example code, we have an Original Class/Base Class named “Packet” containing 3 Properties i.e. command, status, data, and 1 Method i.e. SetStatus. There is another Class name “myPacket” which is Derived/Extended from Base Class Packet. myPacket contains the Method “SetStatus” with updated functionality. Overriding a Method means it hides the Base Class Method from the Extended Class but it does not write over it or replace it. There is a new Method added i.e. “errStatus” to the myPacket Class.
Super in SystemVerilog Inheritance: SystemVerilog provides a “super” keyword to access the Properties and Methods which are present in the Base Class using the super keyword. This aids reuse because we can make small modifications to the Method by adding the code around the overridden Method. The Method being overridden does not have to be immediate Base Class. Remember there can be many layers of extended Classes and each extension inherits everything that Base Class inherited. super reaches down as many levels are needed to find the overridden Method.
typedef enum [IDLE, RESET, P1, ...] cmd_t;
class Packet;
// Properties
int status;
// Methods
function void SetStatus (input int Y);
status = Y;
endfunction: SetStatus
endclass: Packet
class myPacket extends Packet;
bit errBit;
// Overriding SetStatus Function
function void SetStatus (input int Y);
super.SetStatus(Y + 3);
endfunction: SetStatus
endclass: myPacket
In the above example code, the Method inside the Extended Class i.e. myPacket calls the SetStatus function from the Base Class i.e Packet using “super”.
Constructing Extended/Derived Classes:
All the Classes needed a “constructor” i.e. new() to build an Object of that Class type. SystemVerilog implicitly declares it if we do not define it. Extended Classes also need a constructor as well as needing a call to their Base Class constructor as the first statement in their constructor. Again, SystemVerilog provides “super.new()” for us if we do not call up by our-self. Whenever we have a chain of Extended Classes, we also have a chain of constructors from the Base Class to the outermost Class. If none of our Classes have explicit constructors that are perfectly OK as SystemVerilog automatically inserts the constructors for us as well as calls the super.new() for us. But the challenge arises when one of the constructors in the chain has arguments. In that case, we need to call the super.new() with the expected type of arguments else Simulator will generate a compilation error.
class Packet;
int pkt_id;
static int sid;
string CMP_Name;
// Constructor with Argument
function new (string name);
CMP_Name = name;
pkt_id = sid++;
endfunction: new
endclass: Packet
class argPacket extends Packet;
function new;
super.new("newpkt");
endfunction: new
endclass: argPacket
In the above-given example code, we can see that the Base Class i.e. Packet contains a constructor which requires an argument of string type to pass to construct the Object. Hence the Extended Class i.e. argPacket needs to call super.new() with a string argument, if not simulator will pop up the compile error.
Class Variables with Inheritance:
Here we will be going to see How SystemVerilog Inheritance works with Class Variables and Handles. Remember when constructing an Extended Class, the Extended Class variable gets the Handle of a single Object which contains all the Properties and Methods of Base Class as well as Extended Class. The base Class variable can hold the Handles of Extended Class Objects but the reverse will generate compilation error in normal scenarios. We need to perform Down Casting ($cast) if we want to assign Base Class Handle to the Extended Class variable.
Let’s understand by the following example:
typedef enum {IDLE, RUN, P0, P1} cmd_t;
class Packet;
// Properties
cmd_t cmd;
int status;
bit [7:0] data [0:255];
// Method
function void SetStatus (input int y);
status = y;
endfunction: SetStatus
endclass: Packet
class extndPacket extends Packet;
// Added Properties
bit errBit;
// Newly Added Method
function bit ShowError();
return(errBit);
endfunction: ShowError
// Overriding Method
function void SetStatus (input int y);
status = y + 2;
endfunction: SetStatus
endclass: extndPacket
program main;
// Array of Type Extended Packet
extndPacket epkt [3:0];
initial begin
Packet pkt;
foreach (epkt[i]) begin
epkt[i] = new;
epkt[i].cmd = IDLE;
epkt[i].SetStatus(1);
$display("********************************************");
$display("Extended Packet epkt[%0d] Status Value is: %0d & Command value is:%s",
i, epkt[i].status, epkt[i].cmd);
end
pkt = epkt[0];
pkt.cmd = RUN;
pkt.SetStatus(1);
$display("Base Packet variable holding Extended Handle pkt.status=%0d, pkt.cmd=%s",
pkt.status, pkt.cmd);
$display("**********************************************\n");
epkt[0] = pkt; // This line will generate compilation error. Comment it to run.
end
endprogram
In the above example code, there are familiar Classes i.e. Packet and extndPacket. An array of Class type extndPacket is created which contains four elements. Using foreach() loop, Objects are being constructed for the Extended Class. An Object is constructed for Base Class Packet. In the bold code line, the Handle of one of the Extended Objects i.e. epkt[0] is assigned to the Base Class variable. Then values are assigned to different Properties and Methods. Using $display we can see that pkt.status is 1, which means it is accessing the Method inside the Base Class Object. The reason for this is the absence of Virtual Methods. Within foreach loop, we can see that Extended Object Handle accesses the SetStatus() Method from the Extended Class. But even we assigned the Extended Object Handle to the Base Class variable, it still uses to access the Base Class SetStatus() Method. We can not assign the Base Class Handle to the Extended Class variable as shown in the above code with a line of comment. If this line of code is part of the code, it will generate a compilation error.
Up Casting and Down Casting of Class Handles:
Let’s understand Inheritance more deeply using Inheritance Tree.
Here we’ve five Classes with “A” at the root of the hierarchy. “B” & “C” Classes are derived from A Class. Similarly “D” and “E” are derived from Class “C“. One Property is given to each Class. So as per above Inheritance tree, following Properties are available to mentioned Classes:
Class Type | Available Variables |
A | a |
B extends A | A, b |
C extends A | A, c |
D extends C | A, c, d |
E extends C | A, c, e |
Here notice that all the Classes shared the root Class A Property ‘a‘, but Class B only has a Property ‘b’. We can create an Object of any Class type and store its Handle in the Class variable of the same type OR type up the Class tree in the hierarchy. That is called up-casting.
If I construct an E Object, we can assign it Handle to either of the Class variables of type E, C or A. But we can not assign its Handle to a Class variable of type B or D. A Class E does not have a ‘b‘ Property which a B Class expects to be able to access.
But suppose we have an A-Class variable that we know holds the Handle of a Class E Object. At this stage, how can we access the E‘s ‘e‘Properties? To achieve this we need to downcast the Handle presently held by Class A back to the Class E variable. Since this is not allowed to be done directly, SystemVerilog provides a dynamic $cast function that checks to see if the assignment is compatible & if yes, it does assign the Handle back.
Let’s go through the extended Packet example again to understand this concept more clearly:
typedef enum {IDLE, RUN, P0, P1} cmd_t;
class Packet;
// Properties
cmd_t cmd;
int status;
bit [7:0] data [0:255];
// Method
function void SetStatus (input int y);
status = y;
endfunction: SetStatus
endclass: Packet
class extndPacket extends Packet;
// Added Properties
bit errBit;
// Newly Added Method
function bit ShowError();
return(errBit);
endfunction: ShowError
// Overriding Method
function void SetStatus (input int y);
status = y + 2;
endfunction: SetStatus
endclass: extndPacket
program main;
// Array of Type Extended Packet
extndPacket epkt [3:0];
initial begin
Packet pkt;
foreach (epkt[i]) begin
epkt[i] = new;
epkt[i].cmd = IDLE;
epkt[i].SetStatus(1);
$display("**********************************************");
$display("Extended Packet epkt[%0d] Status Value is: %0d & Command value is:%s
Error Bit = %b", i, epkt[i].status, epkt[i].cmd, epkt[i].errBit);
end
// epkt[0] handle is copied to the pkt Base Class variable.
pkt = epkt[0];
pkt.cmd = RUN;
pkt.SetStatus(1);
$display("Base Packet variable holding Extended Handle pkt.status=%0d, pkt.cmd=%s",
pkt.status, pkt.cmd);
$display("***************************************\n");
// Now since we want to access errBit which not part of pkt Object.Performing
// Downcasting to allocate the Handle back to epkt[0] variable.
if ($cast(epkt[0],pkt))
$display("Extended Class Variable holding the Handle epkt[0].status=%0d,
epkt[0].cmd=%s Error Bit = %b", epkt[0].status, epkt[0].cmd, epkt[0].errBit);
end
endprogram
In the above example code, we have a Base Class i.e. Packet, and an extended Class i.e. extndPacket. Packet contains 3 Properties and 1 Method. Inside extndPacket, there is 1 new Property and 1 new Method i.e. errBit & ShowError. One Method is overridden i.e. SetStatus(). Inside the program’s initial block, 4 array elements are constructed. Values for their Properties are set and displayed using $display. Next, we assigned the Handle of epkt[0] to the Base Class variable pkt. Here pkt.SetStatus() with some argument value is called and pkt.cmd is assigned with a RUN value. $display is again used to show the generated value & observe which of the SetStatus() Method is executed. In the end, it’s shown that if we want to access the errBit Property it can not be done in the current state since the Handle is in pkt variable which does not contain the Property errBit. To achieve this goal SystemVerilog checks the compatibility & re-allocates the Handle to the E’s variable. Since this can not be done directly, Dynamic $cast function (aka Down Casting) is used with-in ‘if‘ construct and if successful $display shows the resultant values.