Logo

Class and objects in SV

06 Aug 2023
6 mins

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

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 declaration

class abc
    <body of class>
endclass

How class is a blueprint of object?

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

Object

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.

Object handle and declaration

<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.

How to use objects?

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 and destructors

Constructors

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.

Example
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.

Destructors

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

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.

Example 1

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
Output
# addr = 0xa4a4
# lenght = 10
# addr = 0xb623
# lenght = 22
Try this code in EDA Playground

Automatic vs Static

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.

Automatic keyword

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

In 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 k.

Static Keyword

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.

Example 2

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
Output
# 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
Try this code in EDA Playground

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 pkt in Packet class points to same address and no new object is created for each new object of Packet.

Also, as 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.

Conclusion

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.