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.
# 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[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_bodyLast 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
[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// 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
endtaskMatch 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:
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[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 dropTree 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
Reproduce with +UVM_OBJECTION_TRACE — capture full log.
Identify last unmatched RAISE (component + reason).
Classify component type → apply known failure pattern.
Open source — trace ALL exit paths from raise call.
Apply fix: unified drop, pre_body/post_body, or timeout.
Re-run — verify count reaches 0 and phase ends cleanly.
Check check_phase — no pending queue items (drain time adequate).
Remove debug watchdog before committing fix.
Add comment at raise site documenting drop guarantee.
[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 latencyKey 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.