Hello All!, I wanted to write for SystemVerilog category since a long time but UVM Testbench Architecture category contains so many interesting topics to write about that whenever I wanted I fall-in for the Testbench category.
Anyway, this time I finally choosed to write about SystemVerilog OOP (Object Oriented Programming). Object Oriented Programming (OOP) in SystemVerilog is supported through the “Class Data type”.
SystemVerilog OOP comprises of few key concepts, these are listed below:
Creating containers of data along with the associated behaviors. Basically the code that operates on that data.
The ability to extend or override those containers with additional data and new behaviors.
Hiding the implementation details to reduce the complexity as well as raising the abstraction level.
Generic Programming (Parametrization):
The ability to write code that can be reused across wide range of applications. Its like in Verilog overriding parameters on a Module instance.
Its the simple key concept. This is the reuse of same code to take on many different behaviors based on the type of object at hand.
Further details of these SystemVerilog OOP concepts will be discussed along the way. First and the foremost, lets understand the SystemVerilog Class Data Type.
SystemVerilog Class Data Type:
SystemVerilog uses the term “Class” to define what makes up an Object. We may think Class as a short form of Classification. In the process of encapsulation, we divide things into smaller classifications. For example, We might use one class to represent an audio stream and another class to represent a video stream. Once we have a Class definition, which usually comprises of Data members and Methods, we can construct the Class Object dynamically. In fact, we always construct Classes dynamically which means they do not exist until someone calls a routine to have them constructed. Then we store its Handle in a Class Variable. Some other programming language use the term Pointer, to point to a specific area into a memory. But SystemVerilog prefer to use the term “Handle“. When we have a Handle to an Object we don’t know the value of the Handle, we just know that it refers to a specific Object & its collection of Data. SystemVerilog never permits to know the value of the Handle, we can only use it to refer an Object and its contents.
Basically, a Class is a Data type just like a Structure or the Enum type. There is no storage associated with the type. The SystemVerilog world refers the variables declared inside the Class as “Properties“. SystemVerilog also uses the keyword ‘Properties’ in Assertions which does not have any relation with Class Properties. Subroutines in Verilog i.e. Tasks & Functions are called “Methods” in a Class. Both Properties and Methods are the members of the Class and these are the basis of encapsulation.
Lets summarize these points:
- A Class defines a collection of Data members.
- Class contains variables referred as Class Properties.
- Classes contains Subroutines (Tasks/Functions) referred as Class Methods.
- Both Properties and Methods are treated as Members of the Class.
- Classes are constructed Dynamically to create Class Objects.
- A Class Variable stores Class Handle.
- A Class Objects are accessed via Class Handles.
- Class declaration does not occupy any memory, it only creates a new type.
Lets see an example Class “Packet” code:
typedef enum [IDLE, RESET, P1, ...] cmd_t; class Packet; /// Properties cmd_t command; int status; logic [7:0] data [0:255]; /// Methods function int GetStatus(); return(status); endfunction: GetStatus task SetCommand (input cmd_t X); command = X; endtask: SetCommand endclass: Packet
In the above example code, we can see, A Class is bounded with keywords class…endclass. Inside the Packet Class, Properties are declared i.e. command, status and data. Similarly Methods are also declared i.e. GetStatus and SetCommand.
Next, lets see the construction or instantiation of a Class –
Every Class has a built-in method called new() which is a must to call to create an Object of that Class type. The new() method is also known as the “Constructor“. Constructing an Object allocates the space in the memory needed to hold the Properties of an Object. We can define our own Constructor that overrides the built-in Constructor & put whatever code we want inside it. We can also define arguments to pass to the Constructor. The function new() does not have any return type. But new() method implicitly returns the Handle to an Object of the Class type i.e. Packet in the above example. We store that Handle in a Class variable i.e. my_pkt as shown in the example below:
///// Packet Class class Packet; ... endclass: Packet /// Construction of Object of Packet Class type Packet my_pkt = new;
The above code is an example of using the default Constructor method i.e. new().
///// Packet Class class Packet; ... /// User Defined Constructor function new(); command = IDLE; endfunction: new endclass: Packet /// Construction of Object of Packet Class type Packet my_pkt = new;
The above code showing a user defined constructor method and later creating the Object of Packet Class type using that user defined new() function.
Next, we’ll see an example where an argument can be passed to the Constructor method.
///// Packet Class class Packet; ... /// User Defined Constructor with input argument function new (input int Y); command = IDLE; status = Y; endfunction: new endclass: Packet /// Construction of Object of Packet Class type Packet my_pkt = new(7);
As per the above code, the Class variable “my_pkt” stores the Handle of the Object. So my_pkt will point to the chunk of memory containing the 3 Properties which are defined as part of the Class Packet.
Note that if a Class variable is declared but the Object of the Class is not yet created. In this case, the Class variable will hold a special value i.e. “null“. In SystemVerilog, Destruction/De-allocation is done automatically after an Object has no references.
Few examples related to Handle, Null and testing the Null condition is shown in the code below:
/// Variable pkt1 holds the Handle of the created Object Packet pkt1 = new; /// Variable pkt2 holds a special value "null" Packet pkt2; /// Testing of the null condition task transmit_pkt (Packet pkt); if (pkt == null) pkt = new(); transmit(pkt); endtask: transmit_pkt
Now lets talk about what goes inside of a Class. Properties of a Class can be variables of any Data type, including other Class variables. Properties begin their lifespan when constructing a Class Object. When the above given “Packet” Class is constructed, the three defined Properties i.e. ‘command‘, ‘status‘ & ‘data‘ comes into existence.
The way we access a Property is by using “Class variable name”.”Property name”
Lets see the example of accessing the Properties of Class:
/// Variable declaration of the Class Packet Packet pkt; /// Initial Block, Construct the Packet Object i.e. pkt /// Access the Properties of the Obj pkt. initial begin pkt = new(); pkt.command = P1; if (pkt.status = 9) data_out = pkt.data; end
In the above code, we can see how the Properties of an Object is accessed using the dot (.) operator.
A Class may contain Tasks & Functions which are called Methods of the Class. Tasks and Functions have the same meaning they have in Verilog. Tasks are blocking in nature and they consume time. Functions are non-blocking, runs in zero time and can return value.
Methods are accessed in the same way, as the Properties. Lets see it with the help of an example:
/// Variable declaration of the Class Packet Packet pkt; /// Integer variable declaration to hold return value int S_val; /// Initial Block, Construct the Packet Object i.e. pkt /// Access the Methods of the Obj pkt. initial begin pkt = new(); pkt.SetCommand(P1); S_val = pkt.GetStatus(); end
Class Method has an implicit argument called “this” that refers to the current Object that called the Method. Lets analyze it using following code:
class my_class; int A; function void func (input int A); this.A = A; endfunction: func endclass: my_class my_class mc = new(); mc.func(100); $display("Value of A = %0d", mc.A);
Here in this example we can see that “A” is defined as the Property of the Class “my_class” as well as “A” is also passed as an argument to the Method “func“. “this” is an implicit argument of the function “func” and it assigns the argument value to the Object’s Property which is “mc.A” in the example code above.
However, this is not a very good programming practice to have the local names same as the names of Class members. It is always better to have some prefix or suffix to differentiate among both of the names.
With this, we reach to the end of the Part 1 of the SystemVerilog OOP. In SystemVerilog OOP – Part 2, we’ll discuss further topics related to SV OOP. Do write to me if any particular OOP topic you wanted to see in upcoming Parts of SystemVerilog OOP. As always, please keep sending your inputs/suggestions/comments, those are high energy source to me. Thanks!
Till then..Take care, Bye! 🙂