UVM Phases
Learn the UVM phase lifecycle — build, connect, run, and cleanup — and why the ordering matters.
UVM Phases
UVM uses a phased execution model to ensure that all components are built, connected, and configured before simulation begins. Understanding phases is fundamental to writing correct UVM code.
Phase Categories
| Category | Phases | Execution Order |
|---|---|---|
| Build-time | build, connect, end_of_elaboration | Top-down (parent first) |
| Run-time | run (+ sub-phases) | All components in parallel |
| Cleanup | extract, check, report, final | Bottom-up (child first) |
The Complete Phase Sequence
textbuild_phase ← Create child components (top → down) connect_phase ← Wire up ports/exports (bottom → up) end_of_elaboration ← Final configuration adjustments start_of_simulation ← Print topology, banners │ ▼ run_phase ← Main simulation (all run in parallel) ├── reset_phase ├── configure_phase ├── main_phase └── shutdown_phase │ ▼ extract_phase ← Collect results (bottom → up) check_phase ← Verify results report_phase ← Print summaries final_phase ← Cleanup
The Three Most Important Phases
1. build_phase — Create Components
This is where you create child components using the factory. It runs top-down (parent before child), so a parent exists before its children.
systemverilogfunction void build_phase(uvm_phase phase); super.build_phase(phase); // Create sub-components via the factory driver = my_driver::type_id::create("driver", this); monitor = my_monitor::type_id::create("monitor", this); endfunction
Rule: Never create components outside of
build_phase.
2. connect_phase — Wire Up Ports
This is where you connect TLM ports and exports. It runs bottom-up (children first), so all children are fully built.
systemverilogfunction void connect_phase(uvm_phase phase); // Connect driver's port to sequencer's export driver.seq_item_port.connect(sequencer.seq_item_export); // Connect monitor's analysis port to scoreboard monitor.ap.connect(scoreboard.analysis_export); endfunction
Rule: Never connect ports in
build_phase— the children may not exist yet.
3. run_phase — Run the Simulation
This is where actual simulation happens. All components' run_phase tasks execute concurrently.
systemverilogtask run_phase(uvm_phase phase); phase.raise_objection(this); // "Don't end yet!" // Drive 100 clock cycles of stimulus repeat(100) @(posedge vif.clk); phase.drop_objection(this); // "I'm done." endtask
The Objection Mechanism
The run_phase doesn't end automatically — it ends when all objections are dropped.
systemverilog// In the test: task run_phase(uvm_phase phase); my_sequence seq; phase.raise_objection(this); // Keep simulation alive seq = my_sequence::type_id::create("seq"); seq.start(env.agent.sequencer); phase.drop_objection(this); // Done — simulation can end endtask
What happens if you forget raise_objection?
The simulation ends immediately at time 0 — no stimulus is generated!
Best practice: Raise objections in the test or sequence, not in drivers or monitors.
report_phase — Print Results
systemverilogfunction void report_phase(uvm_phase phase); `uvm_info("REPORT", $sformatf( "Total: %0d | Pass: %0d | Fail: %0d", total, pass_count, fail_count ), UVM_NONE) endfunction
Common Mistakes
| Mistake | What Goes Wrong |
|---|---|
Creating components outside build_phase | Null references, crashes |
Connecting ports in build_phase | Children don't exist yet |
Forgetting raise_objection | Simulation ends at time 0 |
| Using blocking calls in function phases | Only run_phase is a task |
Calling super.build_phase() too late | Factory overrides don't apply |
Try It Yourself
Add a report_phase to a scoreboard component that prints the total number of transactions checked.