The first thing that we think about is how to print out messages. In SystemVerilog, we had a very simple method to print out messages to the console.
$display ("%t addr = 0x%0h data = 0x%0h", addr, data);
The problem here is how to distinguish if a message is informational, debug, warning, error or fatal. Also, it's difficult to maintain consistent format across different files. The reporting classes provide a facility for issuing reports with consistent formatting. Users can configure what actions to take and what files to send output to based on report severity, ID, or both severity and ID. Users can also filter messages based on their verbosity settings.

Most projects try to re-use verification components that are already developed. But, inorder for them to just plug-in an existing piece of code, it should be compatible with the current testbench set-up. Let's say you have written a driver class that can only drive signals to follow PCI Express, then you cannot use the same class to drive signals to follow AXI protocol. To make the driver class re-usable, you probably need to have a function that can accept an argument that specifies the protocol the driver needs to follow. That's how you make it re-usable in simple terms. The main component of any testbench is the data/transaction that all verification components use to send and receive from DUV.

Modeling Data Items for Generation

Every user defined data item must be derived directly or in-directly from uvm_sequence_item.

What is UVM ?

SystemVerilog is a language just like Verilog and has its own constructs, syntax and features, but UVM is a framework of SystemVerilog classes from which fully functional testbenches can be built. There's only one prerequisite to learn UVM, and that is SystemVerilog because it is the foundation for the tower that is UVM.

Click here to refresh concepts in SystemVerilog !

Why do we need UVM ?

The primary advantage is that the methodology specifies and lays out a set of guidelines to be followed for creation of verification testbenches. This will ensure testbench uniformity between different verification teams, cross-compatability between IP and standalone environment integration, flexibility and ease of maintaining testbenches.

For example, there can be many different ways to implement display messages and control verbosity with different settings such as warning, error and debug. In UVM, the underlying reporting mechanism has been standardized and made available so that engineers can instead focus on the most important part of their job which is design verification. Another example is that the sequencer-driver handshake mechanism is taken care of under the hood so that only stimulus needs to be written. This saves quite a lot of time in setting up a testbench structure since the foundation itself is well defined.

How does UVM help ?

Every verification testbench has a few key components like drivers, monitors, stimulus generators, and scoreboards. UVM provides a base class for each of these components with standardized functions to instantiate, connect and build the testbench environment. These are static entities called components in a verification environment that exist throughout a simulation just like buildings in a city. These components operate and process on some kind of data that flows around the environment similar to people and vehicles in the city. The data or transactions are called objects or sequence items since they appear and disappear at various times in the simulation and is more dynamic in nature.

What is the UVM class hierarchy ?

UVM provides a set of base classes from which more complex classes can be built by inheritance and adding onto it certain functions required for verification environment. For example, a new driver class for Wishbone protocol can be built by extending from the UVM base class uvm_driver. Stimulus for the protocol can be written by extending from uvm_sequence_item. How this sequence is built, and handed over to the driver is taken care of internally by the UVM framework.

uvm_void is the base of all classes, but it is primarily empty. uvm_object is the main class in which common functions to print, copy, and compare two objects of the same class are defined.

UVM Class Hierarchy

There are two branches in the hierarchy. The first one contains classes that define verification components like driver, monitor and the rest shown in the diagram as everything underneath uvm_report_object. The second one defines data objects consumed and operated upon by verification components shown in the diagram as everything underneath uvm_transaction.

Major UVM class categories

UVM Objects

The idea behind UVM is to enhance flexibility and reuse code so that the same testbench can be configured in different ways to build different components, and provide different stimulus. These new user defined configuration classes are recommended to be derived from uvm_object. For example, a configuration class object can be built to have certain settings that define how the testbench environment has to be built.

UVM Sequence

UVM also introduces the concept of a sequence which is nothing but a container for the actual stimulus to the design. If you put different stimuli into different sequences, it enhances the ability to reuse and drive these sequences in random order to get more coverage and verification results. All new user defined stimulus classes are recommended to be inherited from uvm_sequence.

Each sequence can be used inside other sequences to create different scenarios. For example, individual sequences can be created, one each for "read" and "write" transactions. They can be used in a random pattern in some other sequence to perform R->W->R->W, or R->R->R->W, W->W->R->R and other similar patterns.

UVM Sequence Items

Data objects that have to be driven to DUT are generally called as sequence items and are recommended to be inherited from uvm_sequence_item. For example, a sequence item class can be defined for an APB transaction that defines what the address, write/read data and access type should be and send it to an APB driver to drive the transaction to DUT.

UVM Components

All major testbench components are derived from the corresponding base class. For example, all new user defined driver classes are recommended to be inherited from uvm_driver and monitor classes from uvm_monitor and so on. A brief description of what each UVM component does is given in the table below.

Component Purpose
uvm_driver Drive signals to DUT
uvm_monitor Monitor signals at DUT output port
uvm_sequencer Create different test patterns
uvm_agent Contains the Sequencer, Driver and Monitor
uvm_env Contains all other verification components
uvm_scoreboard Checker that determines if test Passed/Failed
uvm_subscriber Subscribes to activities of other components

Register Layer

Digital designs support control registers that can be configured by software, and this has been very challenging in a SystemVerilog testbench, because for each project you had to build a separate set of classes that can store and configure these registers. UVM has an extensive set of classes to make that task relatively simpler and belong to something known as a register model. This is very useful in the verification of IPs that can be reused in various SoC designs.

TLM Connections

Another really smart feature is the use of TLM from System C. TLM helps to send data between components in the form of transactions and class objects. It also brings a way to broadcast a packet to its listeners without having to create specific channels and attach to it.

UVM Phases

Another main feature that verification components inherit from their parent class uvm_component is Phasing. This enables every component to sync with each other before proceeding to the next phase. Phases are covered in the next chapter. Every component goes through the build phase where it gets instantiated, connects with each other during the connect phase, consumes simulation time during the run phase and stops together in the final phase.

Recap on major classes

Class Description
uvm_object Define methods for common operations like copy, compare and print. Typically used to build testbench and testcase configurations.
uvm_component All testbench components like driver, monitor, scoreboards, etc are indirectly derived from this class
uvm_sequence_item All sequence items that need to be sent to a driver to be driven onto the bus are extended from this class
uvm_sequence All sequences that define the stimulus or testcase are extended from this class

SystemVerilog provides support for parallel or concurrent threads through fork join construct. Multiple procedural blocks can be spawned off at the same time using fork and join. There are variations to fork join that allow the main thread to continue executing rest of the statements based on when child threads finish.


		// Thread 1
		// Thread 2
		// ...
		// Thread 3