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
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
    • Environment Architecture
    • Tests: Base Classes, Selection and Startup
      • Base Test Pattern: One Contract for Every Scenario
      • Scenario Inheritance: Specialize Intent, Not Infrastructure
      • Factory Overrides in Tests: Explicit, Local, Reviewable
      • Shared run_phase: One End-of-Test Policy for Every Test
      • +UVM_TESTNAME and Plusargs: Runtime Test Selection
      • Top Module and run_test Startup
      • Designing a Test Library
  • 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

‹Tests: Base Classes, Selection and Startup
  1. Topics
  2. UVM Verification
  3. Environment & Test Classes
  4. Tests: Base Classes, Selection and Startup
  5. Shared run_phase: One End-of-Test Policy for Every Test

Part 7 · Environment & Tests · Intermediate

Shared run_phase: One End-of-Test Policy for Every Test

Where raise and drop belong: the bench's per-test objections and settle delays, a template-method base test with a run_scenario hook and a drain time, and letting a checker own 'nothing is still owed'.

On this page

    Search the bench for #100ns; and you find it at the end of six tests, each time with the same comment: let the last output word drain. Then look at dma_drain_test. It drops its objection the moment the last input word is accepted, with no delay at all, and still compares all six words. Six tests guess how long the DMA takes; one test knows. Which policy should every test follow, and where should it be written?

    The idea: end of test is a policy, so write it once

    A UVM run phase ends when no component holds an objection, a counter that says "I am still busy". On the bench each derived test raises one at the start of its run_phase and drops it at the end. That works, but it means nine copies of the end-of-test policy: nine raises, nine drops, six guessed delays. Change the policy, say to wait for the scoreboard instead of a delay, and nine files change.

    There are two separate questions inside "when is the test done?". The stimulus is finished: only the test knows that. Nothing the DUT was asked to do is still outstanding: only a checker that watches the DUT knows that. A restaurant closing works the same way. The host stops seating guests (the test drops its objection), the kitchen finishes the orders already taken (the scoreboard holds the phase until they are out), and only then do the lights go off (the drain time, then the cleanup phases).

    The picture: who holds the phase open, and when

    Sequence diagram: the test raises, runs the scenario and drops while words are still inside the DMA; the phase calls the scoreboard's phase_ready_to_end; the scoreboard raises until the owed words arrive, then drops; the drain time runs, then the cleanup phases.
    End of test split between the two parties who know: the test says the stimulus is done, the scoreboard says nothing is still owed. The drain time is a short margin after the last drop, not a guess at DUT latency.

    Build it

    What the bench does today

    Most tests own their objection and end with a delay. dma_drain_test is the exception: it relies on a scoreboard override that owns the second question.

    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
    // snippet: from the course DMA bench
    // The test drops its objection as soon as the last input word is accepted, with
    // no settling delay. The end-of-test scoreboard keeps the run phase open until
    // the last output word arrives.
    class dma_drain_test extends dma_base_test;
      `uvm_component_utils(dma_drain_test)
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        dma_scoreboard::type_id::set_type_override(dma_scoreboard_eot::get_type());
        super.build_phase(phase);
      endfunction
    
      task run_phase(uvm_phase phase);
        axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
        phase.raise_objection(this);
        reg_write(KEY, 32'h1234_5678);
        reg_write(LEN, 6);
        reg_write(CTRL, 32'h1);                  // GO, interrupt not needed
        if (!pkt.randomize() with { n == 6; }) `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
        pkt.start(env.in_agt.sqr);
        phase.drop_objection(this);              // the last word is still inside the DMA
      endtask
    endclass

    The override, dma_scoreboard_eot, learns how many words each job owes from the observed LEN and GO register writes. When the run phase's count reaches zero it gets a phase_ready_to_end call, and if words are still owed it raises its own objection until they arrive. phase_ready_to_end is the home of that mechanism; here the point is where the policy lives: in the checker, once, for every test that uses it.

    A template-method base test

    To write the first half once, move the objection into the base test and give derived tests a hook. This is the template method pattern: the base class fixes the order of steps, and derived classes fill in one step.

    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
    // snippet
    class dma_tmpl_base_test extends dma_base_test;
      `uvm_component_utils(dma_tmpl_base_test)
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      // The scenario. Derived tests override this, not run_phase.
      virtual task run_scenario();
        `uvm_warning("NOSCEN", "run_scenario not overridden: nothing to run")
      endtask
    
      task run_phase(uvm_phase phase);
        phase.get_objection().set_drain_time(this, 50ns);   // margin after the last drop
        phase.raise_objection(this, get_type_name());
        run_scenario();
        phase.drop_objection(this, get_type_name());
      endtask
    endclass
    
    class dma_smoke2_test extends dma_tmpl_base_test;
      `uvm_component_utils(dma_smoke2_test)
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        dma_scoreboard::type_id::set_type_override(dma_scoreboard_eot::get_type());
        super.build_phase(phase);
      endfunction
    
      task run_scenario();
        dma_job_cfg job = dma_job_cfg::type_id::create("job");
        bit [31:0] status;
        if (!job.randomize() with { len == 8; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")
        run_job(job, 8, status);
        if (status[2:1] !== 2'b01) `uvm_error("STATUS", $sformatf("STATUS=%08h", status))
      endtask
    endclass
    • The objection carries get_type_name() as its description, so +UVM_OBJECTION_TRACE shows which test raised and dropped it.

    • set_drain_time replaces the #100ns lines: after the count reaches zero, the phase waits that long before ending. It is a margin, not a latency guess; the scoreboard owns latency.

    • phase.get_objection() works in UVM 1.2 and IEEE 1800.2. Older code writes phase.phase_done, which IEEE 1800.2 does not define; reference libraries keep it only for compatibility.

    • Name the hook after what it does. Names like pre_main or post_main invite confusion with the runtime phases pre_main_phase and post_main_phase, which are real phases with their own objections.

    The fork trap

    A run_scenario that starts its work with fork ... join_none and returns immediately drops the objection at once. If nothing else objects, the run phase ends after the drain time and the forked threads are killed with it: an early end, not a hang. A hook that starts background work must also wait for it, or the work must be owned by something that objects, like the scoreboard above.

    Choosing a policy

    Policy

    How it ends

    Good for

    Weakness

    Fixed delay in each test (#100ns)

    After a guessed time

    A first bench, a quick test

    Wrong when latency changes; copied into every test

    Drain time in the base test

    A fixed margin after the last drop

    Pipelines with short, known latency

    Still a guess, but written once

    Checker owns outstanding work

    When the scoreboard owes nothing

    Any DUT with real latency or backpressure

    The checker must learn what is owed from the DUT's inputs

    Watch it run

    dma_drain_test drops its objection while the last word is still inside the DMA, and all six words are still compared:

    Diagram
    +UVM_TESTNAME=dma_drain_test
    UVM_INFO dma_tb.sv(411) @ 375000: uvm_test_top.env.sb [SB] 6 words matched, 0 mismatched
    UVM_ERROR :    0

    Compare it with the smoke test, which matches 8 words at 655000 ps after waiting for the interrupt and then 100 ns more. The drain test ends as soon as the work is done, not 100 ns after a guess.

    What breaks: the end-of-test check that passed with a word missing

    The first version of the end-of-test scoreboard decided whether to extend the phase by looking at its queue of expected words. While building the bench, dma_drain_test passed with 5 of 6 words compared and no error. The test had dropped its objection in the same timestep as the last input word was accepted, before the input monitor published that word. The queue looked empty, nothing was extended, and the run phase ended with one word never predicted and never compared.

    The fix was to count what the DUT was asked to do, from the observed LEN and GO writes, not what the testbench had happened to see so far. End-of-test strategy tells the whole story. For test design, the takeaway is that moving the drop earlier is only safe once a checker owns the second question properly.

    Interview

    "Where should raise_objection and drop_objection live in your testbench?"

    A weak answer: "In the test's run_phase, around the sequence." Fine for one test, and it says nothing about the second half.

    A strong answer: "Two parties decide end of test. The test knows when stimulus is done, so it raises and drops, and I put that in the base test as a template method with a run_scenario hook, so the policy is written once. A checker knows when nothing is outstanding, so the scoreboard uses phase_ready_to_end to hold the phase while words are owed, learned from the DUT's register writes. A short drain time covers the final pipeline stage. Monitors never object, and a global timeout catches anything that never ends."

    Follow-ups: What happens if run_scenario forks its work and returns? (the objection drops, the phase ends after the drain time and the threads are killed). Why not just use a long drain time? (it guesses latency, slows every test, and still fails under backpressure).

    Check yourself You convert all nine tests to the template base. dma_timeout_test must still hang until its timeout. Does the template break it?

    No. Its run_scenario calls run_job with the interrupt disabled, which waits forever, so run_scenario never returns and the objection is never dropped. The 20 µs timeout set in its build_phase still ends the run with PH_TIMEOUT.

    Check yourself A colleague sets the drain time to 10 µs "to be safe" and removes the scoreboard override. Under heavy backpressure from axis_sink_agent, what can go wrong, and how would you notice?

    If the slave stalls the DMA for longer than the drain time after the test drops, the run ends with words still inside the DMA. The plain scoreboard's check_phase reports them as left over (SB_LEFTOVER), so the failure is visible, but it is a testbench failure, not a DUT bug, and it is seed-dependent. A checker that owns outstanding work has no such window.

    Next: the policy is written once; now pick which test runs. +UVM_TESTNAME and Plusargs covers selecting tests and knobs from one compiled binary.

    Related topics

    • Objections and end of test

    • End-of-test strategy

    • Drain time

    • +UVM_TESTNAME and Plusargs

    Previous

    122 / 280

    Next: +UVM_TESTNAME and Plusargs: Runtime Test Selection

    Up next

    Up next+UVM_TESTNAME and Plusargs: Runtime Test Selection

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Base Test Pattern: One Contract for Every Scenario
    • Scenario Inheritance: Specialize Intent, Not Infrastructure
    • Top Module and run_test Startup