Inside UVM

Part one: What is the Universal Verification Methodology and how to use it effectively.


We have all been witnesses to the rapid improvements of the iPhone processor chip every year. With the iPhone 8 featuring the newest A11 Bionic at 10 nm FinFET with 4.3 billion transistors, I can’t personally fathom the amount of the verification effort needed for this type of SoC – the required manpower and time to get the job done is absolutely mind-boggling. Thankfully, we have several pre-silicon verification methodologies to help in discovering bugs prior to silicon tape-out, such as the Universal Verification Methodology (UVM). UVM has been a key factor in improving the verification accuracy and quality for today’s SoC.

UVM is an open source System Verilog library that aims to make the verification process flexible by creating reusable verification components and assembling powerful test environments using constrained random stimulus generation and functional coverage methodologies. It was developed with a view to improve/simplify test-bench creation. UVM comes with a generic structure which everybody utilizes and manipulates according to their verification needs. In this blog I will discuss the most essential features of UVM that, together, create the base of the UVM structure.

As shown below in Figure 1 the UVM structure is made up of different classes. Using the inheritance concept of OOP, one can extend the usability of the language. UVM structure is basically divided into 3 base classes.

  1. uvm_object
  2. uvm_transaction
  3. uvm_component.


  • uvm_object is basically the main class. It is the base class for all UVM data and hierarchical classes. uvm_transaction and uvm_component are also derived from uvm_object. It consists of all the basic methods, macros, identification fields in it. Classes derived from uvm_object must implement the pure virtual methods such as create and get_type_name.
  • uvm_object consists of basic methods such as create, print, copy, clone, compare, pack, unpack, etc.
  • Most of these basic methods are useful to build further basis for a particular verification goal. As shown in figure 1, uvm_object is used to write uvm_sequence_item. The uvm_object has a number of virtual methods as mentioned above, which are used to implement common data object functions. These methods are implemented to make the sequence_item purposeful.

Next, let’s consider the macros – uvm_object has different sets of macros defined in it, mainly utility macros and field macros. Utility macros provide implementations of the basic methods such as create. When you are trying to write objects without any field macros, it’s written as follows: `uvm_object_utils(type).

UVM field macros are useful typically for the implementation of methods such as copy, print, pack, unpack, etc. They are invoked inside the `uvm_object_utils_begin and `uvm_object_utils_end. The macro basically has two arguments as part of it:field and flag. Thus, the structure for uvm field macro is as follows : `uvm_field_*(field,flag).

In order to maintain continuity, I have decided to include information on class Sequence_Item. By doing so, I will be able to show how a method as described in uvm_object is typically executed.

A sequence item is used to generate the stimulus. After writing all required classes, when you finished writing the testbench, the sequence items are randomized in order to generate the stimulus which is then passed to design under test. Did I mention about randomizing the sequence items? Well then, isn’t it obvious to declare the data properties in a sequence item as ‘rand’?

Consider the diagram in Figure 1 again. The sequence item class is written by extending the uvm_sequence_item. Now uvm_sequence_item inherits from the uvm_object. Hence, it is of object type.

Below is an example that shows how a sequence item can be used to randomize the data.

Example :

class bus_trans extends uvm_sequence_item;

rand bit [3:0] addr;
rand bit write;
rand bit read;
rand bit [7:0] w_data;
bit [7:0] r_data;

`uvm_object_utils_begin(bus_trans)    — Utility and Field Macro Declaration
function new(bus_trans);                       — Constructor Declaration;

Top Level Testbench to randomize and access sequence item
module tb();
bus_trans trans_seq_0;   — Instance created
bus_trans trans_seq_1;    — Instance created
initial begin
trans_seq_0 = bus_trans :: type_id :: create(“trans_seq_0”);     —New Object of bus_trans type created.
trans_seq_0.randomize();    — trans_seq_0 data fields randomized.
trans_seq_0.print();    — Printing the trans_seq_0 data fields
$cast(trans_seq_1,trans_seq_0.clone());    — Create trans_seq_1 and copy trans_seq_0 to trans_seq_1

In the above example, I show how class bus_trans is written by extending the class uvm_sequence_item. Next, as part of top level testbench, I created objects of class bus_trans using the default methods available to use from class uvm_object and generating random stimulus by accessing data fields of the class bus_trans.

The above may sound confusing at first, but don’ worry! Practice is the key to success. There are a number of UVM-related webinars available on Aldec website. Below I have provided the link to them:

Don’t Be Afraid of UVM (UVM for Hardware Designers)
OVM and UVM – Building a SystemVerilog Testbench in Riviera-PRO

In the next blog, I will continue from where I left off. I will talk about class Sequencer and how it interacts with class uvm_sequence to generate stimulus out of sequence_item.

Leave a Reply

(Note: This name will be displayed publicly)