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. config_db Gets and Conditional Construction

Part 2/Phases & LifecycleIntermediate

config_db Gets and Conditional Construction

Checked config_db gets in build, the wildcard that never matches the agent itself, and building only what the configuration selects.

On this page

    You want the APB agent to check its interface handle once, so you add a uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif) to apb_agent::build_phase with a fatal on failure. The driver and the monitor have always found that same handle. The agent does not: the test dies at time 0 with your new fatal. The handle is in the database, under the right name and type. The path is the problem.

    The idea: notes in labelled pigeonholes

    uvm_config_db is a notice board shared by the whole testbench. A set(context, inst_path, field, value) pins a note addressed to every component whose full name matches the path (the context's full name, a dot, then inst_path). A get(this, "", field, value) looks for notes addressed to the caller's own full name, with that field name and exactly that type. It returns 1 if it found one and 0 if not.

    Top-down build is what makes the board reliable: a parent's build finishes before its children's builds start, and the top module's initial block runs before run_test. So a note pinned by a parent in its build_phase is always there when the child looks. The other half is the address. A path ending in .* reaches everything inside a box, not the box itself.

    The picture: who pins, who reads in the DMA bench

    Diagram: the top module sets interface handles for paths below each agent and the IRQ handle for the test; the env sets the passive setting for out_agt; the APB agent itself matches none of the interface paths.
    Every config_db set in the DMA bench and the components that read it. The interface paths end in .* so they reach the drivers and monitors, never the agents themselves.

    Build it: the bench's sets and gets

    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

    Each interface goes to the components that use it. "uvm_test_top.env.apb.*" matches uvm_test_top.env.apb.drv and uvm_test_top.env.apb.mon, which each read vif in their build_phase with a fatal on failure. It does not match uvm_test_top.env.apb, and that is the opening bug: the agent's own get misses.

    There are three fixes, each with a cost. Set on "uvm_test_top.env.apb*" (no dot), which reaches the agent and its children, but also any sibling whose name starts with apb. Set a second time on the exact path "uvm_test_top.env.apb". Or the common production style: the agent gets the handle once and hands it to its children through an agent configuration object. What not to do is widen the set to "*", which hands every bus's handle to every component that asks for vif: the APB monitor would then match the stream interface's set as well, and only the type parameter would keep it from picking the wrong one.

    Every get in the bench follows one shape: check the return value and stop with a message that names the component. A get whose result is ignored leaves a null handle that fails much later, on the first vif. access, far from the cause.

    design.sv
    // snippet
    if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", $sformatf("apb_if not set for %s", get_full_name()))

    A get matches only if

    Typical mismatch

    The type parameter is identical

    virtual apb_if set, apb_if or virtual axis_if requested

    The setter's path matches the getter's full name

    env.apb.* set, the agent env.apb asks

    The field name is byte-identical

    "vif" set, "apb_vif" requested

    The set happened before the get

    Set in the child's own build, or in connect, after the get ran

    When two sets match: one sentence, then the home lesson

    During build, a set from higher in the hierarchy wins (the precedence is 1000 minus the setter's depth, so the top module and the test beat the env), and between equal precedences, and for any set made after build, the last one wins. Path specificity never decides: a matching wildcard from the test beats an exact path from the env. The full rules, with a worked example, are in config_db resolution order.

    Build only what the configuration selects

    The env pins one note of its own: is_active = UVM_PASSIVE for out_agt, because the DUT drives that port. Each agent then builds a driver and sequencer only when get_is_active() says UVM_ACTIVE, and guards the matching connect the same way:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: the shape of both bench agents
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);              // uvm_agent reads is_active here
      mon = axis_monitor::type_id::create("mon", this);
      if (get_is_active() == UVM_ACTIVE) begin
        sqr = axis_sequencer::type_id::create("sqr", this);
        drv = axis_driver::type_id::create("drv", this);
      end
    endfunction
    
    function void connect_phase(uvm_phase phase);
      ap = mon.ap;
      if (get_is_active() == UVM_ACTIVE)     // same condition as the create
        drv.seq_item_port.connect(sqr.seq_item_export);
    endfunction

    Two details matter. The agent never reads is_active from config_db itself; uvm_agent::build_phase does it, which is why the super call is not optional (What super.build_phase() Really Does shows the silent failure). And every conditional create needs the same condition around every use in connect, or the passive instance dereferences a driver that was never built.

    Watch it run

    Diagram
    out_agt                axis_agent              -     @272
            mon                  axis_monitor            -     @674
              ap                 uvm_analysis_port       -     @682

    The passive agent in the smoke test's topology: one monitor, no driver, no sequencer. When you change configuration and want to see what was actually read, three tools help: +UVM_CONFIG_DB_TRACE on the command line logs every set and get; print_config() on a component lists what is visible to it; and check_config_usage(), called in end_of_elaboration, reports settings that were set but never read, which is usually a misspelled path or field.

    What breaks: the fatal with the right name and the wrong path

    Back to the opening. The fatal fired in uvm_test_top.env.apb, the database held a virtual apb_if named vif, and the engineer's first instinct was a type mismatch. +UVM_CONFIG_DB_TRACE showed the set's scope as uvm_test_top.env.apb.* and the failed lookup as uvm_test_top.env.apb: the path, not the type. They added an exact-path set for the agent and kept the wildcard for its children.

    The same shape shows up the other way round: a set on "env.agt" that the driver at env.agt.drv never sees. Read the set's path as a pattern against the getter's full name, character by character, and the answer is usually obvious.

    "A config_db get returns 0, but you can see the set in the code. How do you debug it?"

    A weak answer: "I'd check the spelling and maybe use a wildcard so it matches." Widening the path hides the bug and leaks the setting to components that should not see it.

    A strong answer: "Three fields must match: the type parameter, the path against the getter's full name, and the field string. Then timing: the set must run before the get. I turn on +UVM_CONFIG_DB_TRACE and compare the set's scope with the get's full name. The classic miss is a path ending in .* that reaches the agent's children but not the agent. I fix it with an exact path, not a wider wildcard, and I add check_config_usage() to catch sets nobody reads."

    Follow-ups: Two matching sets from the test and the env: which wins during build? (the test's, higher in the hierarchy) and Where does uvm_agent read is_active? (in its build_phase, through super).

    Check yourself The env sets a config object on path "in_agt.*". The agent's build_phase reads it with get(this, "", "cfg", cfg). What happens?

    The get fails: the set's pattern is uvm_test_top.env.in_agt.*, which matches the agent's children but not uvm_test_top.env.in_agt itself. Set on the exact path "in_agt", which is right for an object only the agent reads.

    Check yourself Why is it safe for dma_env to set out_agt's is_active *before* creating out_agt, and also safe to set it after?

    The agent reads the value in its own build_phase, which runs after the env's build_phase returns. Any set made anywhere in the env's build, before or after the create, is in place by then.

    Check yourself A passive agent crashes in connect_phase with a null handle, while the active instances of the same class work. What is the likely bug?

    A connect that uses the driver or sequencer without the get_is_active() == UVM_ACTIVE guard. The passive build skipped those creates, so connect must skip their uses under the same condition.

    Next: with every component built and configured, Connect Phase: Bottom-Up TLM Wiring wires them together.

    Related topics

    • Connect Phase: Bottom-Up TLM Wiring

    • config_db and resources

    • Conditional agent build

    Previous

    26 / 280

    Next: Connect Phase: Bottom-Up TLM Wiring

    Up next

    Up nextConnect Phase: Bottom-Up TLM Wiring

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Elaboration, Start and the Pre-Time-Zero Contract
    • The Run Phase: Parallel Execution and Duration
    • Runtime Sub-Phases: Reset, Configure, Main and Shutdown