Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
    • The Phase System
    • UVM Phases: Build, Connect, Elaborate and Run
    • Objections Deep Dive: Drain Time and Propagation
      • Objection Hierarchy Propagation: Up the Component Tree
      • Objection Counting Rules: Raises, Drops, and Edge Cases
      • set_drain_time Deep Dive: Grace Period After Objections Clear
      • phase_ready_to_end: Last-Chance Phase Extension
      • End-of-Test Strategy: Choosing a Mechanism
      • Objections at Scale
    • Phase Debugging and End-of-Test Signoff
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Objections Deep Dive: Drain Time and Propagation
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. Objections Deep Dive: Drain Time and Propagation
  5. End-of-Test Strategy: Choosing a Mechanism

Part 2 · Phases & Lifecycle · Intermediate

End-of-Test Strategy: Choosing a Mechanism

Test-held objection, drain time, phase_ready_to_end, scoreboard-held objection and heartbeat: what each guarantees, how each fails, and how to choose per bench.

On this page

    In a design review, a lead asks you one question about the new block bench: how does a test know it is finished? You can answer "we raise an objection in the test", and you will be asked what happens to the last transaction, what happens when the DUT hangs, and what happens when the same env runs inside the SoC bench, where your test no longer exists. The real question is about strategy, not syntax.

    This lesson lays out the five mechanisms UVM gives you, what each one actually guarantees, how each one fails, and how to choose. The worked comparison is the DMA bench itself, which ends two different ways.

    The idea: who knows when the work is done?

    Ending a test is like closing a restaurant kitchen. The manager can close when the last order is taken (the test drops when stimulus ends), wait a fixed ten minutes after that (drain time), ask the cooks before turning off the lights (phase_ready_to_end), let the head chef keep the kitchen open while any order is in progress (a scoreboard-held objection), or have someone check every few minutes that the cooks are still moving (a heartbeat). Each answers a different question. The right choice depends on who actually knows that the work is done.

    For most designs, the test knows when stimulus is done, and only the checker knows when checking is done. Good strategies combine the two, and add a watchdog for the day neither happens.

    The five mechanisms side by side

    Mechanism

    Who decides

    Guarantees

    Fails as

    Test-held objection

    The test

    Stimulus ran to completion

    Early end if the test drops before the DUT finishes; hang if a wait never returns

    Drain time

    A constant

    A fixed settle period after the last drop

    Lost tail items when latency grows; wasted time on every test when oversized

    phase_ready_to_end hold

    A checker, at count zero

    The phase cannot end while the checker reports work outstanding

    False pass if 'outstanding' is read from a lagging queue; hang if it never drains

    Scoreboard-held objection

    A checker, continuously

    Phase held from first expected item to last compare

    Hang on a lost item; cost and trace noise if raised per item

    Heartbeat or watchdog

    A timer

    Detects a bench that stopped making progress

    Says nothing about correctness; false alarms during legitimately idle periods

    Note what is missing from every row: none of them, alone, both ends at the right moment and protects against a hang. That is why real benches layer them.

    The picture: choosing per bench

    Decision flow: if the test can observe completion, hold the test's objection until done; otherwise if the tail is fixed use a drain; otherwise use a scoreboard hold keyed on owed work. All paths add a timeout, and a heartbeat at SoC scale.
    Start from who knows the work is done. Whatever you choose, keep a timeout so a hang costs minutes, not the farm's time limit.

    Worked comparison: the two DMA tests

    The bench ends its tests two different ways, and both pass. Seeing why tells you when to use each.

    dma_smoke_test

    dma_drain_test

    Who holds the phase

    The test, until the interrupt and STATUS checks, plus #100ns

    The test until the last input word; then dma_scoreboard_eot

    How 'done' is known

    The DUT's done interrupt, observed by the test

    LEN words per GO, counted by the scoreboard from the APB monitor

    Tail handling

    A fixed 100 ns guess

    Exactly as long as words are owed: 10 ns here

    Run ends at

    655 ns (8 words)

    375 ns (6 words)

    Survives at SoC level

    No: the block test is replaced by a chip test

    Yes: the scoreboard travels with the env

    Weak spot

    Tail longer than 100 ns gives SB_LEFTOVER

    A lost word hangs until the timeout unless the wait is bounded

    The smoke test is the right shape when the test can observe completion, which the DMA's interrupt allows. Its only flaw is the fixed #100ns for the output pipeline. The drain test is the right shape when the stimulus side cannot know when checking is complete, and it is the one that keeps working when the block env is reused in a bigger bench, because the end-of-test knowledge lives in a component that is reused with it.

    A good production bench for this DMA would combine them: the test holds its objection until the interrupt (it owns the scenario), the scoreboard holds the tail with an owed-word count (it owns checking), and set_timeout caps the whole run.

    The watchdog layer

    Every bench needs a hang limit. The global timeout ends the run phase with a PH_TIMEOUT fatal: set it with +UVM_TIMEOUT=<time>,<YES|NO> or uvm_root::get().set_timeout(t, overridable). The default is 9200 s of simulated time, far too long to be useful, so set it per test class. dma_timeout_test uses 20 µs:

    Diagram
    UVM_FATAL uvm_phase_hopper.svh(598) @ 20000000: reporter [PH_TIMEOUT] Explicit timeout of 20000000 hit, indicating a probable testbench issue
    UVM_FATAL :    1

    A timeout only says too long. A heartbeat says stopped moving, which catches a stalled bench early in a long test. uvm_heartbeat watches a dedicated objection: each monitored component raises and drops it whenever it makes progress, and at every trigger of an event the heartbeat checks the window that just closed. In UVM_ALL_ACTIVE mode every registered component must have been active in the window, otherwise it reports an HBFAIL fatal naming the silent one. The version below needs no change to the agents: a small subscriber on the output monitor's analysis port turns each observed word into a pulse.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // snippet
    // One heartbeat pulse per observed output word.
    class hb_pulse extends uvm_subscriber #(axis_item);
      `uvm_component_utils(hb_pulse)
      uvm_callbacks_objection hb_obj;
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
      function void write(axis_item t);
        hb_obj.raise_objection(this);
        hb_obj.drop_objection(this);
      endfunction
    endclass
    
    class dma_env_hb extends dma_env;
      `uvm_component_utils(dma_env_hb)
      hb_pulse      pulse;
      uvm_callbacks_objection hb_obj;  // UVM 1.2 needs this type; in IEEE 1800.2 it is a typedef of uvm_objection. Not a phase objection.
      uvm_heartbeat hb;
      uvm_event     hb_tick;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        pulse = hb_pulse::type_id::create("pulse", this);
      endfunction
    
      function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        hb_obj       = new("hb_obj");
        pulse.hb_obj = hb_obj;
        out_agt.ap.connect(pulse.analysis_export);
        hb_tick      = new("hb_tick");
        hb           = new("hb", this, hb_obj);
        void'(hb.set_mode(UVM_ALL_ACTIVE));
        hb.add(pulse);
      endfunction
    
      task run_phase(uvm_phase phase);
        hb.start(hb_tick);
        forever #10us hb_tick.trigger();   // one window per 10 us
      endtask
    endclass

    In a variant of dma_timeout_test with this env and the timeout raised to 100 µs, the DMA moves its 4 words in the first microsecond and then the bench goes silent, waiting for an interrupt that never comes. The heartbeat fails the run at 20 µs, at the end of its second window, long before the timeout would. With dma_smoke_test the same env passes, because the run is over before the first window closes:

    Diagram
    UVM_FATAL @ 20000000: uvm_test_top.env [HBFAIL] Did not recieve an update of hb_obj for component uvm_test_top.env.pulse since last event trigger at time 10000000 : last update time was 315000

    The first trigger only opens a window; each later trigger checks the window that just closed. The misspelling is the library's own message text. In a block test with idle gaps longer than the window, it raises false alarms; it pays off in long SoC tests where a stalled subsystem would otherwise sit silently for hours.

    What breaks: the env that ended other people's tests

    A block team built their end-of-test into the test: raise, run, wait for the interrupt, drop. It was clean and it passed for a year. Then the block env was reused in the subsystem bench, where a different test with its own virtual sequence drove three DMAs. Subsystem tests began passing with words uncompared, because the subsystem test dropped its objection when its stimulus finished, and nothing in the reused env knew that a DMA's output pipeline was still busy.

    The fix moved end-of-test knowledge into the reusable part: the scoreboard learned owed work from the register monitor and held the phase in phase_ready_to_end. The block tests did not change at all. The lesson: put end-of-test knowledge where the knowledge lives, which for checking is the checker, because the test is the one component that is never reused.

    Interview

    "How do your tests end? Defend the choice."

    A weak answer: "The test raises an objection at the start of run_phase and drops it at the end, with a drain time." It describes one mechanism and defends nothing.

    A strong answer: "Layered. The test holds an objection for the scenario, until the DUT signals done, because it owns stimulus. The scoreboard owns the tail: it counts outstanding work from what the DUT was asked to do and holds the phase in phase_ready_to_end, so the end is exact and survives reuse at SoC level, where our test is replaced. Drain time is only for a fixed, measured settle. Every test sets a timeout, and long SoC tests add a heartbeat on the output monitors so a stall fails in minutes."

    Follow-ups: Why not just a big drain time? (it is a constant guess: too short loses tail items, too long taxes every test). Can a monitor raise the objection instead? (monitors never raise; end of test is owned by the test and the checker).

    Check yourself A design has no done interrupt and a latency that varies from 10 to 2,000 cycles with traffic. Which mechanism should end its tests?

    A scoreboard hold keyed on outstanding work, in phase_ready_to_end. The test cannot observe completion, and a drain long enough for 2,000 cycles would waste time on every test. Add a bounded wait and the global timeout so a lost transaction becomes an error, not a hang.

    Check yourself A heartbeat on the output monitor fires HBFAIL during a test that deliberately idles the DUT for 50 µs to test a low-power mode. What do you change?

    Either stop the heartbeat around the idle period (hb.stop(), then hb.start()) or lengthen the window for that test. The heartbeat measures progress, and this test legitimately makes none for a while; the failure is a configuration issue, not a DUT bug.

    Check yourself Your block env's tests all end correctly. Why might the same env end subsystem tests too early?

    If end of test lives in the block test, it disappears when the subsystem test replaces it. The subsystem test drops when its stimulus ends, while the block's output pipeline is still busy. Moving the tail hold into the block's scoreboard makes the env carry its own end-of-test knowledge.

    Next: at SoC scale, objections themselves start to cost time. Objections at Scale covers the cost model and the tools to control it.

    Related topics

    • Objections at scale

    • phase_ready_to_end

    • Objection triage and hang forensics

    Previous

    37 / 280

    Next: Objections at Scale

    Up next

    Up nextObjections at Scale

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Objection Hierarchy Propagation: Up the Component Tree
    • Objection Counting Rules: Raises, Drops, and Edge Cases
    • set_drain_time Deep Dive: Grace Period After Objections Clear