Part 2 · Phases & Lifecycle · Intermediate

Objections Debug: Advanced Hang Triage and Fixes

Advanced techniques for diagnosing objection hangs — trace forensics, hierarchy walk, common root causes by component type, and systematic fix patterns.

Advanced trace forensics

When +UVM_OBJECTION_TRACE shows a stuck count, the fix requires identifying the exact source component, the reason string, and why the matching drop never fired.

bash
# Capture full trace
simv +UVM_OBJECTION_TRACE +UVM_VERBOSITY=UVM_HIGH 2>&1 | tee obj_trace.log

# Find all raises without matching drops
grep "RAISE" obj_trace.log > raises.log
grep "DROP"  obj_trace.log > drops.log

# Last unmatched raise (manual or script)
tail -5 raises.log
# Compare component+reason against drops.log
diagram
[PHASE][UVM] trace forensics workflow

  1. grep last RAISE line  note component + reason + count
  2. grep same component+reason in DROP lines  absent = unmatched
  3. open that component's source  find raise call
  4. trace all exit paths from raise  find path without drop
  5. fix: add drop on missing path OR restructure with pre_body/post_body
  • Last RAISE without matching DROP is the primary forensic signal.

  • Component full name in trace maps directly to hierarchy path.

  • Count value shows how many objectors remain — not just the stuck one.


Root causes by component type

diagram
[PHASE][RUN] hang root cause catalog

  TEST:
    raise in run_phase, drop only on success path
    fix: drop after fork/join regardless of outcome

  SEQUENCE:
    raise in body() instead of pre_body()
    fix: move to pre_body/post_body pairing

  SEQUENCE:
    raise in pre_body, uvm_error in body skips post_body
    fix: post_body ALWAYS runs in UVM — check for disable/abort

  ENV/AGENT:
    raise in reset_phase, wait forever on signal
    fix: add timeout to wait, drop on timeout path

  DEFAULT_SEQUENCE:
    pre_body raises, body infinite loop
    fix: stoppable sequence with event/traffic count limit

  PHASE_READY_TO_END:
    re-raise without drop
    fix: always drop in same callback
systemverilog
// FIX PATTERN: unified drop point after fork/join_any
task run_phase(uvm_phase phase);
  bit done;
  phase.raise_objection(this, "TEST:main");
  fork
    begin run_main(); done = 1; end
    begin #(timeout); if (!done) `uvm_error("TO", "timeout"); end
  join_any
  disable fork;
  phase.drop_objection(this, "TEST:main_exit");  // ALWAYS reached
endtask
  • Match hang component type to known failure pattern.

  • Unified drop point after join_any is the most robust fix pattern.

  • pre_body/post_body in sequences eliminates most sequence-side hangs.


Hierarchy walk technique

When convert2string() output is ambiguous, walk the component tree programmatically:

systemverilog
function void dump_objection_tree(uvm_phase phase, uvm_component comp, int depth=0);
  string indent;
  uvm_objection obj;
  int source_count;
  indent = {"  ", {depth{"  "}}};
  obj = phase.get_objection();
  source_count = obj.get_source_count(comp);
  if (source_count > 0)
    `uvm_info("OBJ_TREE", $sformatf("%s%s source=%0d",
               indent, comp.get_full_name(), source_count), UVM_NONE)
  foreach (comp.children[i])
    dump_objection_tree(phase, comp.children[i], depth+1);
endfunction

// Call from watchdog:
task obj_watchdog(uvm_phase phase);
  #(timeout);
  `uvm_error("WD", "objection tree at timeout:")
  dump_objection_tree(phase, uvm_root);
  `uvm_error("WD", phase.phase_done.convert2string())
endtask
diagram
[UVM][PHASE] hierarchy walk output

  OBJ_TREE: test source=0
  OBJ_TREE:   env source=0
  OBJ_TREE:     axi_agent source=0
  OBJ_TREE:       sqr source=0
  OBJ_TREE:     bg_agent source=0
  OBJ_TREE:       sqr.bg_traffic_seq source=1  ← FOUND

   inspect bg_traffic_seq body() for missing post_body drop
  • Tree walk finds the exact leaf component with source > 0.

  • Combine with convert2string() for reason string context.

  • Add to reusable debug bundle — instantiate only on failing tests.


Systematic fix checklist

  1. Reproduce with +UVM_OBJECTION_TRACE — capture full log.

  2. Identify last unmatched RAISE (component + reason).

  3. Classify component type → apply known failure pattern.

  4. Open source — trace ALL exit paths from raise call.

  5. Apply fix: unified drop, pre_body/post_body, or timeout.

  6. Re-run — verify count reaches 0 and phase ends cleanly.

  7. Check check_phase — no pending queue items (drain time adequate).

  8. Remove debug watchdog before committing fix.

  9. Add comment at raise site documenting drop guarantee.

diagram
[PHASE][RUN][UVM] prevention practices

  □ every raise has documented drop path
  □ sequences use pre_body/post_body (not body raise)
  □ test uses unified drop after fork/join
  □ reason strings prefixed with owner role
  □ watchdog in debug builds with convert2string
  □ check_phase verifies pending queue empty
  □ drain time tuned to protocol latency

Key takeaways

  • Trace forensics: last unmatched RAISE → component → missing drop path.

  • Root cause catalog by component type speeds diagnosis.

  • Hierarchy walk + convert2string pinpoints the exact stuck objector.

  • Unified drop after join_any and pre_body/post_body prevent most hangs.

Common pitfalls

  • Fixing by increasing global timeout — hides bug, wastes regression time.

  • Removing raise instead of adding drop — changes test intent.

  • Fixing one path but missing another exit path — hang recurs intermittently.

  • Not verifying check_phase after fix — trailing transactions still lost.