Your run_phase objections have all dropped. The objection trace shows the test's last drop at 2 ms. Yet the simulation keeps going, and check_phase does not start until 5 ms. Nothing is hung; something is still busy. The question is where, and the answer is a part of the schedule many engineers have never drawn: a second set of phases running beside run_phase.
UVM has two families of phases. The nine common phases (build through final) run on every component in every testbench. The twelve runtime phases (pre_reset through post_shutdown) are optional, finer steps for the time-consuming middle. They do not come after run_phase, and they are not inside it. They run on a parallel track that starts when run_phase starts.
Picture two railway tracks leaving the same station at the same moment. One train (run_phase) goes straight through. The other stops at twelve small stations on the way. Both must arrive at the next junction, extract_phase, before anything after it can happen. If the stopping train is held at one of its stations, the junction waits, even though the express arrived long ago.
In library terms, these tracks are domains. run_phase and the other common phases belong to the common domain. The twelve runtime phases belong to the uvm domain (or to a user-defined domain), which is joined to the common domain so that it starts with run_phase and must finish before extract. Phase Domains and Sync covers user domains.
Keep this picture. Almost every phase question in an interview, and almost every end-of-test bug, is answered by finding the right box in it.
Phase | Kind | Walk | Job | In the DMA bench |
|---|---|---|---|---|
| function | top-down | Create children through the factory, read configuration. |
|
| function | bottom-up | Wire ports, forward handles. | Agents set |
| function | bottom-up | Audit the finished structure. |
|
| function | bottom-up | Banners and last setup before time moves. | Not used. |
| task | parallel | Drive, monitor, check over time. | Drivers and monitors loop; the test runs the scenario under an objection. |
| function | bottom-up | Collect final numbers. | Not used. |
| function | bottom-up | Pass or fail on settled data. |
|
| function | bottom-up | Print summaries. |
|
| function | top-down | Close files, release resources. | Not used. |
Eight of the nine are functions, so they run in zero simulated time: the first four at time 0, the last four at the moment the run phases end. Only run_phase can wait. The walk column is explained in Traversal Order Rules.
Group | Phases | Typical use |
|---|---|---|
Reset |
| Apply and release reset. |
Configure |
| Program registers before traffic. |
Main |
| The primary stimulus. |
Shutdown |
| Let in-flight work drain. |
All twelve are tasks and run in order among themselves. Each has its own objection, so a phase ends only when no one objects to it. When nobody raises an objection in them, as in the DMA bench, they all pass through at time 0. Runtime Sub-Phases explains when to use them and why drivers stay in run_phase.
The skeleton below shows each common phase method with its exact signature. A real component overrides only the ones it needs. There is no objection here on purpose: raising and dropping belongs to the test or a sequence, never to a generic component.
// snippet
class phase_demo extends uvm_component;
`uvm_component_utils(phase_demo)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase); // create children, read config
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase); // wire ports, forward handles
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase); // audit the structure
endfunction
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);// banners
endfunction
task run_phase(uvm_phase phase); // the only common task phase
super.run_phase(phase);
endtask
function void extract_phase(uvm_phase phase);
super.extract_phase(phase); // collect numbers
endfunction
function void check_phase(uvm_phase phase);
super.check_phase(phase); // decide pass or fail
endfunction
function void report_phase(uvm_phase phase);
super.report_phase(phase); // print summaries
endfunction
function void final_phase(uvm_phase phase);
super.final_phase(phase); // close files
endfunction
endclassThree more callbacks exist on every component and are called for every phase, common and runtime: phase_started, phase_ready_to_end and phase_ended. They are how you hook the schedule itself; Inside the Phaser shows them in use.
This log comes from a copy of the smoke test that overrides phase_started and phase_ended to print each phase's full name (the ended line is printed only for run, main and post_shutdown):
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started common.build
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started common.connect
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started common.end_of_elaboration
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started common.start_of_simulation
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started common.run
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started uvm.uvm_sched.pre_reset
... reset through pre_main, all @ 0 ...
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started uvm.uvm_sched.main
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] ended uvm.uvm_sched.main
... post_main through shutdown, all @ 0 ...
UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started uvm.uvm_sched.post_shutdown
UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] ended common.run
UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] ended uvm.uvm_sched.post_shutdown
UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] started common.extract
UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] started common.check
UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] started common.report
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] started common.finalcommon.run and uvm.uvm_sched.pre_reset start at the same time, 0. The prefix is the domain (and schedule) each phase belongs to.
No one objects in the runtime phases, so main starts and ends at time 0. The DMA traffic all happens in run_phase.
post_shutdown started at 0 but ended at 655000, together with run. The last runtime phase is held until run_phase is ready to end too, which is how the two tracks meet before extract.
Extract, check, report and final all run at 655000, the time the run phases ended, not at time 0.
Back to the opening. A block team had added a background sequence that polls a status register, started from the test's shutdown_phase with its own objection "so the DUT is idle before we check". The poll loop waited for a FIFO-empty bit that, on one configuration, never set. The run_phase objections dropped on time, so the objection trace for run looked clean, and the team spent a day looking at the wrong phase.
The clue was in the schedule: extract waits for both tracks. A phase trace (+UVM_PHASE_TRACE) showed shutdown started and never ended, and the objection trace for that phase named the polling sequence. The fix was a bounded poll with an error on timeout. The lesson for debugging is to ask "which phase is still executing?" before asking "who holds the run objection?".
check_phase. It runs only after run_phase and every runtime phase have ended, so no monitor can still be writing. Code at the end of a component's run_phase races the last transactions, and in a forever-looping component it never runs at all.
start_of_simulation runs at time 0, like build: every phase before run is a zero-time function, later in order but not in time. report_phase runs at the time the run phases ended (655000 ps in the smoke test), also in zero time.
main_phase and drops it after 3 µs, and raises nothing in run_phase. The DMA traffic is all in drivers' run_phase. When does run_phase end?At about 3 µs (plus any drain time). run_phase is ready to end at time 0 because nobody objects to it, but the end of the uvm domain is synchronised with run: run cannot pass extract until post_shutdown is done, and post_shutdown cannot start until main has ended. So the main_phase objection holds the whole run window open.
Next: the schedule says which phase runs; Traversal Order Rules says in what order the phaser visits the components inside one phase.