Part 2 · Phases & Lifecycle · Intermediate
build_phase Walkthrough: Test to Agent Timeline
Step-by-step build_phase execution across test, env, and agent — with logging hooks and phase-trace interpretation.
Full build timeline
Follow one regression run from uvm_test_top through env and agent to see exactly when config flows and components appear.
[PHASE][UVM] annotated build timeline
T0 uvm_test_top.build_phase
set cfg → config_db
create env
T1 env.build_phase
get cfg
create agt_tx, agt_rx, sb, cov, v_sqr
T2 agt_tx.build_phase
get cfg, get vif
create mon, drv, sqr
T3 mon.build_phase (leaf)
T4 drv.build_phase (leaf)
T5 sqr.build_phase (leaf)
T6 agt_rx.build_phase
...
T7 build_phase complete → scheduler advances to connect_phasefunction void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("PHASE_BUILD",
$sformatf("enter %s", get_full_name()), UVM_MEDIUM)
// ... construction ...
`uvm_info("PHASE_BUILD",
$sformatf("exit %s children=%0d", get_full_name(), get_num_children()), UVM_MEDIUM)
endfunctionKey takeaways
Phase logging at UVM_MEDIUM gives a build order transcript for free.
Leaf components build last within each subtree — deepest first among siblings varies.
build_phase ends only when every component in the tree has finished building.
Common pitfalls
Infinite recursion — component creates itself as child.
build_phase doing heavy computation — slows every regression.
Logging every field of cfg at HIGH — log flood obscures real errors.
Instrumented reference implementation
Use this scaffold as a teaching reference — each layer documents what it creates and what config it consumes.
Test layer
class base_test extends uvm_test;
my_env env;
env_cfg cfg;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cfg = env_cfg::type_id::create("cfg");
apply_defaults();
uvm_config_db#(env_cfg)::set(this, "env", "cfg", cfg);
env = my_env::type_id::create("env", this);
endfunction
endclassEnv and agent layers
class my_env extends uvm_env;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
void'(uvm_config_db#(env_cfg)::get(this, "", "cfg", cfg));
agt_tx = tx_agent::type_id::create("agt_tx", this);
sb = my_scoreboard::type_id::create("sb", this);
endfunction
endclass
class tx_agent extends uvm_agent;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
void'(uvm_config_db#(tx_cfg)::get(this, "", "cfg", cfg));
void'(uvm_config_db#(virtual tx_if)::get(this, "", "vif", vif));
mon = tx_monitor::type_id::create("mon", this);
if (cfg.is_active == UVM_ACTIVE) begin
drv = tx_driver::type_id::create("drv", this);
sqr = tx_sequencer::type_id::create("sqr", this);
end
endfunction
endclass[PHASE] +UVM_PHASE_TRACE interpretation
UVM_PHASE_TRACE prints:
ENTER/EXIT <phase> <component_full_name>
Build debug recipe:
1) run with +UVM_PHASE_TRACE
2) grep PHASE_BUILD logs
3) compare order to expected top-down tree
4) first missing component → trace its parent's build_phaseCommon pitfalls
Comparing phase trace across UVM versions — log format may differ.
Assuming alphabetical child order from trace — not guaranteed among siblings.
Stopping analysis at first error without capturing full build transcript.