Chapter 2 of 10
The UVM Component Tree and Phasing
uvm_component as a standardized class hierarchy on top of class/extends, the fixed order UVM runs build_phase/connect_phase/run_phase in, the objection mechanism that controls when a test ends, and UVM's reporting macros.
Chapter 1's first gap was concrete: the SV Basics capstone's driver and generator() were just a class and a task, with no shared naming convention and nothing a tool could walk to report or debug. This chapter is where UVM closes that gap — uvm_component, the base class nearly every structural part of a UVM testbench extends.
uvm_component: a class with a standardized shape
uvm_component is a class, the same keyword SV ch10 (oop-classes) introduced, extended the same way SV ch11 (oop-inheritance-polymorphism) taught:
class my_driver extends uvm_component;
`uvm_component_utils(my_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclassEvery uvm_component's constructor takes exactly two arguments: name (a string, this instance's name) and parent (a handle to the component that owns it, or null for the top-level component). super.new(name, parent) hands both up to uvm_component's own constructor, which is what actually builds the tree — it records parent, and appends this instance's name to the end of parent's own path to build a full hierarchical name like uvm_test_top.env.agent.driver. That's the entire payoff of chapter 1's first promise: every component gets a name and a place in a standardized hierarchy for free, just by extending uvm_component and calling super.new() correctly — no hand-rolled naming convention required.
Phasing: what UVM runs, and in what order
Chapter 1's capstone used initial (SV ch8, always-blocks) and fork...join (SV ch14, interprocess-communication) to sequence its testbench by hand: construct the driver, then fork the generator and the driver's run() task together. UVM replaces that hand-sequencing with phasing: a fixed set of methods that UVM itself calls, in a fixed order, on every component in the tree. You don't call these methods — you override them, and UVM calls them for you:
| Phase | Kind | Typical use |
|---|---|---|
build_phase | function | construct sub-components, read configuration |
connect_phase | function | connect TLM ports between sibling components |
run_phase | task | the testbench's actual behavior — driving, monitoring, checking |
(A few more phases exist — end_of_elaboration_phase, start_of_simulation_phase before run_phase, and extract_phase/check_phase/report_phase/final_phase after it — for elaboration-time setup and post-run bookkeeping. This track doesn't use them directly, so they're mentioned here only so the names aren't a surprise if you see them elsewhere.)
None of these run on their own — a top-level module has to call run_test() to actually start the phase engine, which is where chapter 3 picks up.
super.build_phase(phase): don't forget it
Overriding build_phase is common — most components construct their children there. But uvm_component's own build_phase does real work too, and skipping the call to it silently breaks that work:
function void build_phase(uvm_phase phase);
super.build_phase(phase); // always call this first
// ... construct sub-components, read config, etc. ...
endfunctionThis is easy to forget and hard to notice when you do — the symptom shows up elsewhere (a config value that mysteriously never arrives, for instance), not as an error at the call site. The habit: every phase method you override starts with a call to super.<phase_name>(phase).
Phase direction: build runs top-down, connect runs bottom-up
build_phase runs top-down — a parent's build_phase runs before its children's, so a parent can publish configuration before a child's build_phase goes looking for it. connect_phase runs the opposite direction, bottom-up — children connect first, so that by the time a parent's connect_phase runs, its children already have something to connect to.
This isn't just trivia: it's the exact reason chapter 5's config_db pattern works (a value set() at the top, before any child exists, is guaranteed visible by the time a child's build_phase calls get()), so chapter 5 will point back here instead of re-deriving it.
The objection mechanism: how run_phase actually ends
run_phase is a task, not a function — and it's usually written as a forever loop with no natural stopping point, the same way SV ch8's always-style processes never end on their own. Every component's run_phase gets forked as a concurrent process when the phase starts. So what makes the phase — and the whole test — actually end?
The answer is the objection mechanism: phase.raise_objection(this) tells UVM "don't end this phase yet," and phase.drop_objection(this) withdraws that hold. UVM ends run_phase the instant nothing is objecting to it ending:
task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("RUN", "starting work", UVM_MEDIUM)
#10;
`uvm_info("RUN", "work done", UVM_MEDIUM)
phase.drop_objection(this);
endtaskTwo ways this breaks if you get it wrong:
- No objection ever raised: every component's
run_phaseprocess is still forked (they briefly exist), but with nothing objecting, UVM considers the phase satisfied essentially immediately and tears those processes down at time 0 — so it looks like nothing ran, even thoughrun_phasetechnically started. - An objection raised but never dropped: the phase — and the simulation — never ends.
This is UVM's standardized answer to a question SV ch8/ch14's fork...join/disable fork material already raised informally: how does a concurrent process know when it's safe to stop? Instead of every testbench inventing its own answer, every component in the tree uses the same raise_objection/drop_objection pair.
Reporting: `uvm_info, `uvm_warning, `uvm_error, `uvm_fatal, and verbosity
SV ch2 (basic-syntax) covered $display/$warning/$error/$fatal, graded by severity. UVM has its own version of the same idea, used inside any uvm_component or uvm_object:
| Macro | Severity | Stops simulation? |
|---|---|---|
`uvm_info | Info | No |
`uvm_warning | Warning | No |
`uvm_error | Error | No (but counted as a failure) |
`uvm_fatal | Fatal | Yes |
`uvm_info("DRV", $sformatf("driving sel=%0b", sel), UVM_MEDIUM)
`uvm_error("DRV", "unexpected value on the DUT output")`uvm_info's three arguments are an ID (a short string tag, usually the component's role, like "DRV"), the message, and a verbosity level (UVM_LOW, UVM_MEDIUM, or UVM_HIGH). Two things these macros add on top of SV ch2's plain $display-family:
- The reporting component's full hierarchical name is included automatically — a direct payoff of this chapter's naming-tree material. A
`uvm_infocall insideuvm_test_top.env.agent.driverprints that path without you ever formatting it yourself. - Verbosity can filter output at runtime, without touching code. A
`uvm_infocall only prints if its verbosity level is at or below the run's configured threshold — pass+UVM_VERBOSITY=UVM_HIGHon the simulator command line to see everything, or leave it at the default (UVM_MEDIUM) to see less.
Summary
uvm_componentis aclass(SV ch10) youextends(SV ch11); its constructor always takesnameandparent, which is how UVM builds a standardized, fully-named component tree for free.- UVM calls
build_phase,connect_phase,run_phase(and a few less commonly used phases) on every component, in a fixed order, instead of you sequencing things by hand withinitial/fork...join(SV ch8/ch14). You override these methods; UVM calls them. - Always call
super.build_phase(phase)(and the equivalent for any phase method you override) first — skipping it silently breaks the parent class's own setup. build_phaseruns top-down,connect_phaseruns bottom-up — chapter 5'sconfig_dbpattern depends on this ordering.run_phaseis atask, typically aforeverloop with no natural end.phase.raise_objection(this)/phase.drop_objection(this)control when it (and the test) actually ends — zero objections means the phase ends at time 0 before anything meaningful runs; an objection never dropped means it never ends.`uvm_info/`uvm_warning/`uvm_error/`uvm_fatalare UVM's version of SV ch2's severity-graded printing, with two things added: the reporting component's hierarchical name is included automatically, and`uvm_info's verbosity level can be filtered at runtime with+UVM_VERBOSITY, without touching code.
What are the two arguments every uvm_component's constructor takes, and what does UVM do with them?
A component overrides build_phase but never calls super.build_phase(phase). What's the most accurate description of what happens?
A run_phase task never calls phase.raise_objection(this) at all. What happens?
Which macro would you use to print a message severe enough that simulation should stop immediately? (lowercase, no backtick)