Verification Class Categories

Introduction

What can you describe with Object-Oriented Programming? When I learned OOP, we had cute classes like animals, cars, and houses. That would be fine if I was taking a C++ or Java course, but this is SystemVerilog, and I am verifying hardware designs. What should my classes represent? This post shows two major categories. It also covers concepts that confused me when I first learned UVM, like sequence vs. sequencer.

This is the second post in a series. Here is the first post on Class Variables and Objects.

The UVM Way

UVM testbenches have two major groups of classes: transactions and components, plus minor utilities. All UVM classes are derived from uvm_object. In my last post, I recommended that you give every SystemVerilog object either a unique ID or name. uvm_object has both as seen in its constructor.

class uvm_object;
  function new(string name);
    m_inst_id = m_inst_count++;
    m_leaf_name = name;
  endfunction
endclass

This code follows the convention that member variables start with the prefix of “m_”.

UVM Topology

In the following diagram, all the boxes are components: test, environment, agent, monitor, driver, sequencer, and scoreboard. The gold dots are transactions that are generated in the gold sequence and flow to the driver, or are sampled in the monitor and sent to the scoreboard.

UVM Topology with components and transactions
UVM Topology with components and transactions

The OOP term “hierarchy” refers to the tree of base classes and their extensions, an is-a relationship. The UVM term “hierarchy” is a has-a relationship, like an agent has-a driver. To avoid confusion, I call this relationship the UVM “topology”.

UVM components have a fixed topology. A driver is always inside an agent, so UVM says that the driver’s parent is the agent. The agent’s parent is an environment, whose parent is the test.

You can see this in the constructor for the base component class which passes the name up to uvm_object and saves a copy of the parent handle. Neither the name or parent need a default.

class uvm_component extends uvm_object;
  function new(string name, uvm_component parent);
    super.new(name);
    m_parent = parent;
  endfunction
endclass

Transactions have no fixed location, flowing from one component to the next. You can’t verify a design with a single transaction, so UVM groups one or more of them into a sequence. This is why transactions in UVM are derived from uvm_sequence_item. This class is derived from uvm_object, and its constructor does not have a parent argument. However the name must have a default value.

class uvm_sequence_item extends uvm_object;
  function new(string name=”uvm_sequence_item”);
    super.new(name);
  endfunction
endclass

Life in the Tree of Components

Another dimension of your testbench is time. When are these objects constructed and then garbage collected? When you start simulation, UVM builds the components from the top down, from the test down to the driver and monitor. These objects exist for the entire simulation – you wouldn’t want the driver to get garbage collected halfway through the test! I call component objects “permanent”.

After the components are built and connected, UVM runs your test. Hundreds or thousands of transactions are created in sequences, flow through the testbench into the design, or captured by the monitor and sent to the scoreboard. When no longer needed, the objects are garbage collected. Transactions are very dynamic.

Who’s Jiving Who?

The final big difference between these two types is their focus. Transactions are passive containers of properties such as address and data. The have functions to print, copy, and compare the properties. This pseudo-UVM Packet class gives you the flavor. The calcCsm function calculates the checksum (csm) based on the src, dst, and data properties.

The Packet class with functions, and a Packet class variable

Components are active, operating on transactions. (Technically, they have class variables with handles to the transaction objects.)  Components have tasks to send and receive transactions. You would never print, copy, clone, or compare a component.

The Packet_Driver class with run_phase task, and a class variable

Summary: UVM Categories

The following table compares the two major categories of classes in UVM, with examples based on the above code.

WhatTransactionsComponents
UVM TopologyNo fixed locationFixed location
Constructorfunction new(name=”default”)function new(name, parent)
Factory macro`uvm_object_utils(class_name)`uvm_component_utils(class_name)
create callpkt = packet::type_id::create(“pkt”)drv = packet_driver::type_id::create(“driver”, this)
LifetimeShortPermanent
active/passivePassive: method operate on itselfActive: methods operate on transaction objects
MethodsFunctions to print, copy, clone, compare, this object’s propertiesTasks such as run_phase() and functions that operate on transactions
Comparison between UVM transaction and component classes

Final Note

This post goes over many concepts that confused me when I first learned UVM, such as why some constructors have one argument, and other have two. Another issue: what is the difference between a sequence and a sequencer? Just remember that a sequencer, like a driver, and monitor, is an active component – they all end in ‘r’. Hopefully you can get up to speed faster than me!

This describes a very simple system with the minimum number of classes. The code examples left out many details. Otherwise, this post would be twice as long and twice as confusing!

Here is the next post in this series.

Leave a Reply