UVM Basics

Chapter 6 of 10

The Factory: Registration, create(), and Overrides

What `uvm_component_utils`/`uvm_object_utils` actually register, why type_id::create() looks up a type instead of just constructing one, and how set_type_override() swaps an implementation without touching structural code -- closing chapter 3's oldest forward reference.

Chapter 3 used `uvm_component_utils(my_driver) and my_driver::type_id::create("drv", this) with a promise to explain them later. That's this chapter — and it's also the direct payoff of chapter 1's second promise: a standard way to swap an implementation without touching the structural testbench.

What the macro actually registers

`uvm_component_utils(my_driver) (and its object equivalent, `uvm_object_utils, used on mux_transaction back in chapter 4) does two things:

  1. Declares a nested type_id — a small helper type that knows how to construct my_driver specifically.
  2. Registers my_driver with the factory: a single global table, shared by the whole simulation, that maps a type (or a name) to "what to actually construct when asked for this."

Every UVM class that can be built via type_id::create() has to run through this registration first — it's what makes the next part possible.

type_id::create(): a lookup, not just a constructor call

my_driver::type_id::create("drv", this) doesn't just build a my_driver the way new("drv", this) would. It asks the factory a question: "what's currently registered to build when someone asks for my_driver?" Normally, the answer is my_driver itself — so create() behaves exactly like new() would have. But that answer can be changed, globally, without editing a single line of the code that calls create().

That's the entire reason chapter 3 used create() instead of new() in the first place: new() always constructs exactly the type you write. create() constructs whatever the factory currently says to construct — which is what makes the next section possible.

Overriding: set_type_override()

class my_driver_alt extends my_driver;
  `uvm_component_utils(my_driver_alt)
 
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
  task drive(mux_transaction tr);
    `uvm_info("DRV_ALT", $sformatf("(alt driver) %s", tr.convert2string()), UVM_MEDIUM)
    super.drive(tr);
  endtask
endclass

my_driver_alt overrides just the drive() task from chapter 4 — everything else (build_phase's config_db::get(), run_phase) stays inherited unchanged, super.drive(tr) still does the real work, and the override only adds a log line announcing itself. Now tell the factory to use it instead of my_driver, from inside the test's build_phase, before constructing drv:

function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  my_driver::type_id::set_type_override(my_driver_alt::get_type());
  drv = my_driver::type_id::create("drv", this);
endfunction

get_type() is another method `uvm_component_utils generates automatically — it returns a handle the factory can use to identify a type. After that one line, every my_driver::type_id::create(...) call anywhere in the testbench — not just this one — constructs a my_driver_alt instead. drv's declared type is still my_driver, and none of the driver-constructing code changed; only what actually gets built underneath it did. Delete that one line, and drv goes back to being a plain my_driver — no other code anywhere has to change either way.

This is the direct payoff of chapter 1's second promise: the capstone's hand-written driver class could only be changed by editing it directly. Here, swapping behavior for one test is a single line, in exactly one place, that doesn't touch my_driver, my_test, or anything else structural.

The same idea as SV chapter 11's polymorphism — a different lookup mechanism

SV ch11 (oop-inheritance-polymorphism) already covered swapping behavior through inheritance: a base-class handle can point to a subclass object, and a virtual method call dispatches to whichever the handle actually points to at runtime. The factory is the same underlying idea — dispatch to the subclass that's actually meant to run — just driven by a type lookup you configure, instead of by which subclass you happened to write new for. type_id::create() is effectively "polymorphism decided by a table you can change from the outside," rather than "polymorphism decided by which line of code called new."

A more targeted sibling: instance overrides

set_type_override() affects every create() call for that type, everywhere. Sometimes only one specific instance should change — for that, set_inst_override() takes a hierarchical path instead:

my_driver::type_id::set_inst_override(my_driver_alt::get_type(), "uvm_test_top.drv");

Only the component actually constructed at that exact path gets the override; every other my_driver::type_id::create(...) call elsewhere in the tree is unaffected. This track's examples only need the type-wide override, but it's worth knowing the targeted version exists.

Summary

  • `uvm_component_utils/`uvm_object_utils register a class with the factory — a single global table mapping "what type was asked for" to "what type to actually construct."
  • type_id::create(...) looks up that table before constructing anything; by default the answer is the type itself, exactly like new(...) — until an override changes it.
  • set_type_override() (called before the affected create() calls, typically early in a test's build_phase) redirects every future create() call for a type to a different type, without touching the code that calls create().
  • set_inst_override() does the same thing but for one specific instance path, leaving every other instance of that type unaffected.
  • This is SV ch11's inheritance-based polymorphism again, just dispatched through a configurable table instead of through which subclass a line of code happened to new.

What does `uvm_component_utils(my_driver) actually do?

After my_driver::type_id::set_type_override(my_driver_alt::get_type()); runs, what happens the next time my_driver::type_id::create('drv', this) is called?

What's the practical difference between set_type_override() and set_inst_override()?

How does the factory override mechanism relate to SV chapter 11's polymorphism material?