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 Foundations18
  • UVM Phases & Simulation Lifecycle94
  • Factory & Configuration37
  • Transaction-Level Modeling (TLM)42
  • UVM Sequences & Stimulus44
  • UVM Agents & Protocol VIP71
  • Environment & Test Classes55
    • Environment Architecture
    • Environment Composition Hub: Scaling Reusable Env Topologies
    • Scoreboard + Coverage Hub: Integrated Checking Pipelines
    • 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
      • Test Library Organization: Scalable Structure and Naming
      • Test Base Debug: Symptom-First Hierarchy Triage
    • Config Distribution Hub: Test-to-Env Policy Propagation
    • Virtual Sequencer Hub: Env-Level Multi-Agent Coordination
    • Plusargs & Test Selection Hub: Runtime Control Without Recompile
    • End-of-Test Hub: Objections, Readiness, and Fail-Fast Shutdown
    • Top Module Hub: Startup Contracts from HDL to UVM
  • Checking & Coverage in UVM37
  • UVM Register Abstraction Layer (RAL)53
  • Advanced UVM in Practice44
  • Debug & Regression10
  • Coding Practice47
  • Senior Prep: Architecture, Leadership and Interviews92
  • Verification in Industry Practice10
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. Top Module and run_test Startup

Part 7 · Environment & Tests · Intermediate

Top Module and run_test Startup

The bench's top module line by line: clock, reset as an interface so a test can drive it, the DUT, config_db paths with null context and wildcards, run_test as the last statement, and the DMA_NO_TOP guard that lets the subsystem bench replace it.

