Why Learn the UVM Base Classes? Every transaction, sequence and component in a UVM testbench inherits most of its behavior from a handful of library classes. Knowing what uvm_object, uvm_component and uvm_root already provide tells you what you get for free - printing, copying, hierarchy and phasing - and what you still have to write yourself.

Every driver, monitor, scoreboard, and transaction you have written so far extends either uvm_component or uvm_object. Neither of those classes is actually the top of the chain - both inherit functionality from a small set of UVM base classes that rarely get instantiated directly but quietly provide everything from factory registration to message reporting. Understanding this base class chain matters because it explains a question that confuses most people early on: why can a sequence inside a task call uvm_info just like a component can, even though sequences are not components at all?

This article maps out that hierarchy - uvm_void, uvm_object, uvm_report_object, uvm_component, and uvm_transaction - so the deep-dive articles that follow (Root, Copy/Clone, Compare, Print) make sense in context instead of as isolated facts.

6 min read | Intermediate Level

What You'll Learn

  • The full inheritance chain from uvm_void down to uvm_component
  • Why uvm_report_object sits between uvm_object and uvm_component
  • The difference between the object branch and the component branch of the hierarchy
  • Why uvm_transaction is deprecated in favor of uvm_sequence_item

The diagram above shows the two branches that matter day to day. On the left, uvm_object leads down to uvm_sequence_item - the data side, used for transactions and sequences that come and go during simulation. On the right, the same uvm_object leads through uvm_report_object down to uvm_component - the structural side, used for drivers, monitors, and environments that exist for the entire run.

uvm_void

uvm_void sits at the very top of the chain, and it is deliberately empty:


virtual class uvm_void;
endclass

It has no data members and no functions. Its only job is to act as a generic type, the same way a void pointer works in C - it lets you build a container that can hold a handle to "some UVM thing" without committing to which UVM thing it is. Classes that extend uvm_void directly (instead of uvm_object or uvm_component) get none of the UVM utility functions; they can still be stored alongside other UVM objects in a generically typed container, but that is the extent of what uvm_void buys you.

In practice, you will almost never extend uvm_void directly. It exists so that uvm_object has something to inherit from, keeping the type system consistent.

uvm_object

uvm_object is where the UVM base class library actually starts doing useful work. It is the base class for every data-carrying and hierarchical class in a UVM environment - transactions, sequences, configuration objects, and (further down the chain) components all trace back to it.


virtual class uvm_object extends uvm_void;

	extern function void print (uvm_printer printer=null);
 	extern function void copy (uvm_object rhs, uvm_copier copier=null);
  	extern function bit  compare (uvm_object rhs, uvm_comparer comparer=null);
 	extern function void record (uvm_recorder recorder=null);
	...

endclass

uvm_object defines a consistent interface for print, copy, compare, and record so that any object - regardless of what it represents - can be printed, cloned, or compared the same way. None of this is automatic; a class still needs the `uvm_object_utils macro (or hand-written do_print/do_copy/do_compare hooks) to make these methods aware of its specific fields. uvm_object just guarantees the method exists and has a predictable signature.

Read more in UVM Object.

uvm_report_object

uvm_report_object extends uvm_object, and it is the class that gives every component its reporting ability:


// A report has 'severity', 'id_string', 'text_message', and 'verbosity_level'
`uvm_info ("STAT", "Status register updated", UVM_HIGH)

// severity  		: uvm_info
// id_string 		: "STAT"
// text_message 	: "Status register updated"
// verbosity_level 	: UVM_HIGH

Calling `uvm_info, `uvm_warning, `uvm_error, or `uvm_fatal does not print anything directly. Each macro hands the message to an internal uvm_report_handler, which checks the configured verbosity and decides whether the message should be displayed at all. If it should, the message is forwarded to a single, global uvm_report_server, which formats and prints it.

That two-stage handoff is what makes verbosity filtering possible. If the maximum verbosity is set to UVM_MEDIUM, every `uvm_info call tagged UVM_HIGH is silently dropped by the handler before it ever reaches the server. This means you can litter your driver and monitor code with detailed UVM_HIGH debug messages and leave them there permanently - they simply stay quiet until someone raises the verbosity for a debug session, with no recompile needed.

Because uvm_report_object extends uvm_object rather than the other way around, reporting is a property of the object branch first. uvm_component inherits it for free further down the chain, which is why every component can call `uvm_info without any extra setup.

Read more in UVM Reporting Functions.

uvm_component

