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_voiddown touvm_component - Why
uvm_report_objectsits betweenuvm_objectanduvm_component - The difference between the object branch and the component branch of the hierarchy
- Why
uvm_transactionis deprecated in favor ofuvm_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, andrun_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
nullautomatically becomes a child ofuvm_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_topis a global variable, UVM's reporting macros are reachable even from code that is not auvm_componentat 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_voidis an empty class that only exists to anchor the type hierarchyuvm_objectis the base for every data and hierarchical class, providingprint,copy,compare, andrecorduvm_report_objectextendsuvm_objectand gives any class access to`uvm_info/`uvm_warning/`uvm_errorthrough the handler-and-server pipelineuvm_componentextendsuvm_report_object, adding hierarchy, phasing, recording, and factory support on top of reporting it already inheriteduvm_transactionextendsuvm_objectbut is deprecated as a direct base - user transactions should extenduvm_sequence_iteminsteaduvm_root, accessed through the globaluvm_top, is the implicit top-level component that drives phasing and global reporting configuration for the whole environment