UVM-09: Configuration Objects — VLSI Trainers
VLSI Trainers UVM Series · UVM-09
UVM Series · UVM-09

Configuration Objects

Designing configuration objects that mirror the testbench hierarchy, nested configs, the configure_* virtual function pattern for extensible base tests, configuring sequences through the sequencer, and the params package for compile-time constants.

📋 Why Config Objects

Configuration objects are the primary mechanism for making a UVM testbench configurable without modifying its source code. Instead of hard-coding values like the number of transactions, whether coverage is enabled, or the address range of a peripheral, these values are placed in a configuration object. The test creates and populates the object, and components retrieve it to control their own behaviour.

The key advantages over passing individual values through config_db:

📋 Anatomy of a Config Object

A config object extends uvm_object — never uvm_component. It has no phases, no parent, no hierarchy. It is a plain data container. The minimum structure for any agent config:

class apb_agent_cfg extends uvm_object;
  `uvm_object_utils_begin(apb_agent_cfg)
    `uvm_field_int(is_active,          UVM_ALL_ON)
    `uvm_field_int(has_coverage,        UVM_ALL_ON)
    `uvm_field_int(has_err_injection,   UVM_ALL_ON)
    `uvm_field_int(max_wait_states,     UVM_ALL_ON)
  `uvm_object_utils_end

  // ── Mandatory ──────────────────────────────────────────
  virtual apb_if          vif;            // virtual interface handle
  uvm_active_passive_enum is_active = UVM_ACTIVE;

  // ── Optional sub-component enables ─────────────────────
  bit has_coverage      = 1;   // default: coverage ON
  bit has_err_injection = 0;   // default: no error injection

  // ── Protocol-specific behaviour ─────────────────────────
  int max_wait_states   = 4;
  bit check_pslverr     = 1;

  function new(string name = "apb_agent_cfg");
    super.new(name);
  endfunction
endclass
Set all fields to safe defaults. The config object must work correctly even if no test customises it. Active mode on, coverage on, no error injection — sensible defaults mean that a minimally configured test still produces useful results. Tests override only the fields they care about.

The `uvm_field_* macros inside `uvm_object_utils_begin/end enable automatic do_print(), do_copy(), and do_compare() — useful for debugging. Note that virtual interface handles cannot use `uvm_field macros; exclude them from the field automation region.

📋 Mirroring the Hierarchy

The most important structural principle for config objects: each config object mirrors exactly one level of the component hierarchy. The env has one env config. Each agent has one agent config. If a sub-env exists, it has its own sub-env config.

Config Objects Mirror Component Hierarchy Component Hierarchy my_test my_env apb_agent spi_agent scoreboard mirrors Config Objects my_env_cfg has_sb, has_cov (env flags) nested ↓ apb_agent_cfg spi_agent_cfg Each config lives inside env_cfg as a handle Flow — Test creates all, sets one ① test creates env_cfg + agent_cfgs ② nests agent_cfgs inside env_cfg ③ config_db::set(env_cfg) — one call ④ env gets it, redistributes agent_cfgs vlsitrainers.com
Figure 1 — Config objects mirror the component hierarchy. Each component has exactly one config object. The env config nests the agent configs as member handles. The test creates all config objects, nests them, then makes a single config_db::set() call with the top-level env config. The env retrieves it and re-distributes the nested agent configs to each agent.

📋 Nested Config Pattern — Complete Example

// ── Env config — holds all agent configs ─────────────────
class my_env_cfg extends uvm_object;
  `uvm_object_utils(my_env_cfg)

  // Env-level flags
  bit has_scoreboard = 1;
  bit has_coverage   = 1;

  // Nested agent configs
  apb_agent_cfg m_apb_cfg;
  spi_agent_cfg m_spi_cfg;

  function new(string name = "my_env_cfg");
    super.new(name);
  endfunction
endclass

