Difficulty: Medium · Puzzle 6 of 6 · Topic: Testbench Debug Puzzles: Why Did the Test Hang?
A virtual sequence hangs after all traffic. The environment wiring has been verified: the monitor's analysis port is connected to the scoreboard instance the vseq watches, and the log shows the scoreboard did trigger its event. Log tail: UVM_INFO @ 8120ns [SB] all 64 items checked, triggering done_ev UVM_INFO @ 8130ns [VSEQ] start_subseqs returned, waiting for done_ev (no further messages; objection never dropped) Why is the vseq still blocked?
class scoreboard extends uvm_scoreboard;
event done_ev;
int checked, expected_n = 64;
function void write_rsp(rsp_t r);
// ... compare ...
if (++checked == expected_n) begin
\`uvm_info("SB", "all 64 items checked, triggering done_ev", UVM_LOW)
-> done_ev;
end
endfunction
endclass
class top_vseq extends uvm_sequence;
virtual task body();
start_subseqs(); // runs the traffic sequences to completion
\`uvm_info("VSEQ", "start_subseqs returned, waiting for done_ev", UVM_LOW)
@(p_sequencer.env.sb.done_ev); // hangs
endtask
endclassCompare the time the event was triggered with the time the wait started.
1) Rule out wiring first: print sb.get_full_name() in write_rsp and in the vseq, and the checked counter; here they match, and the scoreboard really did trigger done_ev.
2) Compare timestamps: the trigger at 8120ns is before the wait begins at 8130ns.
3) @(event) is edge-sensitive: it only wakes for a trigger that happens after the wait starts. A trigger that already fired is lost (verified in simulation).
4) Fix: use a sticky condition. Either a flag with wait(sb.all_done), or a uvm_event with wait_on() (returns immediately if the event is already on), or check the counter before waiting.
5) Add an assertion that the vseq end condition cannot be missed: after the wait, check checked == expected_n.Answer: Bug: the scoreboard triggers a plain SystemVerilog event before the vseq starts waiting on it; @(event) misses a trigger that has already happened. Fix: make the condition sticky (flag + wait(), or uvm_event::wait_on()) instead of an edge-sensitive event.
Checked by running the code in Verilator 5 and Icarus Verilog 13; the printed output supports the answer above. Plain SV shows an @(event) wait misses a trigger that already fired, while a sticky flag does not; uvm_event itself is not run. Plain SV shows an @(event) wait misses a trigger that already fired, while a sticky flag does not; uvm_event itself is not run.
Candidates blame TLM wiring because that is the usual cause, but the log here proves the trigger happened; the bug is the ordering between trigger and wait.
How would you automatically validate critical TLM connections during build/connect, so wiring bugs are caught before simulation? Model answer: In end_of_elaboration_phase (or check at start_of_simulation) assert that each analysis port has the expected number of subscribers (ap.size() and the connected imp paths), and at end of test fail if a scoreboard that should have seen traffic saw zero items.
The log proves the trigger happened, so I do not blame the TLM wiring and look at the order of trigger and wait instead. The root cause is that the scoreboard triggers a plain SystemVerilog event before the virtual sequence starts waiting, and an edge-sensitive wait misses a trigger that already happened. I confirm it by comparing the trigger and wait timestamps. The fix is to make the condition sticky, with a flag and wait, or uvm_event wait_on.