Build Your First Testbench defined reg_block and reg_if - the DUT and the wires connecting to it. Everything from here on is UVM code, and almost every UVM class you'll write traces back to one of two base classes. This article covers the first one, uvm_object, which is the foundation for any class whose job is to represent data.
6 min read | Intermediate Level
What You'll Learn
- What
uvm_objectis and which UVM classes are built on it - The difference between
new()andcreate(), and why it matters - How the
uvm_object_utilsmacro registers a class with the factory - Where the deeper utility methods - print, copy, compare - are covered
What Is uvm_object?
All data and hierarchical classes in a UVM environment are derived from uvm_object. Its job is to define a set of common utility functions - printing, copying, comparing, and recording - that any class built on top of it gets for free, instead of every class author writing that logic from scratch.
Classes built on uvm_object fall into two groups. Plain data - transactions, configuration objects, results - extends uvm_object directly or through uvm_sequence_item, which Defining the Transaction Class uses. Anything that needs a fixed place in the testbench, like a driver or monitor, extends uvm_component instead, covered in UVM Component. Either way, both paths inherit what uvm_object provides.
The Class Definition
Here's the shape of uvm_object itself, simplified to the parts that matter right now:
virtual class uvm_object extends uvm_void;
// Creates a new uvm_object with the given name, empty by default
function new(string name="");
// Utility functions
function void print (uvm_printer printer=null);
function void copy (uvm_object rhs, uvm_copier copier=null);
function bit compare (uvm_object rhs, uvm_comparer comparer=null);
function void record (uvm_recorder recorder=null);
...
// These two functions have to be redefined by child classes
virtual function uvm_object create (string name=""); return null; endfunction
virtual function string get_type_name (); return ""; endfunction
endclass
Two things stand out. First, the constructor takes just one argument - a name, with an empty default. Compare that to a component's constructor, which always needs a name and a parent; a uvm_object doesn't have a parent, because it isn't part of any fixed hierarchy.
Second, create and get_type_name are marked virtual and return empty/null by default - they exist to be overridden by every class that extends uvm_object. In practice, you won't write these overrides by hand; the factory macro covered next generates them for you. They matter because of what they enable.
new() vs. create()
It's tempting to think of create() as just another name for the constructor, but they do different jobs. new() is the plain SystemVerilog constructor - call it, and you get an object of exactly the type you asked for, no exceptions. create() is a UVM factory method: it asks the factory for an object of a given type, and the factory is free to hand back an object of a different, compatible type instead, if a test has configured an override.
class my_object extends uvm_object;
...
// Implementation : Create an object of the new class type and return it
virtual function uvm_object create(string name="my_object");
my_object obj = new(name);
return obj;
endfunction
endclass
Notice that create()'s own implementation still calls new() underneath - the factory doesn't replace SystemVerilog's normal object construction, it just decides which type's new() actually gets called. get_type_name() supports this by giving every object a way to report its own class name as a string, which the factory and debugging tools both rely on:
class my_object extends uvm_object;
// This static method is used to access via scope operator ::
// without having to create an object of the class
static function string type_name();
return "my_object";
endfunction
virtual function string get_type_name();
return type_name;
endfunction
endclass
You won't typically write either of these functions by hand. The macro in the next section generates both automatically, in their correct form, for any class that uses it.
Registering with the Factory
None of this works automatically just because a class extends uvm_object. UVM's factory has to know about a class before it can create instances of it on request, and that registration has to be explicit:
// An object derived from uvm_object by itself does not get
// registered with the UVM factory unless the macro is called
// within the class definition
class my_object extends uvm_object;
// Register current object type with the factory
`uvm_object_utils (my_object)
...
endclass
uvm_object_utils(my_object) is what generates the create() and get_type_name() overrides shown above, and is also what makes my_object::type_id::create(...) available as a call. Without this macro, a class is still a valid SystemVerilog class, but it's invisible to the factory - it can never be substituted, and the convenience methods you'll use constantly never get wired up.
This is the minimal form of the macro - just factory registration, nothing else. There's also an extended form, uvm_object_utils_begin/uvm_object_utils_end, that additionally registers individual fields so they participate in print, copy, and compare automatically. Defining the Transaction Class, which builds an actual transaction with real fields, uses that extended form and explains why.
Quick Check
Q: If a class extends uvm_object but never includes uvm_object_utils, can you still create instances of it?
Show Answer
Yes - you can still call new() directly, since that's plain SystemVerilog and doesn't depend on the factory at all. What you lose is type_id::create() and everything that depends on factory registration, including the ability for a test to override that class with a derived type later.
Where the Utility Functions Are Covered
uvm_object's signature also includes print, copy, and compare - deep, recursive operations that walk every registered field of an object. Each one is substantial enough to deserve its own explanation rather than a quick summary here:
print(uvm_printer printer=null)- deep-prints an object's fields; covered in UVM Object Printcopy(uvm_object rhs, uvm_copier copier=null)- deep-copies fields from another object; covered in UVM Object Copy/Clonecompare(uvm_object rhs, uvm_comparer comparer=null)- deep-compares fields against another object; covered in UVM Object Compare
None of these three methods are virtual, so a child class can't override them directly. Each one instead calls a matching hook - do_print, do_copy, do_compare - that you can customize. The dedicated articles linked above cover exactly how.
Common Mistakes
Mistake #1: Calling new() when you meant to use the factory
Wrong: my_object obj = new("obj");
Right: my_object obj = my_object::type_id::create("obj");
Why? new() always constructs exactly the type you named, bypassing the factory entirely. If a test ever needs to substitute a derived type in place of my_object, that substitution can only happen through create().
Mistake #2: Forgetting uvm_object_utils and assuming the class still works normally
A class that extends uvm_object without the macro compiles fine and behaves correctly for plain SystemVerilog usage. The problem only shows up later, when something calls type_id::create() on it and gets an error, because the factory never learned the class exists.
Practice Exercise
Challenge: Register a Minimal Class
Write a class called status_info that extends uvm_object, has one field, a string named label, and is properly registered with the factory.
Hint
You'll need the constructor every uvm_object requires, plus the single macro that handles factory registration - no field list necessary for this exercise.
Solution
class status_info extends uvm_object;
string label;
`uvm_object_utils(status_info)
function new(string name = "status_info");
super.new(name);
endfunction
endclass
label is declared like any ordinary class property here, since this exercise doesn't ask for it to participate in automatic print/copy/compare - that's the extended macro form covered in Defining the Transaction Class.
Summary
In this article, you learned:
uvm_objectis the base class for data and hierarchical classes, providing print, copy, compare, and record- Its constructor takes only a name, unlike a component's constructor, since a
uvm_objecthas no parent new()always constructs the exact type requested;create()asks the factory, which can substitute a derived typeuvm_object_utilsregisters a class with the factory, generatingcreate()andget_type_name()for you
uvm_object is the parent class behind most of what this tutorial builds - including uvm_sequence_item, which the transaction class extends, and uvm_component, which testbench structure builds on. Just as often, though, you'll see it extended directly for plain data with no place in the hierarchy, like configuration or response objects.
Going Deeper
- Copying and cloning objects: UVM Object Copy/Clone
- Printing objects: UVM Object Print
- Field macros in full: UVM Utility & Field Macros