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.

diagram
[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 release
  • pre_* 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

systemverilog
// 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
diagram
[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 simple
  • pre_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

systemverilog
// 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
diagram
[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+N
  • post_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:

diagram
[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-checks

Key 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.