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
      • Why build_phase Is Top-Down
      • config_db Gets and Conditional Construction
      • Connect Phase: Bottom-Up TLM Wiring
      • Elaboration, Start and the Pre-Time-Zero Contract
      • The Run Phase: Parallel Execution and Duration
      • Runtime Sub-Phases: Reset, Configure, Main and Shutdown
      • What super.build_phase() Really Does
      • run_test, Timeouts and How a Simulation Ends
    • Objections Deep Dive: Drain Time and Propagation
    • 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

‹UVM Phases: Build, Connect, Elaborate and Run
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. UVM Phases: Build, Connect, Elaborate and Run
  5. run_test, Timeouts and How a Simulation Ends

Part 2 · Phases & Lifecycle · Intermediate

run_test, Timeouts and How a Simulation Ends

How run_test picks and runs a test, the timeout, the quit count, report_summarize, and who calls $finish.

On this page

    Two tests from the same regression, both broken. One reports PASS after 0 ns of simulated time with nothing driven. The other holds a farm slot for nine hours until the job scheduler kills it, leaving no summary and no clue. Both are end-of-test bugs, and UVM has a precise answer for each: the first never raised an objection, and the second had no timeout short enough to matter.

    The idea: the stage manager, the show and the fire alarm

    run_test is the stage manager. It decides which show is on, opens the doors, lets the show run until the cast says it is done, then reads the reviews and closes the theatre. Two other things can close the theatre early: a fatal problem (someone pulls the fire alarm), or a timeout (the building's closing time). On an early close, the audience leaves at once: there is a quick summary at the door, but the critics' review, report_phase, never happens.

    The picture: run_test and the three ways out

    Flowchart: run_test picks the test name, creates uvm_test_top, runs all phases, summarises and calls $finish if finish_on_completion is set; a fatal, the quit count or a timeout instead goes through die, which summarises and finishes.
    What run_test does, and the two early exits. A normal end runs every phase, then report_summarize and $finish. A fatal, the quit count or the timeout goes through die(), which summarises and calls $finish without running the remaining phases.

    Build it: picking the test

    The bench's top module ends with run_test("dma_smoke_test");. That name is only a default. If the command line has +UVM_TESTNAME=dma_rand_test, run_test uses that instead: the plusarg beats the argument. That is how one compiled bench runs a whole regression of different tests. run_test then asks the factory to create the named class with the instance name uvm_test_top, so a misspelled name is a fatal at time 0, not a silent fallback.

    When the phases are done, run_test calls report_summarize() on the report server, which prints the --- UVM Report Summary --- block with the counts by severity and by id, and then calls $finish if finish_on_completion is 1 (the default). Set it to 0 and run_test returns to your initial block instead, which is the place for post-processing that must happen after UVM is done:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet
    initial begin
      // config_db sets for the interfaces, as in the bench's top
      uvm_root::get().finish_on_completion = 0;
      run_test("dma_smoke_test");
      // UVM is done: every phase has run and the summary is printed
      $display("post-processing coverage files...");
      $finish;
    end

    Build it: a timeout that ends a hang

    The bench's timeout test is written to hang: it runs a job with the interrupt disabled and waits for an interrupt that never comes. It survives because it sets a timeout: a watchdog that ends run_phase with a fatal at a fixed simulated time.

    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
    // snippet: from the course DMA bench
    // A test that would hang: the interrupt is never enabled, so it never fires.
    // The global timeout ends the run with a fatal instead of burning the regression slot.
    class dma_timeout_test extends dma_base_test;
      `uvm_component_utils(dma_timeout_test)
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        uvm_root::get().set_timeout(20us, 1);   // replaces a +UVM_TIMEOUT=...,YES value; ,NO locks the command line
      endfunction
    
      task run_phase(uvm_phase phase);
        dma_job_cfg job = dma_job_cfg::type_id::create("job");
        bit [31:0] status;
        phase.raise_objection(this);
        if (!job.randomize() with { len == 4; irq_en == 0; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")
        run_job(job, 4, status);                 // waits for an interrupt that never comes
        phase.drop_objection(this);
      endtask
    endclass
    • uvm_root::get().set_timeout(20us, 1) sets the limit to 20 µs. The second argument says whether a later set_timeout may change it. Without any call, the default limit is 9200 s of simulated time, long enough to burn a farm slot.

    • The watchdog starts with run_phase and measures from there, so the limit is a total, not a per-sequence budget.

    • On the command line, +UVM_TIMEOUT=<time>,<YES|NO> sets the same limit. The time is a plain integer in the time unit the UVM package was compiled with (picoseconds in this bench's run, which is why the log below says 20000000), so +UVM_TIMEOUT=10ms does not mean what it looks like. YES or NO is the same overridable flag.

    The order of the two matters. The library applies +UVM_TIMEOUT in uvm_root's own build_phase, which runs before the test's build_phase (top-down). So a test that calls set_timeout(..., 1) replaces a command-line value given with YES. To make the command line win over the test, pass NO, which locks the value; the test's later call is then refused with an informational message. That is what the comment in the listing means by "can still override".

    Watch it run: the fatal that skips the report

    The timeout test's log, trimmed to the lines that matter:

    Diagram
    UVM_INFO @ 0: reporter [RNTST] Running test dma_timeout_test...
    UVM_FATAL uvm_phase_hopper.svh(598) @ 20000000: reporter [PH_TIMEOUT] Explicit timeout of 20000000 hit, indicating a probable testbench issue
    
    --- UVM Report Summary ---
    ** Report counts by severity
    UVM_ERROR :    0
    UVM_FATAL :    1
    ** Report counts by id
    [PH_TIMEOUT]     1
    [RNTST]     1
    • "Explicit" means a limit set by set_timeout or +UVM_TIMEOUT; the default limit says "Default". The time is printed in the UVM package's unit.

    • The scoreboard's [SB] ... words matched line is missing. It is printed in report_phase, and after a fatal the remaining phases do not run. The summary appears only because die() calls report_summarize() itself.

    • A banner printed in report_phase ("TEST PASSED") would also be missing, which is why regression scripts judge a test by the summary's counts, never by a banner.

    die() also calls pre_abort() on every component, bottom-up, before summarising. That is the hook for writing something useful on the way out, such as the scoreboard's pending queue.

    The quit count: stopping after N errors

    The report server counts every message by severity. Messages whose action includes UVM_COUNT (by default every UVM_ERROR) also count toward a quit count: when it reaches its limit, the server calls die() and the test ends exactly as after a fatal. The default limit is 0, meaning no limit. Set it with +UVM_MAX_QUIT_COUNT=10,NO on the command line, or uvm_report_server::get_server().set_max_quit_count(10) in code, to stop a test that has already failed from producing a 2 GB log.

    What breaks: the test that passed at time zero

    A variant of the smoke test that runs one DMA job but never raises an objection:

    Diagram
    UVM_INFO @ 0: reporter [RNTST] Running test dma_noobj_test...
    UVM_ERROR dma_tb.sv(407) @ 0: uvm_test_top.env.sb [SB_EMPTY] no words were compared: the test checked nothing
    UVM_INFO dma_tb.sv(411) @ 0: uvm_test_top.env.sb [SB] 0 words matched, 0 mismatched
    UVM_ERROR :    1
    UVM_FATAL :    0

    Nothing objected, so the run phase's count was zero from the start, and run_phase ended at time 0 (after the drain time, here zero). The test's run_phase thread had started its register writes, but it was killed with every other run thread. The same thing happens when the test starts its work with fork ... join_none and returns without raising: the forked threads are killed with it. This is an early end, not a hang.

    The test is saved only by the scoreboard's SB_EMPTY check in check_phase. Without it, this run would report UVM_ERROR : 0 and pass. Every bench needs both halves: an objection raised before the first wait, and a check that fails a run that compared nothing.

    The other half of the opening, the nine-hour hang, was a test waiting on an interrupt that a configuration change had disabled, with no timeout below the 9200 s default. The team set a base-test timeout of ten times the longest test's normal run time, and the same bug now ends in seconds with a PH_TIMEOUT line that points straight at run_phase.

    Interview

    "How does a UVM simulation end? Name every way."

    A weak answer: "When all objections are dropped, the test ends and $finish is called." That is one of four, and it skips who calls $finish.

    A strong answer: "Normally: when run_phase and the runtime phases have no objections, the cleanup phases run, run_test calls report_summarize and then $finish if finish_on_completion is 1. Early: a UVM_FATAL, or the quit count reaching its limit, calls die(), which runs pre_abort, report_summarize and $finish, so report_phase never runs. The timeout is a special fatal, PH_TIMEOUT, set by set_timeout or +UVM_TIMEOUT, default 9200 s. And a test that never raises ends at time 0, which is why every scoreboard should fail an empty run."

    Follow-ups: +UVM_TESTNAME and run_test("x") disagree: which wins? (the plusarg) and The test calls set_timeout(…,1) and the command line has +UVM_TIMEOUT=…,YES: which limit applies? (the test's: the plusarg is applied first, in uvm_root's build).

    Check yourself The regression script passes +UVM_TESTNAME=dma_rand_test, and the top calls run_test("dma_smoke_test"). Which test runs, and what does the log say?

    dma_rand_test: the plusarg beats the argument. The log's RNTST line says Running test dma_rand_test....

    Check yourself A test ends with a UVM_FATAL from a protocol checker. Its PASS/FAIL banner is printed in report_phase. What does the log show?

    No banner. A fatal calls die(), which runs pre_abort and report_summarize and then $finish; report_phase is skipped. The summary's UVM_FATAL : 1 is the reliable verdict.

    Check yourself A base test calls set_timeout(1ms, 1) in build. One long test needs 5 ms. What is the cleanest way to give it more time from the command line?

    +UVM_TIMEOUT=<5 ms in the UVM time unit>,NO. The plusarg is applied first, in uvm_root's build; the NO locks it, so the base test's later set_timeout is refused. With YES, the base test would override it back to 1 ms.

    Next: objections decide when the run phase ends, and they have more rules than raise and drop. The Objections Deep Dive covers counting, propagation, drain time and the end-of-test strategies.

    Related topics

    • Objections Deep Dive

    • End-of-Test Chain

    • Your first UVM testbench

    Previous

    32 / 280

    Next: Objections Deep Dive: Drain Time and Propagation

    Up next

    Up nextObjections Deep Dive: Drain Time and Propagation

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why build_phase Is Top-Down
    • config_db Gets and Conditional Construction
    • Connect Phase: Bottom-Up TLM Wiring