Part 11 · Senior Prep · Intermediate

Hang Triage: Objections, Handshakes, and drain_time

Completion-bucket playbook for simulations that never end — objection leaks, sequence/driver handshake stalls, fork/join traps, and drain_time extensions.

Completion bucket first moves

A hang means run_phase never completes . The two dominant causes are objection imbalance and sequence/driver handshake deadlock.

diagram
[DEBUG][SENIOR][UVM] hang triage flow

1) +UVM_OBJECTION_TRACE +UVM_PHASE_TRACE
2) find last phase activity — did run_phase start?
3) display_objections — who holds count > 0?
4) grep "get_next_item" / "item_done" — driver stuck?
5) check fork/join_any without disable fork
bash
simv +UVM_TESTNAME=stress_test \
     +UVM_OBJECTION_TRACE \
     +UVM_PHASE_TRACE \
     +UVM_VERBOSITY=UVM_MEDIUM \
     -l hang.log

grep -E "OBJECTION|get_next_item|item_done|run_phase" hang.log | tail -40
systemverilog
// objection audit at strategic points
function void end_of_elaboration_phase(uvm_phase phase);
  super.end_of_elaboration_phase(phase);
  uvm_top.print_topology();
endfunction

task run_phase(uvm_phase phase);
  phase.raise_objection(this, "test start");
  main_vseq.start(env.v_sqr);
  phase.drop_objection(this, "test done");  // must always run
endtask

Key takeaways

  • Objection trace is the highest-leverage hang tool — use it first.

  • Driver waiting on get_next_item with no running sequence = handshake stall.

  • Every raise must have a matching drop on all paths including errors.

Common pitfalls

  • Adding arbitrary # delays instead of fixing objection balance.

  • Debugging waves before reading +UVM_OBJECTION_TRACE output.

  • fork/join_any timeout branch that forgets disable fork — zombie threads.


Objection leak patterns

Most objection leaks come from a small set of repeatable patterns. Recognize them from trace output.

Common leak sources

diagram
[DEBUG][SENIOR][UVM] objection leak catalog

pattern                          trace signature
  sequence fork without join     seq raises, child never drops
  early return before drop       raise then UVM_FATAL, no drop
  drain_time never expires       count hits 0 but phase waits
  sub-component hidden raise     env/agent raises, test unaware
  duplicate raise same reason    count stuck at 1 forever
systemverilog
// safe pattern: single owner of test objection
virtual task run_phase(uvm_phase phase);
  phase.raise_objection(this, "main");
  fork
    begin
      run_stimulus();
    end
    begin
      wait_for_quiescence();
    end
  join
  phase.drop_objection(this, "main");
endtask

// anti-pattern: raise in sequence AND test
task body();
  if (starting_phase != null)
    starting_phase.raise_objection(this);  // often forgotten drop

Handshake stall triage

  • Log before and after get_next_item in the driver.

  • Confirm a sequence is running on that sequencer (not blocked on lock).

  • Check item_done is called on all driver paths including error.

  • Verify reset did not kill the sequence mid-handshake.

systemverilog
task run_phase(uvm_phase phase);
  forever begin
    seq_item_port.get_next_item(req);
    `uvm_info("DRV", $sformatf("item=%s", req.convert2string()), UVM_MEDIUM)
    drive_item(req);
    seq_item_port.item_done();
  end
endtask
diagram
[DEBUG] handshake evidence

last log = "waiting get_next_item"
and no "got item" within N us
-> sequence not producing OR wrong sequencer

Common pitfalls

  • Raising objections in both test and sequences without documented ownership.

  • Using phase_ready_to_end extensions without understanding drain_time.

  • Ignoring sub-environment components that raise their own objections.