Logo

UVM Object

09 Mar 2024
6 mins

So far, we have learnt about the basics of UVM and seen various UVM concepts which we use frequently in UVM testbench. In this article we will learn more about UVM objects and their implementation. We will see various methods which are provided by UVM object which help in various basics task. We will also see how to create a custom class using uvm_object and how to register it with the UVM factory.

What is uvm_object?

uvm_object is the base class in UVM from which all other UVM components and objects are derived. Uvm_object itself is derived from uvm_void void. It provides a set of common utility functions like print, copy, compare, and record that any class in a UVM testbench can use.

Defining Custom Classes Using UVM Objects

Defining a custom class includes 3 steps:

  • Creating a custom child class of uvm_object
  • Registering the custom class to uvm factory
  • Defining the constructor of child class

Creating class

Creating a child class is like what we have seen in System Verilog

class xyz extends uvm_object

This creates a class xyz which is a child of uvm_object. As we know uvm_object is not available throughout the simulation time. Objects of this class can be created anytime in the simulation and can be destroyed before the simulation is completed.

Registering with uvm factory

All custom classes in UVM are registered with UVM factory so that the object creation can be handled by UVM factory. UVM provides macros which are used to register custom class to UVM factory.

Macros are code snippets which are written using `define compiler directive. When we use a macro, the macro name is replaced with the code snippet while compiling the code.
`uvm_object_utils(xyz)`

The above code snippet registers the class xyz with uvm factory. Now, we can use the create function to create new objects for this class. Also, we can use various functionalities offered by UVM factory.

Defining the constructor

To define a constructor, we need to define a new method. The constructor of uvm_object has an argument name which needs to be passed while creating an object. This name is used to identify the objects of the child class in logs.

function new(string name = “xyz”);
    super.new(name)
endfunction

The overall custom class definition will look like

class xyz extends uvm_object;
    `uvm_object_utils(xyz)
    function new(string name = “xyz”);
        super.new(name)
    endfunction
endclass

In-built utility functions

UVM object provides a set of utility functions which can be used in test bench to perform tasks like copy, compare, clone, print etc. Let us the see the functions available:

  • create – this is a static function which is used to create objects of UVM custom class. This will create an object and return the handle.
  • copy – this function make copies the specified object into this object.
  • clone – this function creates a new object of the class and copies the specified object into this newly created object and returns the handle.
  • print – this function prints all the members of the class using the given printer arg. If no arg is passed, uvm_default_printer which is available globally is used. We will learn about the printers on later article. For most of the cases default printer works fine.
  • compare – compare all the member of this object with the specified objects and return 1 if all the members are matching else return 0.
  • record – this method records the object’s properties using the recorder class.
  • pack – this method packs the members into a stream of bits. This simply means that the members are concatenated bitwise to create packed array of bits.
  • unpack – this method extracts the object’s member from the stream of bit. It can be considered as reverse of pack method.
  • convert2string – this method needs to be overridden in the child class. This method can be called to get a string for the object which can be used to do in-line prints. The logic to covert object into string depends on the user.

User-definable hook methods in UVM object

UVM provides various hook-up methods which can be overridden in the custom derived classes. These methods are called from the in-built methods which we discussed earlier. These methods help custom logic for any of the in-built methods making it more useable.

  • do_copy – this method is called whenever copy(), clone() methods are used. This can be overridden in derived class to add custom logic while copying objects.
  • do_compare – this method is called by the compare() method.
  • do_print – this method is called by the print(), sprint() method.
  • do_record – this method is called by record() method.
  • do_pack – this method is called by pack() method.
  • do_unpack - this method is called by unpack() method.

How to use in-built methods?

We have seen that most of the in-built methods can be used to process the members of defined in the custom derived class. But by default, the in-built methods do not know about the members of the custom derived class.

UVM field macros

To expose the members to the in-built functions UVM provides one more macro that we need to use while defining our custom class. These are known as UVM field macros and written as uvm_field_* where * can be replaced with the data type. For example, for bit, byte, int we can use uvm_field_int()

