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.
[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
Forgotten unlock/ungrab — lock holder died or returned without release.
Lock on exception path — randomize failure or return before unlock.
grab in background sequence — permanent seizure of sequencer.
All sequences blocked at start_item — driver not calling item_done.
Driver hang — looks like arbitration but driver never completes item.
[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 queueSafe lock pattern — unlock on all paths
Structure lock regions so unlock always runs, even on randomize failure or early return:
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
endtaskFor 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:
// 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[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 returnQuick 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.