Part 5 · Sequences · Intermediate

Starvation & Debug Triage: When Sequences Hang at start_item

Forgotten unlock signatures, background lock holders, verbosity playbook, and arbitration hang checklist.

What starvation looks like

Arbitration starvation occurs when one or more sequences block forever at start_item because they never receive a driver grant. The test hangs with no UVM_ERROR — simulation runs until timeout. The driver may be idle or stuck, but the root cause is almost always a sequencer state problem: forgotten unlock, permanent lock holder, or all sequences waiting on each other.

diagram
[UVM] classic starvation signature

  Simulation time: 10ms ... 100ms ... 1s ... TIMEOUT

  Last log messages:
    UVM_INFO  bg_seq requesting item @ 50us
    UVM_INFO  setup_seq requesting item @ 50us
    (silence)

  Driver: idle — no get_next_item activity
  Sequencer: LOCKED by setup_seq (never unlocked)

  Diagnosis: forgotten unlock(this) after lock(this)

Top causes — triage checklist

  1. Forgotten unlock/ungrab — lock holder died or returned without release.

  2. Lock on exception path — randomize failure or return before unlock.

  3. grab in background sequence — permanent seizure of sequencer.

  4. All sequences blocked at start_item — driver not calling item_done.

  5. Driver hang — looks like arbitration but driver never completes item.

diagram
[SEQ] triage flowchart

  Test hangs, no UVM_ERROR
      │
      ▼
  Is driver active (get_next_item / item_done in log)?
      │
      ├─ YES  driver hang lesson (item_done missing) — NOT arbitration
      │
      └─ NO   sequencer arbitration issue
                  │
                  ▼
             Check lock state / last lock() without unlock()
                  │
                  ▼
             Raise sequencer UVM_FULL verbosity
                  │
                  ▼
             Identify sequence ID holding lock or starving queue

Safe lock pattern — unlock on all paths

Structure lock regions so unlock always runs, even on randomize failure or early return:

systemverilog
task body();
  apb_item req = apb_item::type_id::create("req");

  p_sequencer.lock(this);

  foreach (reg_vals[i]) begin
    start_item(req);
    if (!req.randomize() with { addr == REG_BASE + i*4; data == reg_vals[i]; }) begin
      `uvm_error("RAND", $sformatf("randomize failed at index %0d", i))
      break;
    end
    finish_item(req);
  end

  p_sequencer.unlock(this);  // ALWAYS reached — even after break
endtask

For complex bodies with multiple return points, use a flag or wrap in a dedicated sub-task that always unlocks in a single exit path.


Debug playbook — verbosity and logging

Raise verbosity on the sequencer component to see arbitration decisions from the UVM library:

systemverilog
// In test or env build_phase:
uvm_config_db#(int)::set(this, "env.apb_agent.sqr", "recording_detail", UVM_FULL);

// Or via command line:
// +uvm_set_verbosity=env.apb_agent.sqr,_ALL_,UVM_FULL,time,0

// Add to every sequence under debug:
task body();
  `uvm_info("ARB_DBG", $sformatf("%s: start_item entry pri=%0d",
             get_full_name(), get_priority()), UVM_LOW)
  start_item(req);
  `uvm_info("ARB_DBG", $sformatf("%s: granted driver", get_full_name()), UVM_LOW)
  finish_item(req);
  `uvm_info("ARB_DBG", $sformatf("%s: item_done complete", get_full_name()), UVM_LOW)
endtask
diagram
[UVM] debug log — identifying lock holder

  UVM_FULL @ 50us  sqr: lock acquired by env.prog_regs_seq
  UVM_FULL @ 55us  sqr: start_item grant blocked — sequencer locked
  UVM_FULL @ 60us  sqr: start_item grant blocked — sequencer locked
  ...
  (no unlock message ever appears)

  Action: search prog_regs_seq body for missing unlock or early return

Quick isolation steps

  • Disable background sequences — does hang disappear? Lock contention confirmed.

  • Run only the suspected lock holder — verify unlock appears in log.

  • Check fork branches — one branch locks, another crashes before unlock.

  • Sim timeout + last sequence name in log = likely lock holder.

Key takeaways

  • Starvation = sequences block at start_item with no driver grant — usually forgotten unlock.

  • Always pair lock/grab with unlock/ungrab on every exit path.

  • Raise sequencer UVM_FULL verbosity to see lock acquire/release in library messages.

  • Driver idle + pending start_item = sequencer state bug, not driver bug.

Common pitfalls

  • Blaming the driver for start_item hang — check sequencer lock state first.

  • Adding timeout to start_item — masks bug; fix the unlock.

  • Using grab as a workaround for forgotten unlock — creates worse starvation.

  • Ignoring intermittent hangs — usually race between lock timing and fork join.