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_object is and which UVM classes are built on it
  • The difference between new() and create(), and why it matters
  • How the uvm_object_utils macro 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 Print
  • copy(uvm_object rhs, uvm_copier copier=null) - deep-copies fields from another object; covered in UVM Object Copy/Clone
  • compare(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_object is 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_object has no parent
  • new() always constructs the exact type requested; create() asks the factory, which can substitute a derived type
  • uvm_object_utils registers a class with the factory, generating create() and get_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

Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5