Part 2 · Phases & Lifecycle · Intermediate
Scheduler vs User Code: Who Calls Whom
Clear boundaries between library scheduler behavior and user-implemented phase callbacks — what you must never do and what the library guarantees.
The golden rule
The scheduler calls your callbacks . You never call phase methods on other components to 'force' ordering. Coordination flows through phase placement, config_db, TLM, and objections.
[UVM][PHASE] DO / DON'T
DO:
implement build/connect/run/check callbacks
use config_db for hierarchical data distribution
use objections for run completion
use factory overrides before create
DON'T:
env.build_phase calling agent.connect_phase()
'wait for neighbor build' busy loops
module initial ordering UVM component phases
$finish in run_phase to end testKey takeaways
Neighbor phase calls are always a methodology smell.
config_db + factory + objections replace integrator scripts.
Simulation end is objection-driven, not $finish-driven.
Common pitfalls
Exporting 'init()' methods on VIPs that duplicate build_phase.
Top module initial blocks that create UVM components after run_test.
Using global singletons to bypass config_db phase timing.
How user code hooks into the scheduler
Test entry
module tb_top;
initial begin
uvm_config_db#(virtual dut_if)::set(null, "uvm_test_top.*", "vif", dut_if_i);
run_test("my_test"); // scheduler takes over from here
end
endmoduleComponent callbacks
class my_test extends uvm_test;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
phase.raise_objection(this, "test");
env.v_sqr.run_scenario();
phase.drop_objection(this, "test");
endfunction
endclassLibrary guarantees you rely on
When your build_phase runs, parent's build already ran (top-down).
When your connect_phase runs, all build_phase finished tree-wide.
When your run_phase starts, build-time phases completed globally.
When extract starts, run-time quiescence achieved per objection rules.
[PHASE] if you need X, use Y
need neighbor to exist:
-> put create in earlier phase (build), not neighbor call
need neighbor configured:
-> config_db set before their build, not direct field poke
need stimulus after reset:
-> runtime sub-phase or run_phase wait, not build delay
need test to end:
-> drop objections, not $finishKey takeaways
run_test() hands control to the scheduler — respect that boundary.
Every workaround table entry maps to a first-class UVM mechanism.
If you need neighbor phase calls, the architecture needs redesign.
Common pitfalls
Calling run_test() multiple times in one simulation.
Mixing UVM phases with OOP constructors for TB bring-up ordering.
Using uvm_domain jump as a substitute for correct phase placement.