The interviewer turns a laptop towards you. On the screen are the last twenty lines of a simulation log. This came out of last night's regression. Tell me what happened, and what you would check next. There is no waveform, no source code, and no hint. The interviewer is not testing whether you can find the bug in an hour. They are testing whether you can read: which line matters, what the timestamps say, what is missing, and which one question would split the possibilities in half.
This lesson is six such logs, all real, all from the course's DMA bench and its deliberately broken variants (times are in picoseconds; random values differ by simulator and seed). Each one is posed the way an interviewer would pose it, followed by a strong answer and the follow-ups that come next. Practise saying the answers aloud before reading them.
A log is a crime scene, and the last line is where the body was found, not where the crime happened. Start at the bottom: how did the run end? A PH_TIMEOUT fatal means time kept moving and something never finished. A custom fatal names its own story. A list of errors with a normal end means checks fired. A PASS needs the most suspicion of all, because a pass is only as good as the checks that ran. Then go up to the first error, because everything after it may be a consequence. Finally, notice what is missing: a scoreboard summary that never printed tells you report_phase never ran.
"The smoke test hung last night. What happened?"
+UVM_TESTNAME=dma_hang_noack_test
UVM_ERROR @ 175000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_ERROR @ 195000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_ERROR @ 215000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_ERROR @ 235000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_ERROR @ 255000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_ERROR @ 275000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_ERROR @ 295000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_ERROR @ 315000: uvm_test_top.env.in_agt.sqr [uvm_test_top.env.in_agt.sqr] Get_next_item called twice without item_done or get in between
UVM_FATAL base/uvm_phase_hopper.svh(598) @ 5000000: reporter [PH_TIMEOUT] Explicit timeout of 5000000 hit, indicating a probable testbench issueStrong answer. "It ended on PH_TIMEOUT at 5 µs, so simulated time kept advancing: this is a real hang, not a zero-time loop. The first error came from the stream agent's sequencer: the driver called get_next_item again without item_done. So the stream driver is not completing items. Because the first item was never completed, the sequencer hands back the same item on every call, so the driver re-drives one word, every 20 ns, and the DMA, which was programmed for eight words, accepts eight copies and stops taking data. That is why there are exactly eight errors and then silence: the driver is stuck waiting for TREADY. The sequence is still blocked in finish_item of its first item, so the test never gets past starting the packet and its objection is never dropped. Next I would check the driver's loop for a path that skips item_done, and confirm the holder with display_objections or +UVM_OBJECTION_TRACE."
Why is there no `[SB] … words matched` line? A fatal ends the run before report_phase; the scoreboard never got to summarise.
Would the scoreboard have caught this if the run had finished? No. It predicts from the observed input, which really was eight copies of one word, and the output really was eight copies XOR the key. A stimulus-integrity check (sequence items versus monitored beats) is what would catch it.
How do you stop this costing 9200 s of farm time? Set a timeout that fits the bench, and add an activity watchdog (Log 6).
"Two engineers ran what they say is the same test. One got FAIL, one got PASS. Which result do you trust?"
run A: +UVM_TESTNAME=dma_early_end_test
UVM_ERROR dma_tb.sv(407) @ 155000: uvm_test_top.env.sb [SB_EMPTY] no words were compared: the test checked nothing
UVM_INFO dma_tb.sv(411) @ 155000: uvm_test_top.env.sb [SB] 0 words matched, 0 mismatched
UVM_ERROR : 1
UVM_FATAL : 0
TEST VERDICT: FAIL (UVM_ERROR=1, UVM_FATAL=0)
run B: +UVM_TESTNAME=dma_false_pass_test
UVM_INFO dma_tb.sv(407) @ 155000: uvm_test_top.env.sb [SB_EMPTY] no words were compared: the test checked nothing
UVM_INFO dma_tb.sv(411) @ 155000: uvm_test_top.env.sb [SB] 0 words matched, 0 mismatched
UVM_ERROR : 0
UVM_FATAL : 0
TEST VERDICT: PASS (UVM_ERROR=0, UVM_FATAL=0)Strong answer. "Neither run checked anything, so neither result is evidence of a working DMA. Both runs ended at 155 ns with 0 words compared. A smoke test that moves eight words cannot finish in 155 ns, so the run phase ended early: the test dropped its objection before the data moved. Run A is the honest one: SB_EMPTY fired as an error. In run B the same message is printed as UVM_INFO, so someone demoted SB_EMPTY, and the verdict says PASS with nothing compared. That is a false pass. Next I would grep the test and its parents for set_report_severity_id_override and report catchers, and in the test's run_phase look for a fork … join_none followed by drop_objection."
That is exactly the bench: dma_early_end_test starts the packet with fork … join_none and drops its objection at once; dma_false_pass_test adds two unconditional severity overrides on the scoreboard.
// snippet: from the course DMA bench
// False-pass playbook: the same early end, after someone "quietened" the
// scoreboard's end-of-test errors for the whole env. The verdict says PASS
// although the test compared nothing.
class dma_false_pass_test extends dma_early_end_test;
`uvm_component_utils(dma_false_pass_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
env.sb.set_report_severity_id_override(UVM_ERROR, "SB_EMPTY", UVM_INFO); // BUG: unconditional,
env.sb.set_report_severity_id_override(UVM_ERROR, "SB_LEFTOVER", UVM_INFO); // unreviewed waivers
endfunction
endclassHow do you make the regression immune to this? Keep SB_EMPTY an error, make waivers go through a scoped, counted catcher with a ticket, and have the regression script reject any pass whose compare count is zero.
Is there a `set_report_severity_id_override_hier`? No. In UVM 1.2 and IEEE 1800.2 the severity override is per component; the _hier setters exist for verbosity, actions and files.
"One mismatch in a smoke test. Where do you start?"
+UVM_TESTNAME=dma_smoke_test with EXTRA_ARGS=+define+DMA_BUG_WORD6
UVM_ERROR dma_tb.sv(396) @ 375000: uvm_test_top.env.sb [SB_MISMATCH] expected 52c7d685, got ad38297a
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 7 words matched, 1 mismatched
UVM_ERROR : 1
[SB] 1
[SB_MISMATCH] 1Strong answer. "Seven words matched, so the key, the length and the wiring are right: a wrong key would break every word, not one. XOR the two values: 52c7d685 ^ ad38297a = ffffffff. Every bit is inverted, so that word left the DMA as data ^ ~key instead of data ^ key. One word, fully inverted, points at a position-dependent bug in the data path, not a random corruption. Next I would rerun with another key and seed: if the same position fails again, it is the DUT; I would then cut it to a directed test with KEY=0, where the bad word comes out as plain ~data, and file it with expected, actual and the XOR."
The bench's planted bug is exactly that: with DMA_BUG_WORD6 defined, the DUT XORs the sixth word with ~key. The Scoreboard Mismatch Playbook walks the minimal reproducer.
How do you know the model is not the one that is wrong? A second, independent model agrees (the bench's C model through DPI gives the same predictions), and the spec says every word uses KEY.
"A colleague added a reference-model predictor and a comparator next to the scoreboard. Both say eight out of eight. Can we retire the old scoreboard?"
+UVM_TESTNAME=dma_cmp_trap_test
UVM_INFO dma_tb.sv(1530) @ 655000: uvm_test_top.env.cov [COV] plan items hit: 7 of 17
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
UVM_INFO dma_tb.sv(1637) @ 655000: uvm_test_top.env [CMP] class comparator: 8 matches, 0 mismatches
UVM_ERROR : 0
UVM_FATAL : 0Strong answer. "Agreement on a clean run says nothing about whether the new checker can fail. Before I trust it, I break the predictor on purpose and check that the comparator complains. In this bench that experiment is already done: the predictor here is the deliberately buggy one, which predicts every word with the wrong key, and the class comparator still reports eight matches. uvm_in_order_class_comparator compares with the item's compare(), and axis_item has no do_compare and no field macros, so compare() always returns 1. The old scoreboard's 8 matches are real; the comparator's are meaningless. The fix is a do_compare on the item, or uvm_in_order_comparator with an explicit comparison policy, plus a self-test that feeds one known-bad prediction."
How would you catch this class of bug across a whole team? A checker qualification step: every new checker must show one intentional failure before it is trusted.
"The register model reports a mismatch on LEN. Is this a design bug?"
+UVM_TESTNAME=dma_ral_busy_len_test (run.sh scripts/data/uvm-course/bench/dma_ral_tb.sv)
UVM_INFO dma_ral_tb.sv(385) @ 195000: uvm_test_top [RAL] LEN mirror after the write: 9
UVM_ERROR reg/uvm_reg.svh(2812) @ 235000: reporter [RegModel] Register "regmodel.len" value read from DUT (0x0000000000000004) does not match mirrored value (0x0000000000000009) (valid bit mask = 0x000000000000ffff)
UVM_INFO reg/uvm_reg.svh(2833) @ 235000: reporter [RegModel] Field value (regmodel.len[15:0]) mismatch read=16'h4 mirrored=16'h9
UVM_INFO dma_ral_tb.sv(390) @ 235000: uvm_test_top [RAL] DUT LEN (backdoor peek)=4, mirror now 4
UVM_INFO dma_tb.sv(411) @ 515000: uvm_test_top.env.sb [SB] 4 words matched, 0 mismatched
UVM_ERROR : 1
UVM_FATAL : 0Strong answer. "Not necessarily. The mirror expected 9 because the test wrote 9; the DUT reads back 4. So the write reached the bus but the register did not take it. Before calling it a design bug I would check the spec for a rule that blocks the write, such as a register being locked while the engine is busy. Here that is the case: the DMA ignores LEN writes while BUSY, the earlier job had LEN=4, and the model does not know the rule. It is a model gap, not a DUT bug. The fix is to teach the model: a post_predict callback on LEN that keeps the old value while BUSY is set, plus a directed test that writes LEN while busy and expects the old value."
Why does this test turn on auto prediction? With explicit prediction alone, the mirror is updated when the APB monitor publishes, a moment after write() returns. With explicit prediction alone, mirror(UVM_CHECK) would have taken its expected value before the predictor caught up, compared 4 with 4 and passed; the 9-versus-4 mismatch in the log above is caught only because auto prediction is on.
Why is the peek after the check? A backdoor peek() also updates the mirror. Peeking first would have set the mirror to 4 and hidden the error; the log shows "mirror now 4" after the peek.
Is the UVM_ERROR here a failure? It is expected by design in this demo test. In a real bench you would either fix the model or waive it through a scoped, ticketed catcher.
"This one died fast with a fatal from something called WATCHDOG. Talk me through it."
+UVM_TESTNAME=dma_stall_watchdog_test
UVM_FATAL dma_tb.sv(1816) @ 2500000: uvm_test_top.wd [WATCHDOG] no bus activity for 2345000 while the test is still running; last activity at 155000: WR addr=00 data=00000003
UVM_ERROR : 0
UVM_FATAL : 1
TEST VERDICT: FAIL (UVM_ERROR=0, UVM_FATAL=1)Strong answer. "The watchdog says no monitor saw traffic for 2.345 µs while the test still held an objection. The last activity, at 155 ns, was an APB write of 3 to address 0x00: CTRL with GO and IRQ_EN. So the job was started, and then nothing moved on any bus. The DMA is busy waiting for input words, and the test is presumably waiting for the interrupt that only comes after the last word. My first question is whether anything was ever supposed to send data: the stream monitor never published, so either no packet sequence was started, or the stream driver is stuck before its first beat. Here the test never starts a packet, so it is a test bug. Next I would confirm with a rerun that reads STATUS and COUNT (BUSY set, COUNT 0)."
Why have a watchdog when you already have a timeout? The timeout here is 20 µs and the default is 9200 s; the watchdog ends the run eight times sooner and, more importantly, its message says what the last activity was.
When would this watchdog fire falsely? In a test that legitimately waits for a long internal timer. Make the quiet limit a per-test knob; never disable it. It already ignores idle time after the objection count drops to zero.
Jumping to the waveform. "I'd open the waves" is not an answer. Say what you would look for and why, from the log first.
Trusting a PASS. A pass with zero compares, demoted messages or a short run time needs as much scrutiny as a fail.
Calling everything a DUT bug. Half the logs above are testbench or model problems. Interviewers listen for "DUT, model or bench?" as an explicit question.
Who is holding the objection, and what is it waiting for. With no error, nothing complained, so something is simply waiting forever: a wait on an interrupt that never comes, a sequence blocked on a driver, a forgotten drop. display_objections or +UVM_OBJECTION_TRACE names the holder.
It is the difference between the key the scoreboard used and the key the DUT used. One constant XOR across all words means a register-path problem (a KEY write the DUT never took, or one the scoreboard missed), not a data-path bug.
A problem until proven otherwise. Check the compare counts and whether any messages were demoted. A run that short almost always means the run phase ended early because an objection was dropped while work was still in a background thread.
Next: practise on broken benches you have not seen in Debug a Broken Testbench, then move up a level to designing the bench yourself in Architecture Round.