Difficulty: Medium · Puzzle 1 of 6 · Topic: Testbench Debug Puzzles: Why Did the Test Hang?
Regression hangs in about 20 of 200 seeds (roughly 1 in 10). Last log says the sequence is blocked in finish_item(). The item class constrains kind with dist { NORMAL := 999, SKIP_ME := 1 }, so a 100-item sequence contains a SKIP_ME item in about 1 seed in 10. Find the exact liveness bug and fix it without changing stimulus intent.
class my_item extends uvm_sequence_item;
rand bit [31:0] addr;
rand kind_e kind; // NORMAL or SKIP_ME
constraint c_kind { kind dist { NORMAL := 999, SKIP_ME := 1 }; }
// ...
endclass
class req_seq extends uvm_sequence #(my_item);
\`uvm_object_utils(req_seq)
virtual task body();
my_item req;
repeat (100) begin
req = my_item::type_id::create("req");
start_item(req);
assert(req.randomize());
finish_item(req);
end
endtask
endclass
class my_driver extends uvm_driver #(my_item);
\`uvm_component_utils(my_driver)
virtual task run_phase(uvm_phase phase);
my_item req;
forever begin
seq_item_port.get_next_item(req);
if (req.kind == SKIP_ME) begin
continue; // bug
end
drive_req(req);
seq_item_port.item_done();
end
endtask
endclassAudit get_next_item/item_done pairing on every control-flow path, not just the common path.
1) Reproduce one failing seed and add driver logs around get_next_item/item_done with the item id. The hang rate (about 1 seed in 10) matches the rare SKIP_ME item, not a common path.
2) The last driver log is "got item N (SKIP_ME)" with no matching "done N"; the sequence log shows finish_item(N) never returned.
3) The continue jumps back to get_next_item without item_done. The sequence stays blocked in finish_item for the old item, and the new get_next_item finds nothing to grant (in UVM 1.2 calling get_next_item twice without item_done is a UVM_ERROR, after which the call blocks; older versions may behave differently).
4) Refactor so item_done is called exactly once per get_next_item on every path: if (req.kind != SKIP_ME) drive_req(req); seq_item_port.item_done();
5) Add a counter check: outstanding get_next_item minus item_done equals zero at end of test, and a directed test that forces SKIP_ME on item 1.Answer: Bug: the early continue on SKIP_ME skips item_done, so finish_item never returns and the driver then calls get_next_item on a handshake that is still open. Fix: call item_done exactly once per get_next_item on every path (if (kind != SKIP_ME) drive_req(req); item_done();).
Checked by running the code in Verilator 5; the printed output supports the answer above. Plain-SV model of the driver handshake (mailbox + done counter), not the UVM library: the skipped item leaves finish_item stuck, the fixed driver completes all five.
Raising the global timeout or rerunning the failing seed with more time does nothing: the handshake is open forever. The tell is the low hang rate matching a rare item kind, which points at a rarely-taken driver branch.
How would you instrument sequencer-driver latency so this class of bug is caught within 100 cycles? Model answer: Wrap the pull side: timestamp every get_next_item and assert (or count in a watchdog thread) that item_done follows within N cycles, e.g. a driver-side counter of open handshakes with an error if it stays at 1 for more than 100 clocks; the same check on the sequence side bounds finish_item latency. The check fires at the first stuck item instead of at the global timeout.
The hang rate of one seed in ten matches the rare SKIP_ME item, so I look at the driver branch that item takes. The log shows the driver got that item and never printed done, so the sequence is still blocked in finish_item. The root cause is the continue, which skips item_done and leaves the handshake open. I confirm it with a directed test that forces SKIP_ME on item one, and the fix is to call item_done exactly once per get_next_item on every path.