// ── Test build_phase: create all, nest, set once ──────────
function void build_phase(uvm_phase phase);
  super.build_phase(phase);

  m_env_cfg = my_env_cfg::type_id::create("m_env_cfg");

  // Create agent configs and assign into env config
  m_env_cfg.m_apb_cfg = apb_agent_cfg::type_id::create("m_apb_cfg");
  m_env_cfg.m_spi_cfg = spi_agent_cfg::type_id::create("m_spi_cfg");

  // Assign virtual interfaces into the nested configs
  if (!uvm_config_db #(virtual apb_if)::get(
        this, "", "apb_vif", m_env_cfg.m_apb_cfg.vif))
    `uvm_fatal("CFG", "No APB vif!")
  if (!uvm_config_db #(virtual spi_if)::get(
        this, "", "spi_vif", m_env_cfg.m_spi_cfg.vif))
    `uvm_fatal("CFG", "No SPI vif!")

  // Single set() call for the entire config tree
  uvm_config_db #(my_env_cfg)::set(
    this, "m_env", "cfg", m_env_cfg);

  m_env = my_env::type_id::create("m_env", this);
endfunction

// ── Env build_phase: get top config, redistribute children
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  if (!uvm_config_db #(my_env_cfg)::get(
        this, "", "cfg", m_cfg))
    `uvm_fatal("ENV", "No env config!")

  // Push each nested config to the matching agent
  uvm_config_db #(apb_agent_cfg)::set(
    this, "m_apb_agent", "cfg", m_cfg.m_apb_cfg);
  uvm_config_db #(spi_agent_cfg)::set(
    this, "m_spi_agent", "cfg", m_cfg.m_spi_cfg);

  m_apb_agent = apb_agent::type_id::create("m_apb_agent", this);
  m_spi_agent = spi_agent::type_id::create("m_spi_agent", this);
  if (m_cfg.has_scoreboard)
    m_sb = my_scoreboard::type_id::create("m_sb", this);
endfunction

📋 Virtual Configure Functions

A powerful pattern for extensible base tests: instead of setting every field directly in build_phase, the test delegates configuration to virtual configure_* functions. Derived tests override only the functions for the sub-components they want to change.

class base_test extends uvm_test;
  `uvm_component_utils(base_test)

  my_env_cfg    m_env_cfg;
  apb_agent_cfg m_apb_cfg;
  my_env        m_env;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_env_cfg         = my_env_cfg::type_id::create("m_env_cfg");
    m_env_cfg.m_apb_cfg = apb_agent_cfg::type_id::create("m_apb_cfg");
    if (!uvm_config_db #(virtual apb_if)::get(
          this, "", "apb_vif", m_env_cfg.m_apb_cfg.vif))
      `uvm_fatal("CFG", "No vif!")

    // Delegate to virtual functions — overridable by derived tests
    configure_env(m_env_cfg);
    configure_apb_agent(m_env_cfg.m_apb_cfg);

    uvm_config_db #(my_env_cfg)::set(
      this, "m_env", "cfg", m_env_cfg);
    m_env = my_env::type_id::create("m_env", this);
  endfunction

  // Overridable configure functions — set safe defaults
  virtual function void configure_env(my_env_cfg cfg);
    cfg.has_scoreboard = 1;
    cfg.has_coverage   = 1;
  endfunction

  virtual function void configure_apb_agent(apb_agent_cfg cfg);
    cfg.is_active         = UVM_ACTIVE;
    cfg.has_err_injection = 0;
    cfg.max_wait_states   = 4;
  endfunction
endclass

// ── Derived error injection test — only overrides agent config
class err_inject_test extends base_test;
  `uvm_component_utils(err_inject_test)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  // Override ONLY the agent config — env config stays as base_test defined
  virtual function void configure_apb_agent(apb_agent_cfg cfg);
    super.configure_apb_agent(cfg);  // apply base defaults first
    cfg.has_err_injection = 1;      // then enable error injection
  endfunction
endclass
Virtual configure functions make test families maintainable. A project typically has one base_test with sensible defaults and dozens of derived tests that each override one or two configure_* functions. Adding a new configuration parameter means adding it to one configure_* function — all derived tests inherit the new default without any changes.

