Part 2 · Phases & Lifecycle · Intermediate

Function Phase Rules: Zero Time and Structural Work

Rules governing function phases — no time control, allowed actions per phase, and why structural work must finish before simulation time advances.

The zero-time contract

Function phases execute in zero simulation time . The entire build-time and cleanup strips complete before the first clock edge of run-time activity (unless you explicitly advance time inside a task phase later).

  • No #delay, @event, wait(), or fork inside function phases.

  • No blocking TLM calls that consume time.

  • No raise_objection/drop_objection — objections are for task phases.

diagram
[UVM][PHASE] function phase constraints

compiler/runtime enforcement:
  time-consuming statements in functions -> error or undefined

methodology enforcement:
  even 'quick' #1ns in build hides ordering bugs

allowed:
  create, config get, connect, asserts, prints
  non-blocking checks, pure functions

Key takeaways

  • Function phases are instantaneous scheduler barriers.

  • If it needs to wait for hardware, it belongs in a task phase.

  • Zero-time discipline is what makes build/connect ordering reliable.

Common pitfalls

  • Calling a task from a function phase and waiting for it.

  • Using @(posedge clk) in start_of_simulation to 'align' to clock.

  • Forking background threads in build_phase — races run_phase.


Allowed actions by function phase

build / connect / end_of_elaboration

systemverilog
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  void'(uvm_config_db#(my_cfg)::get(this, "", "cfg", cfg));
  mon = my_monitor::type_id::create("mon", this);
endfunction

function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  mon.ap.connect(scb.imp);
endfunction

function void end_of_elaboration_phase(uvm_phase phase);
  super.end_of_elaboration_phase(phase);
  if (drv == null && cfg.is_active)
    `uvm_fatal("ELAB", "active agent missing driver")
endfunction

extract / check / report / final

systemverilog
function void extract_phase(uvm_phase phase);
  super.extract_phase(phase);
  txn_count = scb.get_checked();
  cov_snapshot = cov.get_inst_coverage();
endfunction

function void check_phase(uvm_phase phase);
  super.check_phase(phase);
  if (txn_count == 0) `uvm_error("CHK", "no activity")
  if (scb.get_errors() > 0) `uvm_error("CHK", "scoreboard errors")
endfunction
diagram
[PHASE] function phase forbidden list

#N delay
@(event)
wait()
fork ... join
blocking TLM put/get
drive physical pins
start sequences on sequencers
raise_objection / drop_objection

Key takeaways

  • Each function phase has a narrow job — respect the strip on the timeline map.

  • end_of_elaboration is ideal for structural fatal checks.

  • check_phase is for uvm_error, not long formatted reports.

Common pitfalls

  • Starting a sequence in connect_phase — sequencer may not be ready.

  • Heavy file I/O in check_phase — move harvesting to extract.

  • Using final_phase for functional pass/fail — too late.