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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
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
  • 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
    • Verification Architecture and Planning
      • Writing a Testplan from the Spec
      • Estimating Verification Effort
      • A Block Bench That Upstream Can Reuse
      • From Block to Subsystem to SoC
      • Configuration Strategy Across Levels
      • Passive Agents and Who Owns the Check
      • Buy vs Build VIP, and Qualifying VIP
      • Choosing the Verification Strategy per Block
    • UVM with SVA and Formal
    • Leadership and Senior Judgment
    • Interview Bank
Loading lesson

←Verification Architecture and Planning
  1. Topics
  2. UVM Verification
  3. Senior Prep: Architecture, Leadership and Interviews
  4. Verification Architecture and Planning
  5. Configuration Strategy Across Levels
On this page

    Part 13 of 15, lesson 5 of 32Part 13 of 15/Senior Prep/Lesson 5 of 32Intermediate

    Configuration Strategy Across Levels

    A typed configuration tree owned by the test, config_db tracing on the subsystem bench, and a wildcard that made the wrong agent passive.

    An engineer on the subsystem team wants dma1's input agent passive in a new test. They add one line to the test, config_db set on "env.dma*.in_agt", because the wildcard "looks right". The test crashes at 275 ns with a fatal from deep inside the sequence library. Nobody changed the env. What happened, and how do you stop it happening across three levels of environment and a hundred tests?

    The idea: one typed configuration tree, owned by the test

    Configuration is how the same env behaves differently at block, subsystem and chip level. Left alone, it grows into dozens of loose config_db keys set from wherever was convenient, and the only way to learn what a run used is to read every set in the codebase. The senior pattern replaces that with a configuration tree that mirrors the env tree: one config object per env, nested the way the envs nest, built and decided by the test, and handed down with one config_db set per level.

    A building's electrical panel is the model. Every circuit has one labelled breaker in one place; nobody switches a kitchen light off from a junction box in the attic. A loose config_db set from a test into the depths of an agent is that attic junction box: it works until someone else wires a circuit through it.

    • One config object per env level, nested: the subsystem config holds the block configs.

    • The parent sets, then creates. A set must run before the child's build_phase reads it.

    • The test decides; components only read. Only the test parses plusargs. A shared config object is read-only after it is set.

    • Validate in one place, before anything is built, so an illegal combination fails at time zero with a clear message.

    • `config_db` carries handles, not a hundred scalars: virtual interfaces and config objects. The precedence rule (higher setter wins during build, then the last set) is taught in config_db resolution; this lesson assumes it.

    The picture: the DMA configuration tree

    Flowchart: the test builds soc_cfg, which holds dma_subsys_cfg, which holds two dma_env_cfg objects with agent modes and has_scoreboard and has_coverage knobs; the test passes the tree down with one set per level.
    One tree, built by the test, mirrors the env tree. Each env gets its own branch with one config_db set and passes sub-branches to its children.

    Build it: from the bench's config to a tree

    The bench already has the top of the tree. dma_subsys_cfg holds the two decisions that differ between subsystem configurations, and dma_subsys_env reads it with a default when no test set one:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // snippet: from the course DMA bench
    // Two DMAs in a chain (dma0's output feeds dma1's input), verified by reusing the
    // block env twice, unchanged, plus an end-to-end checker.
    class dma_subsys_cfg extends uvm_object;
      bit has_e2e_checker = 1;                   // the end-to-end scoreboard
      bit chain_internal  = 1;                   // dma1's input is driven by dma0, so its agent is passive
      `uvm_object_utils(dma_subsys_cfg)
      function new(string name = "dma_subsys_cfg");
        super.new(name);
      endfunction
    endclass

    What is missing is the block level. dma_env hard-codes its one decision (output agent passive) and has no switches at all, so dma_subsys_env reaches into it with a path-based config_db set on "dma1.in_agt". That works for one decision. With ten it becomes the attic junction box. The proposal below adds a block config and nests two of them in the subsystem config. It is a design sketch, not bench code:

    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
    // snippet: a configuration tree for the DMA envs (a proposal, not in the bench)
    class dma_env_cfg extends uvm_object;
      `uvm_object_utils(dma_env_cfg)
      uvm_active_passive_enum apb_mode = UVM_ACTIVE;
      uvm_active_passive_enum in_mode  = UVM_ACTIVE;
      bit has_scoreboard = 1;
      bit has_coverage   = 1;
    
      function new(string name = "dma_env_cfg");
        super.new(name);
      endfunction
    
      function void validate(string who);
        if (has_scoreboard && apb_mode == UVM_PASSIVE && in_mode == UVM_PASSIVE)
          `uvm_info("CFG", {who, ": fully passive with a scoreboard: inputs must come from monitors"}, UVM_LOW)
      endfunction
    endclass
    
    class dma_subsys_tree_cfg extends uvm_object;
      `uvm_object_utils(dma_subsys_tree_cfg)
      bit         has_e2e_checker = 1;
      bit         chain_internal  = 1;
      dma_env_cfg dma[2];
    
      function new(string name = "dma_subsys_tree_cfg");
        super.new(name);
        foreach (dma[i]) dma[i] = dma_env_cfg::type_id::create($sformatf("dma%0d_cfg", i));
      endfunction
    
      // Level policy lives here, in one place, not in tests.
      function void apply_chain();
        if (chain_internal) dma[1].in_mode = UVM_PASSIVE;
      endfunction
    
      function void apply_soc();                 // the CPU owns both register buses
        foreach (dma[i]) dma[i].apb_mode = UVM_PASSIVE;
      endfunction
    endclass
    
    // In the subsystem env's build_phase, before creating the children:
    //   uvm_config_db#(dma_env_cfg)::set(this, "dma0", "cfg", cfg.dma[0]);
    //   uvm_config_db#(dma_env_cfg)::set(this, "dma1", "cfg", cfg.dma[1]);
    // and in dma_env's build_phase, the agent modes come from its cfg:
    //   uvm_config_db#(uvm_active_passive_enum)::set(this, "apb",    "is_active", cfg.apb_mode);
    //   uvm_config_db#(uvm_active_passive_enum)::set(this, "in_agt", "is_active", cfg.in_mode);
    • Nested objects are created in `new`, so policy functions never touch a null handle.

    • Level policy is a named function (apply_chain, apply_soc). A test calls one; it does not poke six fields.

    • Each env passes its children their branch with an exact path on its own direct children. No wildcards cross more than one level.

    • The `is_active` sets remain, because uvm_agent reads its mode from config_db, but each one is made by the agent's own parent, from a field with a name.

    Watch it run: what the trace shows

    When a configuration question comes up, turn on tracing before reading code: +UVM_CONFIG_DB_TRACE on the command line, or uvm_config_db_options::turn_on_tracing() from a test's constructor (the scratch run below uses the call, because this Verilator build has no DPI for the command-line processor). This is the unchanged subsystem bench with tracing on, filtered to the lines that matter (the printed object is shortened to its two fields):

    Diagram
    +UVM_TESTNAME=dma_cfg_trace_test   (scratch run: dma_subsys_test with tracing turned on)
    UVM_INFO base/uvm_config_db_implementation.svh(485) @ 0: reporter [CFGDB/SET] Configuration scope='uvm_test_top.env' name='cfg' (type PARAMTYPEDTYPE 'T') set accessor=uvm_test_top = (PARAMTYPEDTYPE 'T') '{has_e2e_checker:'h1, chain_internal:'h1, ...}
    UVM_INFO base/uvm_config_db_implementation.svh(485) @ 0: reporter [CFGDB/GET] Configuration scope='uvm_test_top.env' name='cfg' (type PARAMTYPEDTYPE 'T') read accessor=uvm_test_top.env = (PARAMTYPEDTYPE 'T') '{has_e2e_checker:'h1, chain_internal:'h1, ...}
    UVM_INFO base/uvm_config_db_implementation.svh(485) @ 0: reporter [CFGDB/SET] Configuration scope='uvm_test_top.env.dma1.in_agt' name='is_active' (type PARAMTYPEDTYPE 'T') set accessor=uvm_test_top.env = (PARAMTYPEDTYPE 'T') 0
    UVM_INFO base/uvm_config_db_implementation.svh(485) @ 0: reporter [CFGDB/SET] Configuration scope='uvm_test_top.env.dma0.out_agt' name='is_active' (type PARAMTYPEDTYPE 'T') set accessor=uvm_test_top.env.dma0 = (PARAMTYPEDTYPE 'T') 0
    UVM_INFO base/uvm_config_db_implementation.svh(485) @ 0: reporter [CFGDB/SET] Configuration scope='uvm_test_top.env.dma1.out_agt' name='is_active' (type PARAMTYPEDTYPE 'T') set accessor=uvm_test_top.env.dma1 = (PARAMTYPEDTYPE 'T') 0
    • Every set names its accessor, the component that made it. dma1.in_agt was made passive by uvm_test_top.env, the subsystem env, which is exactly where the decision belongs. Value 0 is UVM_PASSIVE.

    • The config object is set by the test and read by the env, once each, with its field values printed. If the env had read before the test set, the GET would show the default object.

    • The trace is noisy. The same run prints 79 trace lines, most of them failed lookups the library makes for its own options (check_connection_relationships, wait_for_sequences_count). Grep for your field name.

    What breaks: a wildcard one level too wide

    Back to the opening. The test meant to make only dma1's input passive, and wrote this scratch test:

    design.sv
    1
    2
    3
    4
    5
    // snippet: scratch test, extends dma_subsys_test
    function void build_phase(uvm_phase phase);
      uvm_config_db#(uvm_active_passive_enum)::set(this, "env.dma*.in_agt", "is_active", UVM_PASSIVE);
      super.build_phase(phase);
    endfunction
    Diagram
    +UVM_TESTNAME=dma_cfg_wild_test   (scratch run)
    UVM_FATAL @ 275000: reporter@@pkt [SEQ] neither the item's sequencer nor dedicated sequencer has been supplied to start item in pkt
    UVM_FATAL :    1

    dma* matches dma0 too. The test's set outranks anything the env sets during build, so dma0.in_agt was built passive, with a monitor and no sequencer: the topology print in the same log shows in_agt under dma0 with only mon. The test then started its packet sequence on env.dma0.in_agt.sqr, a null handle, and the sequence library died at the first start_item. The crash is the lucky outcome; had the stimulus come from elsewhere, the run would simply have checked nothing.

    The tree prevents this by construction. The test calls cfg.apply_chain(), which names dma[1] explicitly, and the only is_active sets are made by each agent's parent on its own children. When something does go wrong, the triage order is short: turn on the trace, find the SET whose scope matches the component, read its accessor, and check the topology print for what was actually built.

    "How do you structure configuration in a multi-level testbench?"

    A weak answer: "With config_db. The test sets what it needs with wildcards, and components get their settings in build_phase."

    A strong answer describes ownership: "A typed config tree that mirrors the env tree: a block config with agent modes and has_scoreboard and has_coverage switches, nested inside the subsystem config, inside the chip config. The test builds the tree, calls a named policy function per level and validates it before build. Each env passes its children their branch with an exact set on its own direct children, so no wildcard crosses levels. config_db carries handles and config objects, not dozens of scalars; only the test reads plusargs. When something's wrong I turn on config_db tracing and read the accessor on each SET. We had a test whose wildcard on dma* made both DMAs' input agents passive, because a test set outranks the env during build; the tree makes that kind of mistake impossible."

    Follow-ups: Which set wins, an exact path from the env or a wildcard from the test? (during build the test's, because precedence comes from the setter's depth, not path specificity). Why shouldn't agents read plusargs? (an agent that reads the command line cannot be configured differently per instance or per level).

    Check yourself A config object is set once by the test and shared by reference with both DMA envs. In build_phase, dma1's env sets cfg.has_coverage = 0 for itself. What happens to dma0?

    It loses coverage too, because both envs hold the same object. Shared config objects are read-only after the set. Give each instance its own branch (cfg.dma[0], cfg.dma[1]), or clone before modifying, and decide per-instance policy in the test or the parent, not in the child.

    Check yourself The trace shows the env's GET of cfg returning a default object, and the test's SET appears later in the log. What is wrong, and what is the fix?

    The set ran after the env's build_phase read it, typically because the test created the env before setting the config, or set it in a later phase. Set first, then create: in the test's build_phase, build and set the config object before dma_subsys_env::type_id::create.

    Next: configuration decides which agents go passive. Passive Agents and Who Owns the Check decides what keeps checking when they do.

    Related topics

    • config_db resolution: who wins

    • Config objects instead of twenty knobs

    • Agent configuration

    • The command line in full

    Previous

    253 / 280

    Next: Passive Agents and Who Owns the Check

    Up next

    Up nextPassive Agents and Who Owns the Check

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Writing a Testplan from the Spec
    • Estimating Verification Effort
    • A Block Bench That Upstream Can Reuse