Class and objects in SV
In our previous article we saw the basic concept of OOPs. Now from this article onwards we will implement those concepts in System Verilog and will learn how those concepts are applied in System Verilog. We will start from classes and objects and slowly explore all the OOPs concept.
Class encapsulates properties and methods in a single entity. In System Verilog all classes are dynamic in nature by default. So, classes are how the encapsulation property is implemented in OOPs.
In System Verilog we use
class keyword to declare a class and
endclass keyword to end the class scope.
class abc <body of class> endclass
A class will define all the properties and methods that will be present in the object. Let’s see this with an example. There is a class which is a blueprint of car. A car can have some unique properties like license plate number, registration number and some not unique property like colour, transmission type, etc. Now when user shifts gear it needs to perform some tasks which would be common across all the object of the class.
class abc <properties> <methods> endclass
Objects are a unique entity of the class. They are created dynamically whenever needed and destroyed when their need is over. Just like a car has some expiry date after which it is destroyed.
<class name> <object name>; <object name> = new();
First line where we declare the object name, also acts a pointer which points to the memory where this is object is created. So, in first when where we just declare the name, we are just creating a handle to store the pointer. Currently it will not point to anything and thus would be null.
In second line, we are creating a new object of class and memory is allocated to the object. The memory address is stored in the object handle which we created in previous line.
If we directly print the object without using any dot operator we will get some random number, which is nothing but the pointer to the object, i.e., address of the created object.
car car1 = new(); car1.license = 12423; car1.color = “red”; car1.transmission = MANUAL; car1.change_gear(2);
This is how we can use objects to change different properties of the objects and call different methods present inside class.
Constructors are in-built methods which are called to create a new object. Basically, when we use
new() to create a new class this acts a constructor.
Default constructor will just make the object and assign the properties the default value of the data type. Sometimes we want to create an object by passing some of the properties, for ex, we want to pass the colour of the car while making the object. For this, we can create a constructor and that will be called automatically when we want to create a new object.
As we have seen that constructors are nothing a method with name, thus we can create a method with name
new to override the default behaviour.
class car; string color; function new(string color1); color = color1; endfunction endclass
In highlighted line we assign the argument passed while object creation to the variable declared inside class.
Also, now every time we want to create a new object, we need to pass the
color1 arg. If we want to provide default value of arg we can do as well just like we do in normal methods.
Constructors are special method used to create objects so unlike a normal method, constructor don’t have a return type. In example mentioned above please note there is no return type for function new.
These are just the opposite of constructor and are called when we want to destroy an object. In SV, we can’t manually destroy objects and thus destructors are not present in System Verilog.
Objects are destroyed in system Verilog by automated garbage collector which will destroy objects which are not use, i.e, null.
this keyword represents the current object. This can be used to specifically point to properties which belong to the current object.
In our previous constructor example
class car; string color function new(string color); this.color = color; endfunction endclass
In this example, the argument name for new method is color and the class property name is also color. Now, in this case compiler can get confused as to which one is argument and which one is class property inside new function. Thus, we use this keyword to specifics that assign the value of color argument to the color property of this object.
class packet; bit [4:0] length; bit [15:0] addr; function new(bit [15:0] addr, bit [4:0] l=1); this.length = l; this.addr = addr; endfunction: new function void print_packet(); $display("addr = 0x%0h", addr); $display("lenght = %0d", length); endfunction: print_packet endclass: packet module class_ex_1; initial begin packet pkt1, pkt2; pkt1 = new('ha4a4,10); pkt1.print_packet(); pkt2 = new('hb623); pkt2.length = 22; pkt2.print_packet(); end endmodule
Try this code in EDA Playground
# addr = 0xa4a4 # lenght = 10 # addr = 0xb623 # lenght = 22
In previous article we saw about the static and dynamic property of objects in OOP. This concept is also applicable for the methods declared inside a class. In System Verilog, all methods defined inside a class is automatic, i.e., dynamic in nature.
What does this mean??
When we define a method, we might use some variables inside the method, or even arguments. These variables would require some space in memory. When a method is automatic in nature these variables won’t be allocated memory when we create an object but rather when the methods are called. Once the method call is over the memory is released.
We have sent that by default methods are automatic in nature for dynamic classes. But we can explicitly make a method dynamic by using
automatic keyword. This is also used with some variable inside method.
If we have a
fork_join inside a for loop and we are using the loop variable inside
fork_join then changes made by any one of the processes to that loop variable will be reflected on all other processes. But if we declare that variable as automatic, then all process will have individual memory space for that variable and thus, changes in variable won't be reflected on other processes.
module test_1; initial begin for (int i = 0; i<3; i++) begin fork $display("i = %0d", i); join_none end end endmodule module test_2; initial begin for (int i = 0; i<3; i++) begin fork automatic int k = i; $display("k = %0d", k); join_none end end endmodule
test_2, we have use automatic keyword and storing
i into this variable. Thus, the value of
k will be different in different processes, and we see prints with different value of
Static keyword is used to make a method or class static in nature. Static methods would share it internal variables between different method calls. Thus, if we set som internal value of the method to some value, it will be reflected on all other method calls.
Similarly, we can use static keyword for class as well. This makes class of static nature and thus can be used without making objects.
In this example we will see use automatic and static keyword in our System Verilog code.
class Packet2; int a = 0; endclass class Packet; bit [15:0] addr; bit [7:0] data; int ctr = 0; static int static_ctr = 0; static Packet2 pkt = new(); function new (bit [15:0] ad, bit [7:0] d); this.addr = ad; this.data = d; this.static_ctr++; this.ctr++; $display ("static_ctr=%0d ctr=%0d addr=0x%0h data=0x%0h pkt=%0h", static_ctr, ctr, addr, data, pkt); endfunction static function void print_no_objs(); $display ("no of objects of class packet = %0d", static_ctr); endfunction endclass module tb; initial begin Packet p1, p2, p3; p1 = new (16'hdead, 8'h12); p2 = new (16'hface, 8'hab); p3 = new (16'hcafe, 8'hfc); p1.print_no_objs(); end endmodule
Try this code in EDA Playground
# static_ctr=1 ctr=1 addr=0xdead data=0x12 pkt=10002 # static_ctr=2 ctr=1 addr=0xface data=0xab pkt=10002 # static_ctr=3 ctr=1 addr=0xcafe data=0xfc pkt=10002 # no of objects of class packet = 3
In above example, we see that we have used an object of another class
Packet2 inside class
Packet. Object of class
Packet2 is static in nature, thus it can be observed that the object handle
Packet class points to same address and no new object is created for each new object of
static_ctr is static in nature, the value is shared across the objects and thus whenever we are creating a new object, it will increment the value from previous value.
Class forms the basis of complex test benches and thus, the concepts in this article will help in writing some complex test benches with lots of components. Classes and objects can be a tricky thing to get hold off and thus it is recommended to try to write some classes and do experiments with them. In the next article we will be how a child class can be created through inheritance.