On this page

    The subsystem bench, two DMAs in a chain, reuses every class of the block bench without a single edit. The one file it does not reuse is the top module: it defines DMA_NO_TOP, includes dma_tb.sv, and supplies its own. Forty lines decide whether a bench can be reused at the next level. What has to be in them, and what must never be?

    The idea: the top is where the static world meets the dynamic one

    SystemVerilog has two worlds. Modules, interfaces and wires are static: they exist from time 0 and are connected by elaboration. Classes are dynamic: they are created at run time and can only reach signals through a virtual interface, a handle to a static interface instance. The top module is the only place that lives in both worlds: it instantiates the static parts, then publishes handles to them in uvm_config_db and hands control to UVM with run_test.

    Think of an airport jet bridge. The terminal (the testbench classes) and the aircraft (the DUT and its pins) are built separately and know nothing about each other. The bridge connects one door to one door. It does not fly the plane or check tickets; it only has to be in the right place before boarding starts.

    The picture

    Flowchart: clock and reset feed the interfaces; the interfaces connect to the DUT and are published in config_db; components fetch them; run_test starts the components.
    The top module's job: make the clock and reset, instantiate interfaces and the DUT, publish interface handles in config_db, then call run_test. The components fetch the handles in their build_phase.

    Build it: the bench's top, line by line

    top.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
    // snippet: from the course DMA bench
    module top;
      import uvm_pkg::*;
      import dma_pkg::*;
    
      logic clk = 0;
      always #5 clk = ~clk;                        // 100 MHz
      rst_if rst (clk);
      wire rst_n = rst.rst_n;
      initial begin
        rst.rst_n = 1'b0;
        repeat (4) @(posedge clk);
        rst.rst_n <= 1'b1;
      end
    
      apb_if  apb  (clk, rst_n);
      axis_if s_ax (clk, rst_n);
      axis_if m_ax (clk, rst_n);
      irq_if  irqi (clk);
    
      // The output side accepts every beat unless a test adds a reactive slave
      // (axis_sink_agent), whose driver then drives TREADY.
      initial m_ax.tready = 1'b1;
    
      dma_dut dut (
        .clk, .rst_n,
        .paddr(apb.paddr), .psel(apb.psel), .penable(apb.penable), .pwrite(apb.pwrite),
        .pwdata(apb.pwdata), .prdata(apb.prdata), .pready(apb.pready), .pslverr(apb.pslverr),
        .s_tdata(s_ax.tdata), .s_tvalid(s_ax.tvalid), .s_tlast(s_ax.tlast), .s_tready(s_ax.tready),
        .m_tdata(m_ax.tdata), .m_tvalid(m_ax.tvalid), .m_tlast(m_ax.tlast), .m_tready(m_ax.tready),
        .irq(irqi.irq)
      );
    
      initial begin
        uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.apb.*", "vif", apb);
        uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.in_agt.*", "vif", s_ax);
        uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.out_agt.*", "vif", m_ax);
        uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top", "irq_vif", irqi);
        uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top.env.irq.*", "vif", irqi);
        uvm_config_db#(virtual rst_if)::set(null, "uvm_test_top*", "rst_vif", rst);
        run_test("dma_smoke_test");
      end
    endmodule

    Clock and reset: why reset is an interface

    The clock is an ordinary always toggle. Reset is not an ordinary reg: it lives in a tiny interface, rst_if, and the top only drives it for the first four clocks. Because it is an interface, a handle to it can be published, and dma_reset_test can drive rst_n low in the middle of a packet. A reset generated by a fixed initial block in the top could never be repeated by a test. Reset on the fly builds on that.

    Interfaces, the DUT and one default

    Each bus is one interface instance, connected to the DUT port by port. The line initial m_ax.tready = 1'b1; makes the output side accept every beat by default. When dma_backpressure_test swaps in the reactive slave, the sink driver starts driving TREADY from the class side and takes over. The default belongs in the top because it is a property of the harness, not of any test.

    config_db sets: context, paths and wildcards

    • `null` context means the path is absolute and the set has the highest precedence (1000), so no component can accidentally override a virtual interface. Every path therefore starts with uvm_test_top, the instance name run_test gives the test.

    • *`"uvm_test_top.env.apb."**: the agent's children, the driver and the monitor, are the ones that get the handle. A wildcard must match the getter's full name, so env.apb.* matches env.apb.drv but not env.apb` itself.

    • `"uvm_test_top"`, field `irq_vif`: an exact path, because only the test (in dma_base_test) needs the raw interrupt line.

    • *`"uvm_test_top", field rst_vif`: no dot before the star, so it matches the test itself and** everything under it. The reset test and the reset-aware scoreboard both fetch it.

    run_test last, in the same initial block

    The sets come first and run_test comes last, in one initial block. Components call get in build_phase, which runs inside run_test; putting both in one block guarantees the order. run_test here is the global task in uvm_pkg, a thin wrapper around uvm_root::get().run_test(). Its argument is only the default test, because +UVM_TESTNAME wins.

    The DMA_NO_TOP guard

    The whole top sits inside ` ifndef DMA_NO_TOP `. The subsystem bench defines the macro, includes the file for its package and interfaces, and writes a top with two DUTs that sets the same field names one level deeper, uvm_test_top.env.dma0.apb.*` and so on. Vertical Reuse shows that top in full.

    What the top must never do

    No scenario decisions: no register values, no packet counts, no choice of test beyond the default. No #delay before run_test. No class logic beyond the config_db sets. A top that knows about the scenario cannot be shared by the next test, let alone by the next level of integration.

    Watch it run

    The first lines of every run come from run_test, before any component exists:

    Diagram
    UVM_INFO @ 0: reporter [RNTST] Running test dma_smoke_test...
    UVM_INFO uvm_root.svh(531) @ 0: reporter [UVMTOP] UVM testbench topology:
    ...
    UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched

    No NOVIF fatal means every driver, monitor and the test found their handles: all the config_db paths matched. The scoreboard's full name, uvm_test_top.env.sb, shows the hierarchy the paths were written for.

    What breaks: the path that stopped one level short

    A cleanup commit "tidied" the top's sets, removing what looked like redundant wildcards: "uvm_test_top.env.apb.*" became "uvm_test_top.env.apb". Every test died at time 0 with NOVIF from the APB agent's children (apb_if not set for this driver, or the monitor's version). The handle was set on the agent, and the agent never asks for it; the driver asks, under the name uvm_test_top.env.apb.drv, which the new path does not match.

    The fix was to restore the wildcard. The lesson is a rule worth knowing by heart: a config_db path is matched against the full name of the component that calls get. When a get fails, print the getter's get_full_name() and compare it with the set path character by character; +UVM_CONFIG_DB_TRACE shows every set and get for one run.

    Interview

    "What does your top module do, and how do the classes get the interfaces?"

    A weak answer: "It instantiates the DUT and calls run_test." It skips the only hard part, the handle hand-off.

    A strong answer: "It's the static-to-dynamic bridge. It makes clock and reset, with reset in an interface so a test can drive it mid-run, instantiates interfaces and the DUT, then sets each virtual interface in config_db with a null context and an absolute path starting at uvm_test_top, wildcarded to the agent's children, since they are the getters. run_test is the last statement of the same initial block, so the sets come before build. Its argument is only a default. The top holds no scenario logic, and it's guarded so a subsystem bench can replace it and reuse everything else."

    Follow-ups: Why null context rather than a component? (there is no component yet, and null gives the highest precedence). Why does run_test have to come after the sets? (build_phase runs inside run_test and the gets happen there).

    Check yourself A teammate moves the config_db sets into a second initial block that starts with #1. What happens?

    run_test starts at time 0 and the build phase runs in zero time, so every get happens before the delayed sets. Each driver and monitor reports NOVIF at time 0. The sets must happen before run_test creates the components, which is why they share one block, ahead of the call.

    Check yourself Why does rst_vif use "uvm_test_top*" while irq_vif uses the exact path "uvm_test_top"?

    Two different consumers. The interrupt line is fetched only by the test, so an exact path is enough and keeps the handle out of everyone else's reach. The reset interface is fetched by the reset test and by dma_scoreboard_ra at uvm_test_top.env.sb, so the pattern must match both the test and its descendants; with no dot before the star, it does.

    Next: you now have every piece of a test. Designing a Test Library turns nine tests into a library that a team can grow for a year without copying.

    Related topics

    • config_db paths and wildcards

    • Vertical Reuse: From Block to Subsystem

    • run_test and timeouts

    • Designing a Test Library

    Previous

    332 / 658

    Next: Designing a Test Library

    Up next

    Up nextDesigning a Test Library

    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
    • Factory Overrides in Tests: Explicit, Local, Reviewable