Every component you build - drivers, monitors, environments, tests - needs a parent somewhere up the chain, all the way to a single root. uvm_root is that root. You never write uvm_root my_root = new(); yourself, but every testbench you have ever run has one, automatically created the moment run_test() is called, and every orphaned component in your hierarchy quietly becomes its child.
This article covers what uvm_root actually is, where it sits in the base class hierarchy, and the three things its global handle uvm_top is most commonly used for: implicit parenting, hierarchical component search, and global report configuration.
7 min read | Intermediate Level
What You'll Learn
- What
uvm_rootis and why you should never instantiate it yourself - How orphaned components (no parent specified) end up under
uvm_top - How to search the component hierarchy with
find()andfind_all() - How to set verbosity and access the config database globally through
uvm_top
What is uvm_root ?
uvm_root is a singleton class that serves as the top-level container for all UVM components in a verification environment. Its single instance is called uvm_top. UVM creates this instance automatically the moment run_test() is called - you never construct it, and you should never try to create a second instance.
If you only remember one thing about uvm_root, make it this: it is always there, it is always exactly one object, and every component without an explicit parent ends up attached to it.
How the Source Actually Creates It
It's worth seeing the mechanism in the UVM library itself rather than taking "it's a singleton" on faith. Inside uvm_root.svh, the class keeps a private static handle and only ever lets one instance exist:
class uvm_root extends uvm_component;
// singleton handle
static local uvm_root m_inst;
// Function: get()
// Get the singleton uvm_root instance
extern static function uvm_root get();
...
endclass
m_inst is declared static local, so no code outside uvm_root itself can touch the handle directly. The only door in is the static get() function, which goes through a layer called uvm_coreservice_t - UVM's central lookup point for the factory, the report server, and the root, all in one place:
// uvm_coreservice_t::get_root() ultimately calls back into uvm_root
// to allocate the one instance on first use, and returns the same
// handle on every call after that.
uvm_coreservice_t cs;
uvm_root r;
cs = uvm_coreservice_t::get();
r = cs.get_root();
// uvm_root::get() is a convenience wrapper around the two lines above
r = uvm_root::get();
This is also exactly what the global uvm_top variable resolves to - uvm_top and uvm_root::get() always hand back the same object, because there is only ever one to hand back.
The other detail worth seeing in source is how a brand-new component decides whether it belongs to a real parent or to uvm_top. That logic lives in uvm_component::new() inside uvm_component.svh:
function uvm_component::new (string name, uvm_component parent);
uvm_root top;
uvm_coreservice_t cs;
super.new(name);
// If this construction IS uvm_top itself, blank its name so it
// never shows up in any child's full hierarchical path, then return
if (parent==null && name == "__top__") begin
set_name("");
return;
end
...
// Any other component with parent==null is attached to uvm_top here
endfunction
That name == "__top__" check is the special case reserved for uvm_root's own construction. Every other component you create with parent=null falls through past that check and is parented to uvm_top instead - which is the source-level reason behind the "orphaned components attach to uvm_top" behavior described above.
Finally, this is what actually happens when your testbench calls run_test("my_test"). The global run_test task (defined in uvm_globals.svh) does almost nothing itself - it just forwards to the one root instance:
task run_test (string test_name="");
uvm_root top;
uvm_coreservice_t cs;
cs = uvm_coreservice_t::get();
top = cs.get_root();
top.run_test(test_name);
endtask
Inside uvm_root::run_test(), the test class you named is pulled from the factory and constructed with uvm_top as its parent and the fixed instance name "uvm_test_top":
task uvm_root::run_test(string test_name="");
uvm_factory factory = uvm_factory::get();
uvm_component uvm_test_top;
...
$cast(uvm_test_top, factory.create_component_by_name(test_name,
"", "uvm_test_top", null));
...
endtask
That single line is the entire source-level explanation for why every full path in your log starts with uvm_test_top: the factory builds your test, names the instance literally "uvm_test_top", and the constructor logic shown above attaches it under uvm_top because no other parent was given.
Class Hierarchy
uvm_root extends uvm_component, which means it inherits the full chain described in UVM Base Classes: hierarchy management, phasing, reporting, and factory access all come along for free. The one thing that makes uvm_root different from every other component you write is that UVM itself creates and manages its single instance - you interact with that instance through the global variable uvm_top, never through a class handle you declare yourself.
Implicit Top-Level
uvm_top is the implicit top-level component, and any component whose parent argument is left unspecified (or explicitly passed as null) becomes a child of uvm_top instead of floating around with no place in the hierarchy.
class my_env extends uvm_env;
`uvm_component_utils (my_env)
// Note that if parent is not explicitly passed during creation, it will be null
function new(string name = "my_env", uvm_component parent = null);
super.new(name, parent);
endfunction
endclass
class my_test extends uvm_test;
`uvm_component_utils (my_test)
my_env m_env_1;
my_env m_env_2;
...
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Parent is set to my_test
m_env_1 = my_env::type_id::create("m_env_1", this);
// Parent is null, and hence set to uvm_top
m_env_2 = my_env::type_id::create("m_env_2");
endfunction
endclass
In the example above, m_env_1 is built normally, with this (the test) passed as its parent - that is the pattern you should use for almost everything in a real testbench. m_env_2 is created with no second argument at all, so it silently attaches itself under uvm_top instead of under the test. The test class itself works the same way: when you call run_test("my_test"), UVM creates an instance of my_test with uvm_top as its parent and names it uvm_test_top, which is why that name shows up in every full hierarchical path you have ever seen in a log file.
Quick Check
Q: If you create a component and pass null as its parent, where does it end up in the hierarchy?
Show Answer
It becomes a direct child of uvm_top, the same place any component with an unspecified parent ends up.
Search Functionality
uvm_top can search for components by their full hierarchical name, using find() for a single match or find_all() for every match. Both accept wildcard patterns: * matches any sequence of characters and ? matches exactly one character. The search walks the hierarchy top to bottom in alphanumeric order, and a pattern starting with a dot is treated as an absolute path rather than a wildcard search from the top.
class my_test extends uvm_test;
`uvm_component_utils (my_test)
my_env m_env;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_env = my_env::type_id::create("m_env", this);
endfunction
virtual function void end_of_elaboration_phase(uvm_phase phase);
uvm_component comp;
uvm_component comp_q [$];
super.end_of_elaboration_phase(phase);
// Gets handle to the driver indicated by the path
comp = uvm_top.find("uvm_test_top.m_env.m_agent.m_driver");
// Gets the first handle to the driver that matches the pattern
comp = uvm_top.find("*apb_driver*");
// Gets queue of all handles that match the pattern
uvm_top.find_all("*apb_driver*", comp_q);
foreach (comp_q[i]) begin
`uvm_info (get_type_name(), $sformatf("Found component %s", comp_q[i].get_full_name()), UVM_LOW)
end
endfunction
endclass
find_all() takes an optional third argument, a starting component, which limits the search to that component's subtree instead of the whole testbench. This is useful when two different agents happen to have identically-named children and you only want matches under one of them.
Where Is This Used?
Wildcard search through uvm_top is commonly used in debug code and in generic testbench utilities that need to act on every instance of a particular component type - for example, raising verbosity on every monitor in a multi-agent SoC environment, or collecting handles to every scoreboard to print a combined pass/fail summary at the end of a test, without hardcoding each instance name.
Report Configuration
uvm_top can set a global report verbosity that affects every component in the simulation in one call, and its reporting interface is reachable from outside uvm_component entirely - from modules, sequences, or anywhere else that has visibility into uvm_pkg.
class my_test extends uvm_test;
...
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_top.set_report_verbosity_level_hier(UVM_HIGH);
endfunction
endclass
The name of uvm_top itself is set to an empty string "", specifically so it does not appear in the full hierarchical name of its children - this is why a path looks like uvm_test_top.m_env.m_agent rather than uvm_top.uvm_test_top.m_env.m_agent. Another very common use of the same instance is passing it as the context argument in uvm_config_db, which makes a value visible to every component in the testbench:
// Set m_cfg to the top level and let everything else beneath it have access
uvm_config_db #(my_cfg)::set(uvm_root::get(), "*", "my_cfg", m_cfg);
if (! uvm_config_db #(my_cfg)::get(uvm_root::get(), "*", "my_cfg", m_cfg))
`uvm_fatal (get_type_name(), $sformatf("[ERROR] my_cfg not found in config_db !"))
uvm_root::get() is the static accessor for the singleton instance, and it returns the exact same object as the global uvm_top variable. The two are interchangeable; uvm_root::get() is just the form you reach for in contexts (like static functions) where referencing a global variable directly feels less natural.
One more behavior worth knowing about uvm_top: at the end of the end_of_elaboration phase, it checks whether any errors have been generated so far in the simulation. If it finds any, it raises a UVM_FATAL and the simulation stops before start_of_simulation_phase or run_phase ever begins. If your test fails before a single clock edge happens, this check - not your run_phase code - is usually the first place to look.
Common Mistakes
Mistake #1: Forgetting to pass this as the parent when creating a child component
Wrong: m_agent = my_agent::type_id::create("m_agent");
Right: m_agent = my_agent::type_id::create("m_agent", this);
Why? Omitting the parent does not cause an error - the component still builds and runs. But it silently attaches under uvm_top instead of under its intended parent, so its full hierarchical name (and anything that depends on that name, like uvm_config_db scoping) ends up wrong in a way that is easy to miss until a config lookup fails.
Mistake #2: Calling find() before the hierarchy is built
Wrong: calling uvm_top.find("*.m_driver") inside the test's build_phase
Right: calling it in end_of_elaboration_phase or later
Why? build_phase runs top-down, so while the test's build_phase is executing, the components below it have not been created yet. find() can only return components that already exist, so it returns null and prints a warning even though the path is correct.
Practice Exercise
Challenge: Find Every Driver in a Multi-Agent Environment
Suppose your environment has three agents, each named differently but each containing a driver named m_driver. Write a snippet inside end_of_elaboration_phase that collects handles to all three drivers using a single wildcard search, and prints each one's full hierarchical name.
Hint
You want every match, not just the first one, so reach for find_all() with a pattern that matches any path ending in m_driver.
Solution
virtual function void end_of_elaboration_phase(uvm_phase phase);
uvm_component drv_q[$];
super.end_of_elaboration_phase(phase);
drv_q = uvm_top.find_all("*m_driver");
foreach (drv_q[i]) begin
`uvm_info(get_type_name(), $sformatf("Driver found: %s", drv_q[i].get_full_name()), UVM_LOW)
end
endfunction
The leading * matches any prefix path, so this picks up m_driver regardless of which agent or environment it sits under.
Summary
In this tutorial, you learned:
uvm_rootis a singleton, automatically created byrun_test(), and accessed through the global handleuvm_topor the static accessoruvm_root::get()uvm_rootextendsuvm_component, inheriting hierarchy, phasing, reporting, and factory access from the same base class chain every other component uses- Any component created with no parent (or
parent=null) automatically becomes a child ofuvm_top, including the top-level test itself, which is nameduvm_test_top find()andfind_all()search the hierarchy by wildcard pattern, with an optional starting component to limit the search scopeuvm_topcan set verbosity globally and is commonly used as the context argument inuvm_config_dbso a value is visible from anywhere in the testbenchuvm_topraises aUVM_FATALat the end ofend_of_elaborationif any errors were already reported, stopping the simulation beforerun_phasebegins