Part 2 · Phases & Lifecycle · Intermediate
Pre/Post Runtime Pairs: Bookend Phases
Why pre_* and post_* phases exist, typical usage for each pair, timing margins, and when to skip them.
Why bookend phases exist
Each runtime group has three phases: pre_*, the core phase, and post_*. The bookends provide setup and settle margins around the core activity without polluting the core phase itself.
[PHASE][RUN] bookend pattern
pre_X: prerequisites, wait for conditions, arm checks
X: core activity (reset, configure, main, shutdown)
post_X: settle, propagate, verify state before next group
example reset group:
pre_reset: wait for power-good, enable clocks
reset: assert/deassert reset, drive idle
post_reset: #N cycles settle after reset releasepre_* phases set up conditions the core phase assumes.
post_* phases provide propagation/settle time after core completes.
Skipping bookends is fine when the core phase is self-contained.
Common pre_* usage
// pre_reset: wait for power and clocks before reset manipulation
task pre_reset_phase(uvm_phase phase);
phase.raise_objection(this, "wait power good");
wait (power_good === 1'b1);
wait (clk_stable === 1'b1);
phase.drop_objection(this, "power/clocks ready");
endtask
// pre_configure: verify reset completed across all interfaces
task pre_configure_phase(uvm_phase phase);
phase.raise_objection(this, "pre-config check");
if (vif.reset_active)
`uvm_fatal("CFG", "reset still active before configure")
phase.drop_objection(this, "pre-config ok");
endtask
// pre_main: final readiness — interrupts masked, clocks checked
task pre_main_phase(uvm_phase phase);
phase.raise_objection(this, "pre-main setup");
mask_all_interrupts();
verify_clock_frequencies();
phase.drop_objection(this, "pre-main ready");
endtask[PHASE] pre_* phase decision guide
pre_reset: power/clock readiness (SoC, multi-clock)
pre_configure: sanity check post-reset state (complex DUT)
pre_main: interrupt mask, final cfg (interrupt-driven DUT)
pre_shutdown: stop sequence acceptance (graceful stop)
skip pre_* when: core phase is self-contained and simplepre_reset is essential for SoC with power/clock sequencing.
pre_configure catches 'reset not done' before wasted register writes.
pre_main is the last gate before irreversible traffic.
Common post_* usage
// post_reset: settle cycles after reset deassertion
task post_reset_phase(uvm_phase phase);
phase.raise_objection(this, "post-reset settle");
repeat (16) @(posedge vif.clk); // DUT pipeline flush
phase.drop_objection(this, "post-reset settle done");
endtask
// post_configure: wait for config propagation
task post_configure_phase(uvm_phase phase);
phase.raise_objection(this, "config propagate");
repeat (cfg.settle_cycles) @(posedge vif.clk);
phase.drop_objection(this, "config propagated");
endtask
// post_main: wind down — stop new items, wait for last completions
task post_main_phase(uvm_phase phase);
phase.raise_objection(this, "post-main wind down");
stop_ev.trigger();
#(cfg.post_main_drain_ns);
phase.drop_objection(this, "post-main done");
endtask
// post_shutdown: final idle verification
task post_shutdown_phase(uvm_phase phase);
phase.raise_objection(this, "post-shutdown check");
verify_all_interfaces_idle();
phase.drop_objection(this, "all idle confirmed");
endtask[PHASE][RUN] post_* settle timeline
reset deassert at T
post_reset: T → T+16clk (pipeline flush)
configure writes at T+16clk
post_configure: T+16clk → T+16clk+N (propagation)
main traffic starts at T+16clk+Npost_reset/post_configure provide deterministic settle margins.
post_main triggers controlled stop before shutdown group.
post_shutdown verifies clean idle state before cleanup phases.
When to skip bookends
Not every testbench needs all 12 phases. A simple block-level TB with synchronous reset may only need reset_phase and main_phase:
[PHASE][UVM] minimal vs full runtime schedule
MINIMAL (block-level):
reset_phase → main_phase → (optional shutdown_phase)
3 phases, no bookends
STANDARD (multi-agent):
reset group (all 3) → configure group (all 3) → main group → shutdown group
12 phases, full coordination
rule: add bookends when you need timing margins or cross-checksKey takeaways
pre_* phases prepare conditions; post_* phases provide settle margins.
Bookends keep core phases focused on their primary responsibility.
Skip bookends for simple block-level TBs — use only the core phases needed.
Every bookend phase needs its own raise/drop pair.
Common pitfalls
Heavy work in pre_* that belongs in the core phase — splits logic awkwardly.
Zero-cycle post_reset when DUT needs pipeline flush time.
Raising objection in pre_main but dropping in main — cross-phase mismatch.
Implementing all 12 bookends 'just in case' — adds sim time without value.