Below are some of the uvm field macros available:

  • `uvm_field_enum
  • `uvm_field_int
  • `uvm_field_queue_int
  • `uvm_field_object
  • `uvm_field_string

UVM field macros have two arguments:

  • ARG – this is the member’s name, The data type of the member variable passed in this argument should be compatible with the field macro being used.
  • FLAG – this argument is used to specify that the member passed in ARG should be part of which all in-built methods. For example, if UVM_NOCOMPARE is passed as FLAG, then this member will exclude from compare method and it will not affect the output of the method.
`uvm_field_enum is an exception here as it has three arguments. The first argument in this case is the enum type of the member variable.

Syntax:

`uvm_object_utils_begin(xyz)
    `uvm_field_int(var_a, UVM_ALL_ON)
    `uvm_field_string(var_b, UVM_NOCOMPARE)
`uvm_object_utils_end
Do note that when we want to use UVM field macros we need to use`uvm_object_utils_begin() instead of `uvm_object_utils().
Whenever we use a UVM field macro they are replaced with in-line codes which can affect the performance of the code when lots of members are present. Hence, it is not recommended to use these macros in complex test bench as these can drastically impact the performance. Instead we can use the hook-up methods to write custom logic but indeed that increases the implementation time as we need to write the hook-up functions for all the classes.

Different flags for UVM field macros

Below table shows the various options that can be passed to the FLAG argument of UVM field macros. It is possible to pass single or multiple options in that arg. To pass multiple options we need to OR them will passing. For example - UVM_DEFAULT | UVM_REFERENCE

ConstantUse
UVM_ALL_ONSet all operations on.
UVM_DEFAULTThis is the recommended set of flags to pass to the field macros. Currently, it enables all the operations, making it functionally identical to UVM_ALL_ON. In the future however, more flags could be added with a recommended default value of off.
UVM_NOCOPYDo not copy this field.
UVM_NOCOMPAREDo not compare this field.
UVM_NOPRINTDo not print this field
UVM_NOPACKDo not pack or unpack this field.
UVM_REFERENCEFor object types, work only on the handle (e.g., no deep copy)

We can also set the radix for printing and recording in the FLAG argument. This is done by OR(ing) the below constants with the one present in above table.

ConstantUse
UVM_BINPrint / record the field in binary
UVM_DECPrint / record the field in decimal
UVM_UNSIGNEDPrint / record the field in unsigned decimal
UVM_OCTPrint / record the field in octal (base 8).
UVM_HEXPrint / record the field in hexadecimal (base-16)
UVM_STRINGPrint / record the field in string format.
UVM_TIMEPrint / record the field in time format
By default, radix is set to HEX and thus all prints and records will be done in hex.

Code Example

Let us see a working code example and understand how the various pieces come together to create a custom object class. In this example we will create an object class test_cfg which will have various members inside. We will see how the UVM field macros are used and how to implement hook-up methods.

