Protected Course Page
Enter the password to view Day 12 of the UVM 30-Day plan.
Day 12: End-of-Test and Exceptions — Drain Time, Resets, Error Injection, Timeouts
A 30-day SystemVerilog/UVM verification series. Most "mysterious UVM failures" are actually end-of-test bugs. Today we make test endings boring.
Two failure modes account for more debugging time in UVM than everything else combined: the test that ends too early (drops objection while traffic is still in flight), and the test that never ends (deadlock, missing objection drop, missing transaction). Both are end-of-test problems. Both are solvable with a small number of disciplined patterns.
1. Objection Drain Time
An objection keeps the phase alive. When all objections drop, the phase ends. That sounds simple, and it is — except when you drop the last objection while three drivers are still mid-transaction. The sim ends mid-drive, the scoreboard sees truncated traffic, and the test reports a false pass or a confusing failure.
The fix is drain time: after all real stimulus is issued, hold the objection a little longer to let in-flight traffic complete. Two ways to implement:
1.1 Phase drain time
class fifo_test extends uvm_test;
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
phase.phase_done.set_drain_time(this, 100ns);
// ... stimulus ...
phase.drop_objection(this);
endtask
endclass
set_drain_time ensures the phase does not end for at least 100ns after the last objection is dropped. Simple, deterministic, and the standard pattern for small DUTs.
1.2 Explicit completion check
For larger DUTs, 100ns is either too short or too long. The better pattern is to wait for a concrete completion signal — the scoreboard's shadow queue is empty, or a specific uvm_event is triggered:
phase.raise_objection(this);
// ... stimulus ...
wait (env.sb.expected.size() == 0 && env.sb.outstanding == 0);
phase.drop_objection(this);
2. Reset Handling
Mid-test resets break scoreboards, sequences, and drivers in ways that are rarely obvious. Three patterns are common:
2.1 Reset as a separate phase
Treat reset as its own scenario. Before stimulus, apply reset, wait for it to release, sample the interface to confirm the reset value, then start stimulus. Do not mix reset with running stimulus in a base test — it makes failures ambiguous.
2.2 Reset-aware scoreboard
If the test does reset mid-run, the scoreboard must flush its expected queue. Otherwise the expected data from before reset collides with observed data after reset and every subsequent comparison fails.
function void write_rst(fifo_tx tr);
if (!tr.rst_n) begin
`uvm_info("SB", "reset seen — flushing expected queue", UVM_LOW)
expected.delete();
num_exp = 0;
end
endfunction
Wire the monitor's reset detection into the scoreboard via a dedicated analysis import. The scoreboard must know a reset happened; otherwise it will report the flushed data as mismatches.
2.3 Reset-aware driver
A driver mid-handshake when reset asserts will drive stale data. Either delay the driver with a reset-aware gate, or use a callback to skip driving while rst_n is low.
3. Error Injection
Real DUTs are required to detect errors, not just work correctly on good data. That means the testbench must inject errors and check that the DUT flags them. Three standard mechanisms:
- Factory override with an error-injecting driver — swap the whole component for the test.
- Callback that mutates transactions before driving — inject on a subset of items without touching the driver.
- Config flag read by the driver in
run_phase— the least flexible but easiest to understand.
The scoreboard must also be error-aware. If it expects wdata=0x10 and the DUT correctly flags an error because the driver sent 0x11, the scoreboard should record "error expected, error observed" as a pass — not a mismatch. That requires the scoreboard to know which transactions were intentionally corrupted.
function void write_exp(fifo_tx tr);
if (tr.corrupt_expected) begin
// This item is expected to cause an error — record it in a separate queue
err_expected.push_back(tr);
return;
end
expected[tr.id] = tr;
endfunction
uvm_is_match alone. The uvm_error counter in the report is global. An "expected error" that the testbench reports as uvm_error will show up in the report and the regression will fail. Either raise the error severity to UVM_INFO in the check-path when a corrupt transaction is expected, or wrap the check with a custom macro that increments a separate counter.
4. Timeout Patterns
A test that hangs is worse than a test that fails — the regression stalls, CI blocks, and someone has to manually kill the job. Three layers of timeout:
4.1 Global phase timeout
// In the top module, before run_test():
initial begin
uvm_top.set_timeout(1ms, 0); // 0 = do not print stack on timeout
run_test();
end
If run_phase exceeds 1ms of simulation time without an objection drop, the test fatals. This catches the "forgot to raise/drop objection" class of bug.
4.2 Per-blocking-operation timeout
task body();
fork
begin
seq_item_port.get_next_item(req); // or any blocking call
// handle
end
begin
#1ms;
`uvm_fatal("TIMEOUT", "get_next_item timed out")
end
join_any
disable fork;
endtask
Wrap the blocking call in a fork/join_any with a timeout branch. If the branch fires, the test fatals with a specific message instead of hanging. This is how you diagnose scoreboard deadlocks (Day 9's warning) in real time.
4.3 Scoreboard queue timeout
In the scoreboard's check_phase, if expected.size() != 0 after run_phase, report it as a fatal — not just an error. That way the test fails fast instead of waiting for the drain time to expire.
5. What Day 13 Will Add
Tomorrow: Debugging and Reporting. uvm_info verbosity, ID filtering, transaction recording with uvm_tr_database, and waveform debugging strategies. All the tools that turn "the test failed and I don't know why" into "I see exactly which transaction was wrong at which cycle."
6. Quiz
Ten questions on drain time, reset handling, error injection, and timeouts.
Coming Up Next: Day 13 — Debugging and Reporting
- uvm_info verbosity — when to use UVM_NONE, UVM_LOW, UVM_MEDIUM, UVM_HIGH, UVM_DEBUG, and how to filter by ID.
- Transaction recording —
uvm_tr_databaseand how to get transactions into the waveform viewer. - Waveform debugging strategies — what to look at first when the scoreboard reports a mismatch.
- Log parsing — how to grep a UVM log for the actual failure among a thousand UVM_INFO lines.
Series: 30 days of SystemVerilog/UVM verification. Day 12's end-of-test discipline.
No comments:
Post a Comment