The nightly regression has one red test, and its log ends like this:
UVM_FATAL uvm_phase_hopper.svh(598) @ 20000000: reporter [PH_TIMEOUT] Explicit timeout of 20000000 hit, indicating a probable testbench issue
UVM_FATAL : 1No error before it, no mismatch, nothing. Something has been waiting for most of 20 microseconds. The question is what, and the answer is almost never found by staring at the last line. This lesson gives you a short map of every place a sequence-driven test can wait, and an order to check them in.
Every hang in a UVM test is a blocking call whose wake-up never comes. In a sequence-driven bench there are only a handful of those calls, and each has a different owner. Think of a stuck order at a restaurant: the slip may never have reached the pass, the cook may have it and be stuck, the cook may have finished but never rung the bell, or the waiter may be standing at a table waiting for a customer who left. You do not search the whole restaurant; you ask who is holding the slip.
Where it waits | Whose wake-up is missing | Typical cause |
|---|---|---|
Sequence in | The sequencer's grant | No driver pulling (inactive agent, driver crashed or not built), wrong sequencer, another sequence holds a lock or grab |
Sequence in | The driver's | A driver path that skips |
Driver inside a transfer | A pin condition | PREADY or TREADY never rises: DUT in reset, wrong clock, interface not connected |
Test waiting on the DUT | A DUT event | The interrupt is never enabled, or the job never started (as in |
Phase never ends | An objection drop | A raise without a drop on some path |
A hung test with no timeout burns a regression slot until the default of 9200 seconds of simulated time, which in practice means the farm kills it with no UVM summary at all. Set a timeout that is generous for the test but short for a hang. The bench's dma_timeout_test does it in code. The command-line form is +UVM_TIMEOUT=20000000,YES; the value is a plain integer in the library's time unit (picoseconds on this bench, as the PH_TIMEOUT line shows), not a string such as 20us:
// snippet: from dma_timeout_test::build_phase
uvm_root::get().set_timeout(20us, 1); // replaces a +UVM_TIMEOUT=...,YES value; ,NO locks the command line+UVM_OBJECTION_TRACE logs every raise and drop with the object that made it. If the last raise in the trace is the test's and there is no matching drop, the test's run_phase is the thread to examine. +UVM_PHASE_TRACE confirms which phase you are stuck in. In the timeout test, the trace would end with the test's raise: it is blocked in run_job, on wait (irq_vif.irq === 1'b1), for an interrupt that the job disabled with irq_en == 0. A DUT event, not the handshake.
When the test is blocked on a sequence, the sequence's state tells you how far it got. get_sequence_state() returns UVM_BODY for a sequence still running its body; combine it with the last item the driver logged. A small watchdog in the test prints it before the timeout fires:
// snippet: a debug fork in the test, started next to the stimulus
fork
begin
#15us; // shortly before the 20 us timeout
`uvm_info("HANG", $sformatf("pkt state=%s, apb grabbed=%0b, axis has_do_available=%0b",
pkt.get_sequence_state().name(), env.apb.sqr.is_grabbed(),
env.in_agt.sqr.has_do_available()), UVM_NONE)
end
join_noneState `UVM_BODY`, and the driver logged nothing for it: the sequence is in start_item. Check that the agent is active, that the driver was built and connected (drv.seq_item_port.connect(sqr.seq_item_export)), that the sequence was started on the sequencer you think, and whether is_grabbed() shows another sequence holding it.
State `UVM_BODY`, and the driver logged "got" for its last item: the sequence is in finish_item, so the driver never called item_done. Look for a path that skips it, or a pin wait that never ends.
`has_do_available()` is 1 but the driver is quiet: a sequence is waiting for a grant and nobody is pulling. The driver thread has died or was never started.
Raise verbosity only where you need it. The component field of +uvm_set_verbosity is matched against the full name, so +uvm_set_verbosity=*.apb.drv,_ALL_,UVM_HIGH,run works and env.apb.drv,... does not. A driver that logs "got" and "done" at UVM_HIGH around each transfer turns this check into a one-line grep.
A driver waiting for PREADY that never comes is the most common handshake hang on real designs, and the cheapest to make loud. Count the clocks and fail with the item in the message:
// snippet: inside apb_driver::drive, replacing the plain access-phase wait
int unsigned waits = 0;
do begin
@(posedge vif.clk);
if (++waits > 64)
`uvm_fatal("APB_HANG", {"no PREADY after 64 clocks: ", it.convert2string()})
end while (!vif.pready);A small lab bench (seq_lab_tb.sv: the same UVM library, a one-sequencer agent, and a driver that "skips" its second item with an early continue before item_done) shows what the bookkeeping errors look like. The sequence sends four items:
UVM_INFO seq_lab_tb.sv(139) @ 22000: uvm_test_top.env.drv [DRV] got s1_0 (call 1)
UVM_INFO seq_lab_tb.sv(139) @ 35000: uvm_test_top.env.drv [DRV] got s1_1 (call 2)
UVM_ERROR @ 35000: uvm_test_top.env.sqr [uvm_test_top.env.sqr] Get_next_item called twice without item_done or get in between
UVM_INFO seq_lab_tb.sv(139) @ 35000: uvm_test_top.env.drv [DRV] got s1_1 (call 3)
UVM_INFO seq_lab_tb.sv(139) @ 55000: uvm_test_top.env.drv [DRV] got s1_2 (call 4)
UVM_INFO seq_lab_tb.sv(139) @ 75000: uvm_test_top.env.drv [DRV] got s1_3 (call 5)
UVM_ERROR : 1The driver went back for an item without releasing the one it held, and the sequencer handed it the same item again (s1_1 on calls 2 and 3), because an item stays at the head of the sequencer's request queue until item_done removes it. Here the skip happened once, so the run recovers with one error. A skip condition that is true every time can never get past that item, and the sequence waits in finish_item for good. Either way, that Get_next_item called twice line is worth a search in every hung log.
A DMA regression started timing out on one seed in forty. Two engineers spent a day on the sequences: the stream sequence was in finish_item, so they audited every item_done path in the stream driver and found nothing wrong. The driver was in its TREADY loop, waiting for the DUT to accept a beat. The DUT never did, because on that seed dma_job_cfg had randomized len to 1 while the test streamed more words: the DMA took one word, finished, and dropped TREADY for good. The stream driver waited, correctly, for a ready that would never come.
The bug was in the test (it streamed a fixed number of words instead of job.len), but the lesson was about triage order. They had started from the handshake because the sequence was blocked in finish_item, when the driver's own state, inside a pin wait, pointed straight at the DUT. A clock-counting guard in the driver, like the one above, would have printed the stuck beat and the DMA's state on the first failure.
env.out_agt.sqr never sends anything. out_agt is the passive output agent. What do you see, and why?In the bench, a passive agent never builds its sequencer, so env.out_agt.sqr is null. The sequence starts with no sequencer, and its first start_item fails at once with a SEQ fatal ("neither the item's sequencer nor dedicated sequencer has been supplied"). In a design where the sequencer exists but no driver is connected to it, the sequence instead blocks in start_item forever, because a grant only happens when a driver asks for an item.
pkt.start(...), and the stream driver's last message is "got" for beat 5 of 8. Where do you look next?The sequence is in finish_item for beat 5, so the driver has not called item_done. Either a code path in the driver skips it, or the driver is inside its TREADY wait. Check TREADY on the waveform (or the driver's guard message): if the DUT stopped accepting, look at the DUT's state and the job it was given, not at the sequence.
UVM_ERROR before the timeout?Because every party is doing exactly what it should: the test waits for a sequence, the sequence waits for item_done, the driver waits for a ready signal. No rule is broken; one event simply never happens. That is why timeouts, objection traces and clock-counting guards matter: they convert silent waiting into a message with a location.
Next: the handshake has a lot more machinery than four calls. The Sequence mechanics topic opens it up, starting with what seq.start() does, step by step.