Part 2 · Phases & Lifecycle · Intermediate

build_phase Debug: Symptom-First Construction Triage

Deterministic debug workflow for factory misses, config_db failures, null children, and topology surprises at build time.

Symptom matrix

Most build failures fall into four buckets. Start with the symptom, not the innermost component.

diagram
[PHASE][UVM] build-phase triage matrix

SYMPTOM                          LIKELY CAUSE
─────────────────────────────────────────────────────────
uvm_fatal NOCFG / NOVIF          config_db path/type/name mismatch
wrong component type in tree     override too late or wrong path
null child at connect            conditional build branch mismatch
duplicate component name         two creates with same instance name
build_phase never entered        method signature typo (not an override)
sim hangs in build               infinite create loop / recursion
systemverilog
// Signature typo — this is NOT an override, build_phase never runs:
function void build(uvm_phase phase);  // WRONG NAME
  super.build_phase(phase);
endfunction

Key takeaways

  • config_db dump() is the first tool for any get failure.

  • Factory print() confirms active overrides before create.

  • print_topology in end_of_elaboration validates build results.

Common pitfalls

  • Debugging connect-phase nulls without revisiting build-phase branches.

  • Adding uvm_fatal bypass with default cfg — hides real integration bug.

  • Changing randomize in build_phase — non-deterministic structure.


Deterministic debug toolkit

Keep these diagnostics cheap enough to leave enabled in base_test and env base classes.

Instrumentation primitives

systemverilog
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  build_enter_count++;
  `uvm_info("BUILD_DBG",
    $sformatf("comp=%s cfg_ok=%0d vif_ok=%0d",
      get_full_name(), cfg != null, vif != null), UVM_LOW)
endfunction

function void report_phase(uvm_phase phase);
  super.report_phase(phase);
  `uvm_info("BUILD_SUMMARY",
    $sformatf("build_enter_count=%0d", build_enter_count), UVM_NONE)
endfunction
diagram
[PHASE] debug command recipe

simv +UVM_TESTNAME=smoke_test \
     +UVM_PHASE_TRACE \
     +UVM_CONFIG_DB_TRACE \
     +UVM_VERBOSITY=UVM_MEDIUM

Then:
  1) grep NOCFG / NOVIF / FACTORY
  2) uvm_config_db::dump() at failure point
  3) factory.print(1)
  4) uvm_top.print_topology() in end_of_elaboration

Reproduction checklist

  1. Reproduce with one seed and UVM_MEDIUM verbosity.

  2. Capture build_phase log order from test to failing component.

  3. Validate config_db set paths against get_full_name() of target.

  4. Confirm factory overrides precede the create that should use them.

  5. Print topology — verify conditional branches match cfg intent.

  6. Only then inspect sequence or run_phase behavior.

  • Keep a smoke_test that builds env with zero stimulus for fast iteration.

  • Diff topology print between passing and failing cfg settings.

  • Track build_enter_count — unexpected zero means override never ran.

Key takeaways

  • Boundary-first debug: config → factory → topology → connect.

  • A smoke build-only test isolates construction in under one second.

  • Persistent low-cost build logging pays off across the whole project.

Common pitfalls

  • Enabling UVM_FULL before establishing baseline MEDIUM logs.

  • Fixing symptoms in connect_phase when root cause is build branching.

  • Skipping post-fix rerun with the exact failing plusarg combination.