uvm_component extends uvm_report_object, which is the detail the diagram makes clear and a flat list of classes does not: every driver, monitor, agent, and environment you build is, by inheritance, also a uvm_report_object and a uvm_object. That is the entire reason a component never needs to do anything special to call `uvm_info or use copy/compare - both abilities arrive already built in from its parent classes.

uvm_component itself adds the structural machinery that turns a collection of classes into an actual testbench hierarchy:

  • Hierarchy - methods for searching and traversing the component tree, for example env0.agent0.driver0
  • Phasing - the phase callbacks every component follows, such as build_phase, connect_phase, and run_phase
  • Reporting - inherited directly from uvm_report_object, so messages and errors flow through the same handler/server pipeline described above
  • Recording - methods to record transactions a component produces or consumes to a transaction database
  • Factory - an interface to uvm_factory, used to create new components and objects by type or by instance override

Read more in UVM Component.

uvm_transaction

uvm_transaction extends uvm_object and adds a timing and notification interface - timestamps and events that mark when a transaction is accepted, begun, and ended.

uvm_transaction is deprecated as a base class for user-defined transactions. Use uvm_sequence_item instead, which extends uvm_transaction and adds the fields a sequence actually needs - things like a sequence ID and a handle back to the sequencer that generated it.

In other words, you will see uvm_transaction in class hierarchy diagrams and in the UVM source, but in your own code, a transaction class should always look like this:


class my_transaction extends uvm_sequence_item;
  `uvm_object_utils(my_transaction)

  rand bit [7:0] addr;
  rand bit [31:0] data;

  function new(string name = "my_transaction");
    super.new(name);
  endfunction
endclass

not directly from uvm_transaction.

Where Is This Hierarchy Used?

Every signal-level transaction sent from a sequence to a driver, and every register read or write issued by a RAL model, is ultimately a uvm_sequence_item riding on top of uvm_object. Meanwhile every piece of structural testbench code - agents, scoreboards, environments - rides on uvm_component. Recognizing which branch a class belongs to tells you immediately whether it has a fixed place in the testbench tree or whether it is a transient piece of data passing through.

uvm_root

uvm_root is a special case: it is the implicit top-level component that UVM creates automatically the moment a test starts running. You never instantiate it yourself, but you can reach it through the global handle uvm_top:

  • Any component constructed with its parent argument set to null automatically becomes a child of uvm_top
  • It drives the phasing mechanism for every component in the testbench
  • It supports searching for components by their full hierarchical name
  • It lets you set report verbosity globally, across the entire environment
  • Because uvm_top is a global variable, UVM's reporting macros are reachable even from code that is not a uvm_component at all, such as a sequence or a free-standing module

uvm_root checks for unresolved errors at the end of the end_of_elaboration phase and raises a UVM_FATAL if it finds any, which stops the simulation before the run_phase ever starts. If your test fails before a single clock edge happens, this check is one of the first places to look.

Read more in UVM Root.

Common Mistakes

Mistake #1: Extending uvm_transaction directly for a new transaction class

Wrong: class my_txn extends uvm_transaction;
Right: class my_txn extends uvm_sequence_item;

Why? uvm_transaction is deprecated for this purpose. uvm_sequence_item already extends it and adds the sequencer-handshake fields a real transaction needs, so extending uvm_sequence_item gives you everything uvm_transaction offers plus the parts that actually make sequences work.

Mistake #2: Assuming a sequence cannot call `uvm_info because it is not a component

A sequence is not a uvm_component, but reporting is not exclusive to components - it comes from uvm_report_object, and sequences have their own access to the global reporting mechanism through uvm_top. The macro works fine inside a sequence's body() task without any extra setup.

Mistake #3: Treating uvm_void as something you should extend for "lightweight" classes

Extending uvm_void directly throws away every UVM utility - no print, no copy, no factory registration. If a class needs to interoperate with UVM in any way, extend uvm_object instead; uvm_void only exists to anchor the type hierarchy.

Quick Check

Q: A driver class can call `uvm_info without registering for reporting separately. Which base class makes this possible?

Show Answer

uvm_report_object. Since uvm_component extends uvm_report_object, every component inherits the reporting interface automatically.

Practice Exercise

Challenge: Trace the Chain

For each class below, write down the full inheritance chain back to uvm_void: uvm_driver, uvm_sequence_item, and uvm_env.

Hint

uvm_driver and uvm_env are both UVM-provided subclasses of uvm_component. uvm_sequence_item sits on the other branch, below uvm_transaction.

Solution

uvm_driver: uvm_driver → uvm_component → uvm_report_object → uvm_object → uvm_void

uvm_sequence_item: uvm_sequence_item → uvm_transaction → uvm_object → uvm_void

uvm_env: uvm_env → uvm_component → uvm_report_object → uvm_object → uvm_void

Summary

In this tutorial, you learned:

  • uvm_void is an empty class that only exists to anchor the type hierarchy
  • uvm_object is the base for every data and hierarchical class, providing print, copy, compare, and record
  • uvm_report_object extends uvm_object and gives any class access to `uvm_info/`uvm_warning/`uvm_error through the handler-and-server pipeline
  • uvm_component extends uvm_report_object, adding hierarchy, phasing, recording, and factory support on top of reporting it already inherited
  • uvm_transaction extends uvm_object but is deprecated as a direct base - user transactions should extend uvm_sequence_item instead
  • uvm_root, accessed through the global uvm_top, is the implicit top-level component that drives phasing and global reporting configuration for the whole environment
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5
Cron Job Starts