Verification

UVM heartbeat and objections

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: UVM objections keep a phase alive while work is pending and end it cleanly when the last objection drops, while the heartbeat watchdog fires if components stop making progress — together they stop a testbench from both exiting too early and hanging forever.

    Two of the most misunderstood mechanisms in a UVM environment control when a simulation is allowed to finish: the objection mechanism and the heartbeat. Get them wrong and you will either see a test "pass" in 40 ns because nobody held the run phase open, or watch a regression burn a 10-hour license seat on a testbench that silently deadlocked. This article explains how both work, why they exist, and the practical patterns experienced verification engineers use in Synopsys VCS, Cadence Xcelium and Siemens Questa.

    Why end-of-test is hard

    A UVM phase such as run_phase runs concurrently across every component. There is no single thread that "knows" the test is done. The sequence driving stimulus, the scoreboard waiting for the last expected transaction, and the monitor sampling the bus all live in different fork branches. Something has to arbitrate the moment when it is safe to stop. That arbiter is the objection.

    An objection is a distributed counter owned by uvm_phase. Any component can raise_objection() to say "do not end this phase, I am still busy", and must later drop_objection(). When the total count across the whole hierarchy returns to zero, the phase ends, phase_ready_to_end is called, and the simulation advances. If nobody ever raises an objection, the run phase has nothing to wait for and the kernel ends it immediately.

    Objection lifecycle raise (count = 1) sequence body runs drop (count = 0) phase held open (count > 0) simulation time phase_ready_to_end CourseTron · coursetron.com
    An objection holds the run phase open from first raise to last drop.

    Where to raise objections — and where not to

    The modern convention is that sequences, not components, own objections for stimulus. Since UVM 1.2 a sequence can set starting_phase.raise_objection(this) in pre_start and drop it in post_start, or more cleanly use set_automatic_phase_objection(1) so the library raises and drops around body() automatically. Raising an objection per transaction is an anti-pattern: it creates thousands of counter updates and serialises the simulator's event scheduler, which measurably slows long random tests.

    Rule of thumb: raise exactly one objection at the top of your stimulus and drop it once when the last expected result has been checked — not once per packet. A single raise/drop pair around a 50,000-item sequence is correct; 50,000 pairs is a performance bug.

    Draining: giving the DUT time to settle

    Real designs have latency. When your last stimulus item is driven, the DUT pipeline, FIFOs and the scoreboard still need time to flush. If you drop the objection the instant the driver is idle, you may end the test before the final response arrives and miss a real bug. UVM provides a drain time for exactly this: phase.phase_done.set_drain_time(this, 200ns) tells the phase to wait an extra 200 ns after the objection count hits zero before ending. For event-driven closure instead of a fixed wait, override phase_ready_to_end() and re-raise the objection until a completion predicate (e.g. "scoreboard queue empty") is true.

    MechanismWhat it controlsTypical value
    set_drain_timeFixed delay after count reaches 0100–1000 ns
    phase_ready_to_end overrideCondition-based extensionuntil scoreboard drained
    Heartbeat windowMax idle time before fatal1k–100k ns
    Drain time extends the phase last stimulus driven count reaches 0 phase ends drain time, e.g. 200 ns DUT pipeline and scoreboard flush set_drain_time(this, 200ns), or re-raise in phase_ready_to_end CourseTron · coursetron.com
    After the count reaches zero, the drain time holds the phase open before it ends.

    The heartbeat: catching hangs instead of exiting early

    Objections solve "ended too soon". The heartbeat solves the opposite failure: a testbench that never ends because a component stalled waiting on an event that will never fire. A uvm_heartbeat watches a list of registered components and an associated uvm_objection. On every tick of a user-supplied event (often a periodic clock-divided event), it checks that every registered component has "bumped" the heartbeat at least once in the window. If any component has gone silent, the heartbeat issues a UVM_FATAL and the simulation stops with a clear message instead of timing out in regression.

    You configure it with one of three modes: UVM_ALL_ACTIVE (every component must beat each window), UVM_ONE_ACTIVE (at least one), or UVM_ANY_ACTIVE. For a data-path scoreboard, UVM_ALL_ACTIVE is the strongest signal that forward progress is real.

    Heartbeat watchdog: a healthy window beat detected One pulse per window from each registered component CourseTron · coursetron.com
    Each window the heartbeat expects a pulse from registered components; a flat line triggers a fatal.

    Field note: A common Xcelium/VCS regression failure is a test that runs for the full wall-clock timeout with no error. Nine times out of ten this is a missing drop (objection never released) or a stalled consumer — a heartbeat on the scoreboard converts that silent 10-hour waste into a 5,000 ns UVM_FATAL that points at the guilty component.

    Putting it together: a robust end-of-test recipe

    A production environment usually layers all three ideas. The virtual sequence owns a single objection around the whole test. The scoreboard sets a drain time or overrides phase_ready_to_end so late responses are not lost. A heartbeat guards against stalls. See how these interact with the broader phasing model in our companion piece on UVM phases explained, and how to drive it from stimulus in UVM virtual sequences.

    Teams building reusable VIP — the kind of methodology work done at design-services houses like Avecas VLSI design services — standardise this into a base test so every derived test inherits correct objection, drain and heartbeat behaviour, eliminating a whole class of "my test ends early" bugs.

    Want to master UVM end-of-test, phasing and sequencing hands-on with real EDA tools? Explore the Verification track on our CourseTron courses page.

    Frequently asked questions

    Why does my UVM test finish instantly at time 0?

    No objection was raised. If your sequence does not raise an objection (or use set_automatic_phase_objection) and no component holds run_phase open, the phase has nothing to wait for and ends immediately. Add a single raise/drop pair around your stimulus.

    What is the difference between drain time and a heartbeat?

    Drain time extends the phase after objections drop, to let the DUT flush. A heartbeat is an independent watchdog that fails the test if components stop making progress. One prevents ending too soon; the other prevents hanging forever.

    Should every component register with the heartbeat?

    No. Register only components that must make continuous forward progress — typically active agents and the scoreboard. A passive coverage collector that beats irregularly will cause false fatals under UVM_ALL_ACTIVE.

    Does raising an objection per transaction hurt performance?

    Yes, noticeably on long tests. Each raise/drop walks the component hierarchy and schedules events. Use one objection for the whole sequence; this can save meaningful runtime across a large regression.

    Advertisement