UVM
uvm_component
The base class for persistent structure — hierarchy, phasing, configuration, and reporting added over uvm_object, the (name, parent) constructor, and why structure extends it.
UVM Base Classes · Module 4 · Page 4.3
The Engineering Problem
The previous chapter covered uvm_object — the base class for data. This one covers its counterpart, uvm_component — the base class for structure. Where a uvm_object is transient data that flows through the testbench, a uvm_component is a permanent node of the testbench: the test, the env, the agent, the driver, the monitor, the scoreboard — every structural piece you've built extends uvm_component.
The engineering question is precise: what does uvm_component add over uvm_object? Because a component is an object (it inherits the whole data toolkit), but it adds four things that make it structural rather than data: a place in the hierarchy (a parent, children, a full name), participation in phasing (build, connect, run…), integration with the configuration database (scoped to its path), and reporting (messages tagged with its name). And it adds a fifth, implicit property: it persists for the entire simulation, while objects come and go. Getting the distinction right is foundational — it determines which base class you extend, which constructor you write, and whether a piece participates in phasing at all. Extend the wrong one, or write an object's constructor on a component, and it won't build.
What is
uvm_component, what does it add overuvm_object(hierarchy, phasing, configuration, reporting, lifetime), and why does everything structural extend it — including the constructor that must differ from an object's?
Motivation — why structure needs a different base class
The four additions of uvm_component are exactly what a structural node requires and a data object must not have:
- Hierarchy makes it addressable and composable. A component has a parent and children and a full path (Module 3.3), so it can be configured, found, and composed into a tree. Data has no business in the tree; structure has no business without one. The hierarchy is the difference.
- Phasing makes it execute. Components are phased — the framework calls
build_phase,connect_phase,run_phaseon every component in the tree. This is what makes a component do things in an orchestrated order; objects have no phases and never execute on their own. - Configuration makes it adaptable. A component participates in the config database scoped to its path, so the test can configure it from above (Module 3.5). This is the mechanism of reuse, and it is keyed on the component's place in the hierarchy.
- Reporting makes it accountable. A component is a
uvm_report_object, so its messages (`uvm_info,`uvm_error,`uvm_fatal) are tagged with its name and routed through UVM's reporting system — which is how you trace which of dozens of components produced a message.
The motivation, in one line: data and structure are fundamentally different kinds of thing, so they have different base classes — and uvm_component is the one that gives a piece a place in the tree, a phased lifecycle, configurability, and a reporting identity, which is precisely what a structural node needs and a data object must not have.
Mental Model
Hold uvm_component as a building with an address, a schedule, and a nameplate:
A
uvm_componentis auvm_objectthat has moved into the building permanently and gotten an address, a daily schedule, a mailbox, and a nameplate. It still has the object's data toolkit (it inherits copy/compare/print, though it rarely uses them), but now it also has: an address (its place in the hierarchy — a parent, a full path), a schedule (phases — it shows up for build, then connect, then run, in lockstep with every other component), a mailbox (the config database — the test sends it settings scoped to its address), and a nameplate (reporting — every message it sends is signed with its name). And unlike the transient objects that visit (transactions passing through), the component lives there for the whole simulation. A data object is a visitor; a component is a resident.
So the model for choosing a base class is: is this a permanent resident of the testbench (a node that builds, connects, runs, and persists) or a transient visitor (data that flows through)? Residents extend uvm_component; visitors extend uvm_object. And residents get the two-argument constructor — a name and the address (parent) they move into.
Visual Explanation — what uvm_component adds over uvm_object
uvm_component inherits everything from uvm_object and layers structural capabilities on top. The picture is one of addition: the data toolkit at the base, the structural machinery above it.
Read the stack as inheritance plus addition. The base (greyed) is the uvm_object data toolkit — a component has it but a driver almost never copies or compares itself. Above it, the four structural layers are what make it a component: hierarchy (it's a node with an address), phasing (it executes in orchestrated phases), configuration (the test adapts it from above), and reporting (it has a logging identity). The implicit fifth property — persistence — runs through all of them: because it's in the tree and phased, it exists from build_phase to the end of simulation. This is the precise answer to "what does uvm_component add": four capabilities and a lifetime, layered over the inherited object.
RTL / Simulation Perspective — a component uses all four additions
A single component touches all four structural capabilities, and the code shows each — including the constructor that distinguishes a component from an object.
class my_driver extends uvm_component; // a component (uvm_driver extends uvm_component too)
`uvm_component_utils(my_driver) // factory registration for a COMPONENT
virtual my_if vif;
// CONSTRUCTOR: name AND parent — this is the component signature (objects take name only)
function new(string name, uvm_component parent);
super.new(name, parent); // hand the parent up → placed in the tree
endfunction
// PHASING: the framework calls this (objects have no phases)
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// CONFIGURATION: the config database, scoped to THIS component's path
if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "virtual interface not set") // REPORTING: tagged with this component
// HIERARCHY: ask the component its own address
`uvm_info("DRV", $sformatf("built at %s", get_full_name()), UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
// ... persists and runs for the whole simulation ...
endtask
endclassEvery line maps to one of the additions. The constructor new(string name, uvm_component parent) is the component signature — note the parent argument, absent from an object's constructor, which is what places the component in the tree via super.new(name, parent). Phasing: build_phase and run_phase are framework-called methods an object doesn't have. Configuration: uvm_config_db::get(this, ...) retrieves a setting scoped to this component's path. Reporting: `uvm_fatal and `uvm_info emit messages tagged with this component (it's a uvm_report_object). Hierarchy: get_full_name() returns its address in the tree. The factory registration is `uvm_component_utils (note: component, not object). One class, all four structural capabilities — which is what being a uvm_component means.
Verification Perspective — component vs object, side by side
The clearest way to understand uvm_component is directly against uvm_object: same library, opposite roles. The contrast determines which base class you extend for anything you build.
This side-by-side is the decision you make every time you write a class. If the thing is data — it carries values, flows through the environment, gets randomised/copied/compared, and is discarded — it extends uvm_object (or a data subtype like uvm_sequence_item), takes a one-argument constructor new(string name = "..."), and has no phases. If the thing is structure — it occupies a place in the testbench, builds children, connects, runs, is configured from above, and persists — it extends uvm_component (or a subtype like uvm_driver), takes the two-argument constructor new(string name, uvm_component parent), and is phased. The two columns never blur: a transaction is never phased, a driver is never randomised. Picking the base class is picking the column, and the column dictates the constructor, the lifecycle, and the capabilities.
Runtime / Execution Flow — a component's phased lifetime
A component's defining behaviour over time is its phased lifetime: it is constructed in build_phase, wired in connect_phase, runs in run_phase, reports in report_phase, and exists continuously throughout — a sharp contrast with an object's create-use-discard.
This lifetime is the runtime meaning of "persistent structure." Unlike an object, which is created on demand and dropped when no longer referenced, a component is created once at build_phase and remains — phased through connect, run, and report, present the entire time. That persistence is why a component can hold long-lived state (a driver's interface handle, a scoreboard's expected queue), participate in connections (it must exist for the whole run to send and receive), and accumulate results to report at the end. The phased lifecycle also enforces the construct-then-connect-then-run ordering (Module 2.6): because the framework phases the whole tree of persistent components in lockstep, every component is built before any is connected, and connected before any runs. The component's phased, persistent lifetime is the structural backbone the transient objects flow through.
Waveform Perspective — the resident and the visitors
The component-versus-object distinction is visible in time: the component is alive the whole simulation while transaction objects flow through it and are discarded. The resident persists; the visitors come and go.
The component persists for the whole run; transaction objects come and go
10 cyclesThe contrast between comp_alive (high the whole trace) and txn (brief pulses) is the entire chapter on one timeline. The component is the resident: built once, it persists from build_phase to the end, holding state and participating in connections throughout — which is why uvm_component needs hierarchy, phasing, configuration, and reporting, all of which are about a long-lived participant in the testbench. The transactions are the visitors: each uvm_object is created, used to drive the pins, and dropped, needing only the data toolkit. Seeing them together makes the base-class choice obvious: anything that must persist and participate (hold state, connect, be phased) is a uvm_component; anything that merely carries data through is a uvm_object.
DebugLab — the component that wouldn't build (wrong constructor)
A component with an object's constructor — the factory couldn't create it
An engineer wrote a new monitor and, copying the pattern from a transaction class they'd just written, gave it the constructor function new(string name = "my_monitor"); super.new(name); endfunction. It would not work: depending on the simulator it failed to compile, or the factory's type_id::create("mon", this) errored because it could not pass the parent. The class looked like a component — it extended uvm_monitor, had `uvm_component_utils — but it could not be created in the tree.
The wrong constructor signature — an object's constructor on a component. A uvm_component must take a name and a parent and pass both to super.new; the engineer used the object form (name only):
uvm_object constructor: function new(string name = "x"); super.new(name);
uvm_component constructor: function new(string name, uvm_component parent); super.new(name, parent);
what was written (on a component): new(string name = "my_monitor"); super.new(name); // OBJECT form
the factory does: type_id::create("mon", this); // needs new(name, PARENT)
result: no matching 2-arg constructor → create fails / won't compile
fix: new(string name, uvm_component parent); super.new(name, parent);The factory creates a component by calling its two-argument constructor with the name and parent, so a component must provide new(string name, uvm_component parent) and pass the parent to super.new. The single-argument object constructor has no parent to place the component in the tree, so the framework cannot construct it as a component at all.
The tell is a class that extends a component base yet fails to be created — a constructor mismatch. Diagnose base-class/constructor problems by checking the signature against the base:
- Match the constructor to the base class. A
uvm_component(or any subtype: driver, monitor, agent, env, test) needsnew(string name, uvm_component parent); auvm_object(or subtype) needsnew(string name = "..."). A 1-arg constructor on a component is the bug. - Confirm
super.newpasses the parent. Even with the right signature,super.new(name)(dropping the parent) fails to place the component. It must besuper.new(name, parent). - Read the
_utilsmacro.`uvm_component_utilsexpects a component constructor;`uvm_object_utilsexpects an object one. A mismatch between the macro and the constructor signals copied-from-the-wrong-base code.
Match the base class, its constructor, and its utils macro — they go together:
- Components take (name, parent); objects take (name). Memorise the two constructor forms and use the right one for the base class you extend —
super.new(name, parent)for components,super.new(name)for objects. - Don't copy a constructor across the object/component line. When you reuse a template, check whether the source was a component or an object; the constructor and the
_utilsmacro must both match the base you're extending. - Let the tooling/snippets default correctly. Use editor snippets or templates that pair the right base class, constructor, and
`uvm_*_utilsmacro, so the signature is correct by construction.
The one-sentence lesson: a uvm_component constructor takes a name and a parent and passes both to super.new — using an object's name-only constructor on a component means the factory cannot place it in the tree, so it won't build.
Common Mistakes
- Object constructor on a component. A
uvm_componentneedsnew(string name, uvm_component parent)andsuper.new(name, parent); the object formnew(string name="x")makes the factory unable to create it. Match the constructor to the base class. - Mismatched
_utilsmacro.`uvm_component_utilsfor components,`uvm_object_utilsfor objects. Using the object macro on a component (or vice versa) registers the wrong kind of class and breaks creation. - Extending the wrong base for the role. Structure (test, env, agent, driver, monitor, scoreboard) extends
uvm_component(or a subtype); data (transactions, configs, sequences) extendsuvm_object(or a subtype). Extendinguvm_objectfor structure loses phasing and hierarchy. - Forgetting
super.build_phase/super.new. Omitting thesupercall skips base-class setup (placement, phase bookkeeping). Callsuper.new(name, parent)andsuper.build_phase(phase). - Treating a component like data. Components are not meant to be randomised, copied, or compared as values — that's the object's role. Don't
clonea component or put it in an analysis port; pass transactions (objects), not components. - Building components outside
build_phase. A component's children are created inbuild_phase(with the parent), so they're phased; constructing them elsewhere or without a parent makes orphans (Module 3.3).
Senior Design Review Notes
Interview Insights
uvm_component is the base class for all persistent structure in UVM — the test, environment, agent, driver, monitor, sequencer, and scoreboard all extend it (directly or via subtypes like uvm_driver). It inherits from uvm_object, so it has the data toolkit (copy, compare, print), but it adds the things that make a thing a structural node: a place in the hierarchy (a parent, children, and a full hierarchical name), participation in phasing (the framework calls build_phase, connect_phase, run_phase, etc.), integration with the configuration database (scoped to its path, so it can be configured from above), and reporting (it's a uvm_report_object, so its messages are tagged with its name). It also persists for the entire simulation — built once and alive throughout — whereas a uvm_object is transient data (a transaction, config, or sequence) that is created, used, and discarded with no hierarchy and no phases. The practical difference shows up immediately in the constructor: a component takes new(string name, uvm_component parent) while an object takes new(string name = "..."). So the rule is: structure that must persist, be phased, and participate in the tree is a uvm_component; transient data is a uvm_object.
Exercises
- Object or component? For each, choose the base class and write its constructor signature: (a) a bus transaction; (b) a scoreboard; (c) an agent configuration; (d) a monitor; (e) a sequence.
- List the additions. Name the four capabilities
uvm_componentadds overuvm_object, and for each, one line of code in a driver that uses it. - Fix the constructor. A monitor extends
uvm_monitorbut hasnew(string name="mon"); super.new(name);and fails to create. State the root cause and write the corrected constructor. - State placement. A teammate stores a scoreboard's running list of expected transactions as a field of the transaction class. Explain why this is wrong in terms of persistence, and where the list belongs.
Summary
uvm_componentis the base class for all persistent structure — test, env, agent, driver, monitor, sequencer, scoreboard. It inherits theuvm_objectdata toolkit and adds the machinery of a structural node.- Over
uvm_objectit adds four capabilities — hierarchy (parent/children/full name), phasing (build/connect/run/…), configuration (the config database scoped to its path), and reporting (messages tagged with its name) — plus a fifth, implicit property: it persists for the whole simulation. - Its constructor differs:
new(string name, uvm_component parent)(andsuper.new(name, parent)), because the parent is what places it in the tree — versus an object'snew(string name = "..."). The factory creates components via this two-argument constructor, so getting it wrong means it won't build. - The component-vs-object choice is the structure-vs-data choice: residents that build, connect, run, hold state, and persist are components; transient data that flows through is objects. The two halves never blur — components aren't randomised, objects aren't phased.
- The durable rule of thumb: extend
uvm_componentfor anything that must live in the tree, be phased, be configured, and persist — give it the(name, parent)constructor and`uvm_component_utils— and keep it in its lane: structure persists and participates, data flows through.
Next — uvm_transaction: with both halves of the library in view — uvm_object (data) and uvm_component (structure) — the next chapter zooms into the data side, to uvm_transaction: the uvm_object subtype that adds transaction identity and timing, and the base from which sequence items are built.