Every class you write in a UVM testbench, from a one-field transaction to a full environment, starts with a single macro line. That line is what lets the factory build your class, what lets a test swap it for a derived version, and what gives every object a name the reports can print. Get it wrong and the class compiles fine but fails the first time anything tries to create it.

UVM utility macros register a class with the UVM factory and generate the small set of methods the factory needs, such as create() and get_type_name(). Objects use `uvm_object_utils, components use `uvm_component_utils, and parameterized classes use the _param_ versions. The _begin/_end forms also open a block for field macros, which automate copy, compare and print.

7 min read | Intermediate Level

What You'll Learn

  • Register objects and components with the factory using the correct utility macro
  • Understand what the macro generates and why constructors need a particular form
  • Handle parameterized classes with the _param_utils macros
  • Create objects through type_id::create() so they can be overridden

Syntax


`uvm_object_utils(TYPE)                 // object, no field macros
`uvm_object_utils_begin(TYPE)           // object, followed by `uvm_field_* macros
`uvm_object_utils_end

`uvm_component_utils(TYPE)              // component, no field macros
`uvm_component_utils_begin(TYPE)        // component, followed by `uvm_field_* macros
`uvm_component_utils_end

`uvm_object_param_utils(TYPE)           // parameterized object
`uvm_component_param_utils(TYPE)        // parameterized component

TYPE is the name of the class the macro sits in. Pick the macro family by the class's base: anything derived from uvm_component uses a component macro, and everything else derived from uvm_object, including sequence items and sequences, uses an object macro.

Object Utility Macro

Every class derived from uvm_object without being a component registers with `uvm_object_utils. Place the macro inside the class body, usually right after the class declaration line.


class ABC extends uvm_object;

  // Register this user-defined class with the factory
  `uvm_object_utils(ABC)

  function new(string name = "ABC");   // every argument needs a default value
    super.new(name);
  endfunction
endclass

The create() method the macro generates allocates the object by calling its constructor without arguments. That is why the name argument of an object's constructor must have a default value. Leave the default out and the class fails to compile as soon as the macro is added.

Component Utility Macro

Classes derived directly or indirectly from uvm_component register with `uvm_component_utils. Here the generated create() takes two arguments and calls a two-argument constructor, so every component needs a constructor with a name and a parent.


class DEF extends uvm_component;

  // Class derived from uvm_component, register with factory
  `uvm_component_utils(DEF)

  function new(string name = "DEF", uvm_component parent = null);
    super.new(name, parent);
  endfunction
endclass

The constructor is mandatory for a component. Unlike uvm_object, the uvm_component constructor has no default arguments, so a component class without its own new() cannot compile.

What the Macro Generates

You rarely need to read the macro source, but it helps to know what the single line adds to your class.

  • A factory proxy typedef, type_id. This is a lightweight registry class for your type. ABC::type_id::create() goes through it.
  • get_type() and get_object_type(). Both return that proxy. The static version works on the class, and the virtual version works on an existing object. Factory override calls use them.
  • create(). Allocates a new object of this exact type.
  • get_type_name(). Returns the class name as a string, here "ABC". Reports and the factory's name-based lookups use it.
  • Factory registration. The type is registered under the string "ABC" before the simulation starts running any test.

Quick Check

Q: A transaction class has function new(string name); with no default value, and uses `uvm_object_utils. What happens?

Show Answer

It does not compile. The create() method generated by the macro allocates the object without passing a name, which needs a constructor whose arguments all have defaults. Writing string name = "my_item" fixes it.

Parameterized Classes

A class with parameters is really a family of types, one per parameter value. It must use the _param_utils macros, with the full parameterized type as the argument.


class bus_item #(int WIDTH = 8) extends uvm_sequence_item;
  `uvm_object_param_utils(bus_item #(WIDTH))   // pass the parameterized type

  rand bit [WIDTH-1:0] data;

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

The only difference from the regular macro is that no type name string is registered with the factory. As a result, you cannot look up or override a parameterized class by name. Override it by type instead, for example with set_type_override_by_type(), which is explained in UVM Factory Override.

Creating Objects Through the Factory

Registering a class only pays off if you create it through the factory. Call type_id::create() instead of new() everywhere you build an object or component.


class base_test extends uvm_test;
  `uvm_component_utils(base_test)

  function new(string name = "base_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    // Object: name only
    ABC abc = ABC::type_id::create("abc");

    // Component: name and parent
    DEF def = DEF::type_id::create("def", this);
  endfunction
endclass

When abc is created this way, the factory first checks whether a test has asked for a different type in place of ABC. If it has, the factory returns an object of that derived type, and the code above never needs to change. Calling new() directly skips that check, so the class can no longer be overridden.

Utility Macros with Field Macros

The _begin/_end forms do everything the plain macros do, and also open a block for `uvm_field_* macros. Each field macro adds one class property to the automatic copy(), compare(), print(), pack() and record() implementations.


class ABC extends uvm_object;
  rand bit [15:0] m_addr;
  rand bit [15:0] m_data;

  `uvm_object_utils_begin(ABC)
    `uvm_field_int(m_addr, UVM_DEFAULT)   // included in copy, compare, print, pack, record
    `uvm_field_int(m_data, UVM_DEFAULT)
  `uvm_object_utils_end

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

Each data type has its own field macro and each macro takes flags that switch individual operations off. The full list is in UVM Field Macros, and worked examples of the operations themselves are in UVM Object Print, UVM Object Copy/Clone and UVM Object Compare.

Field macros save typing but expand into a lot of generic code, which slows compilation and simulation. A common team rule is to use plain `uvm_object_utils and write do_copy(), do_compare() and do_print() by hand for transactions on performance-critical paths.

Common Mistakes

Mistake #1: Using the object macro in a component, or the reverse

Wrong: `uvm_object_utils(my_driver) in a class that extends uvm_driver
Right: `uvm_component_utils(my_driver)

Why? The two macros generate different create() methods and different factory proxies. The object version never passes a parent, so depending on the constructor you get either a confusing compile error inside the macro expansion or a component built without its parent, sitting in the wrong place in the hierarchy.

Mistake #2: Passing the wrong class name to the macro

Copying a class and forgetting to update `uvm_object_utils(old_name) registers the wrong type. get_type_name() then reports the old name, and type_id::create() builds the old class, which usually surfaces as a compile error wherever the result is assigned. The macro argument must always be the class it sits in.

Mistake #3: Using the plain macro on a parameterized class

Wrong: `uvm_object_utils(bus_item) inside bus_item #(WIDTH)
Right: `uvm_object_param_utils(bus_item #(WIDTH))

Why? The plain macro registers a type name string, which cannot be done once for a family of specializations. The _param_ version registers each specialization by type only.

Mistake #4: Creating registered classes with new()

A class that is registered but built with new() works until someone tries to override it. The override is then silently ignored, because new() never consults the factory. Use type_id::create() consistently.

Summary

In this article, you learned:

  • `uvm_object_utils registers objects and `uvm_component_utils registers components with the factory
  • The macro generates type_id, get_type(), create() and get_type_name()
  • Object constructors need default values on every argument, and component constructors need a name and a parent
  • Parameterized classes use the _param_utils macros and can only be overridden by type
  • type_id::create(), not new(), is what makes factory overrides possible
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5