import uvm_pkg::*;
`include "uvm_macros.svh"

typedef enum int { IDLE, WAIT, ACTIVE, COMPLETE } state_e;

class test_cfg extends uvm_object;
    //  Group: Variables
    int no_devices;
    rand bit [4:0] no_ports;
    bit has_scoreboard;
    state_e start_state = IDLE;
    int wait_time;
    string test_cfg_name = "default_test_cfg";

    `uvm_object_utils_begin(test_cfg)
        `uvm_field_int(no_devices, UVM_ALL_ON | UVM_UNSIGNED)
        `uvm_field_int(no_ports, UVM_NOCOPY)
        `uvm_field_enum(state_e, start_state, UVM_ALL_ON)
        `uvm_field_int(has_scoreboard, UVM_ALL_ON | UVM_BIN)
    `uvm_object_utils_end

    //  Constructor: new
    function new(string name = "test_cfg");
        super.new(name);
    endfunction: new

    //  Group: Functions
    function void do_print(uvm_printer printer); 
        `uvm_info(get_name(), "printing from do_print()", UVM_LOW)
        `uvm_info(get_name(), $sformatf("wait_time = %0d", wait_time), UVM_LOW)
    endfunction: do_print

    function string convert2string(); 
       string s;
       s = $sformatf("no_devices: %0d\tno_ports: %0d\thas_scoreboard: %0d\tstart_state: %0s\twait_time: %0d", no_devices, no_ports, has_scoreboard, start_state.name(), wait_time);
       return s;
    endfunction
endclass: test_cfg

module top();
    test_cfg cfg, cfg2, cfg3;
    bit packed_val[];
    initial begin
        cfg = test_cfg::type_id::create("cfg");
        cfg.no_devices = 10;
        cfg.no_ports = 2;
        cfg.has_scoreboard = 1;
        cfg.start_state = ACTIVE;
        cfg.wait_time = 100;
        cfg.print();

        #20;
        cfg2 = new("cfg2");
        cfg2.copy(cfg); 
        cfg2.print(); 

        // clone returns a new object of uvm_object type.
        // This needs to be typecasted to the test_cfg type
        $cast(cfg3, cfg2.clone()); 
        $display("\ncfg3 data is %s", cfg3.convert2string()); 

        if(cfg3.compare(cfg2)) 
            `uvm_info("top", "cfg3 and cfg2 are same", UVM_LOW)
        else
            `uvm_info("top", "cfg3 and cfg2 are different", UVM_LOW)

        if(!cfg3.pack(packed_val)) 
            `uvm_error("top", "packing failed")
        else begin
            `uvm_info("top", "packing successful", UVM_LOW)
            $display("packed_val = %p", packed_val);
        end
    end
endmodule
Output
# UVM_INFO uvm_object_intro.sv(30) @ 0: reporter [cfg] printing from do_print()
# UVM_INFO uvm_object_intro.sv(31) @ 0: reporter [cfg] wait_time = 100
# ----------------------------------------
# Name              Type      Size  Value 
# ----------------------------------------
# cfg               test_cfg  -     @457  
#   no_devices      integral  32    'd10  
#   no_ports        integral  5     'h2   
#   start_state     state_e   32    ACTIVE
#   has_scoreboard  integral  1     'b1   
# ----------------------------------------
# UVM_INFO uvm_object_intro.sv(30) @ 20: reporter [cfg2] printing from do_print()
# UVM_INFO uvm_object_intro.sv(31) @ 20: reporter [cfg2] wait_time = 0
# ----------------------------------------
# Name              Type      Size  Value 
# ----------------------------------------
# cfg2              test_cfg  -     @458  
#   no_devices      integral  32    'd10  
#   no_ports        integral  5     'h0   
#   start_state     state_e   32    ACTIVE
#   has_scoreboard  integral  1     'b1   
# ----------------------------------------
# 
# cfg3 data is no_devices: 10	no_ports: 0	has_scoreboard: 1	start_state: ACTIVE	wait_time: 0
# UVM_INFO uvm_object_intro.sv(65) @ 20: reporter [top] cfg3 and cfg2 are same
# UVM_INFO uvm_object_intro.sv(72) @ 20: reporter [top] packing successful
# packed_val = '{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}
Try this code in EDA Playground

Observations

Following observations can be made from our example:

  • The print() method only prints the members which were exposed using uvm field macros using appropriate flag. Thus, we see that wait_time is not part of any print.
  • do_print() method was overridden through which we print the wait_time
  • no_ports was not included in the copy and thus the value for this member was not copied when we used copy/clone method.
  • Clone returns object of uvm_object class and this needs to be typecasted to our custom class.
  • The first argument for the pack() method is a ref port, which means it is pass by reference argument.
We need to import uvm_pkg in our code to access all the uvm base classes and functionalities. As macros cannot be imported, we need to includeuvm_macros.svh to use all the UVM macros.
In our example, we have used $display as well to print some of the information. This was used to show that $display can also be used in UVM. But it is not recommended to use $display as this does not follow UVM verbosity and thus can pollute the logs with unnecessary prints.

Conclusion

This article covers the basics of UVM objects, how to define a custom class, and introduces some of the essential macros and built-in methods. We also went through an example to understand how the actual UVM code looks like. In the next article we will learn UVM components in detail.