📋 Configuring Sequences

Sequences are objects — they have no component path and cannot be directly targeted by config_db using a component hierarchy path. There are two clean patterns for passing configuration into sequences:

Pattern 1 — Set fields directly before starting

task run_phase(uvm_phase phase);
  write_seq seq;
  phase.raise_objection(this);
  seq = write_seq::type_id::create("seq");
  // Set sequence fields before starting
  seq.num_transactions = 50;
  seq.addr_range_lo    = 32'h0000;
  seq.addr_range_hi    = 32'h01FF;
  seq.start(m_env.m_agent.m_seqr);
  phase.drop_objection(this);
endtask

Pattern 2 — Sequence retrieves config_db using sequencer as context

// In sequence body() — use the sequencer's path as context
task body();
  apb_agent_cfg cfg;
  // The sequencer IS a component — use it as get() context
  if (!uvm_config_db #(apb_agent_cfg)::get(
        m_sequencer, "", "cfg", cfg))
    `uvm_warning("SEQ", "No agent cfg — using defaults")
  // Use cfg fields to control sequence behaviour
  repeat(cfg != null ? cfg.max_wait_states : 4) begin
    // ... generate items
  end
endtask
Pattern 1 is simpler and more common. Use pattern 2 only when the sequence needs to adapt based on configuration set by the test — for example, a sequence that generates a different number of transactions depending on a feature enable flag. Both patterns are valid; choose based on how often the sequence parameters change between tests.

📋 Params Package for Compile-Time Constants

Some values cannot be runtime-configurable — bus widths, memory sizes, interface parameters — because they must be fixed at elaboration time. These belong in a parameters package that is shared between the DUT and the testbench.

// File: test_params_pkg.sv
package test_params_pkg;
  // DUT parameters — must match the DUT instantiation
  parameter int DATA_WIDTH   = 32;
  parameter int ADDR_WIDTH   = 12;
  parameter int NUM_GPIOS    = 32;

  // Memory map — used by both DUT and sequences
  parameter logic [ADDR_WIDTH-1:0]
    ADDR_DATA  = 12'h000,
    ADDR_DIR   = 12'h004,
    ADDR_IEN   = 12'h008;

  // Testbench constants
  parameter int DEFAULT_TIMEOUT_NS = 10_000;
endpackage

// In testbench files — import at the top
import test_params_pkg::*;

// Sequences use the constants without magic numbers
task body();
  item.addr = ADDR_DATA;   // self-documenting
endtask
TypeMechanismWhen setExample
Runtime configurableConfig object + config_dbSimulation runtime (test build_phase)num_transactions, is_active, coverage enable
Compile-time constantParameters packageCompilation / elaborationDATA_WIDTH, NUM_GPIOS, address map constants
Command-line overridableplusargsSimulator invocation+num_txns=100, +UVM_TESTNAME=my_test

📋 Quick Reference

ItemKey fact
Config object base classuvm_object — not uvm_component. No phases, no parent, no path.
Registration macro`uvm_object_utils(ClassName)
Field automation`uvm_object_utils_begin/end + `uvm_field_int etc — enables do_print/copy/compare
Virtual interface in configCannot use `uvm_field macros — exclude from automation region
Hierarchy mirroring ruleOne config object per component level. Env config nests agent configs.
Nesting patternTest creates all configs, nests agent configs into env config, sets env config once
Env re-distributesEnv gets env_cfg, extracts nested agent_cfgs, sets each into config_db for its agents
Virtual configure functionsDelegate config field assignments to virtual functions — derived tests override only what they need
Sequence config pattern 1Set sequence fields directly before seq.start() in run_phase
Sequence config pattern 2Sequence calls config_db::get() using m_sequencer as context
Params package use caseCompile-time constants — bus widths, address maps, DUT parameters
Default valuesAll config fields must have safe defaults — testbench works without any override
Scroll to Top