TO Be coming

Nov 14, 2026

UVM Course :Day 12: End-of-Test and Exceptions — Drain Time, Resets, Error Injection, Timeouts

Day 12: End-of-Test and Exceptions — Drain Time, Resets, Error Injection, Timeouts
Advertisement
Top banner — drop your 728×90 or responsive ad unit here.

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);
Rule: the objection should be dropped when the testbench is done observing, not when it is done driving. Those are different moments whenever a DUT pipelines or buffers.

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
Do not gate error checking on 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.

Layered timeouts: the global phase timeout catches the "forgot objection" case. The per-operation timeout catches the "blocked forever on a get" case. The scoreboard leftover check catches the "never came" case. Together they turn every hang into an actionable failure with a specific message.

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.

Question 1 of 10
Loading…
    0 / 10
    Advertisement
    Bottom banner — drop your 728×90 or responsive ad unit here.

    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_database and 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.
    ← Back to the 30-Day UVM Course
    See all 30 days, week by week.

    Series: 30 days of SystemVerilog/UVM verification. Day 12's end-of-test discipline.

    No comments:

    Post a Comment

    SiteMap