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.
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:
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
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.
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.
// ── 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
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
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:
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
// 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
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
| Type | Mechanism | When set | Example |
|---|---|---|---|
| Runtime configurable | Config object + config_db | Simulation runtime (test build_phase) | num_transactions, is_active, coverage enable |
| Compile-time constant | Parameters package | Compilation / elaboration | DATA_WIDTH, NUM_GPIOS, address map constants |
| Command-line overridable | plusargs | Simulator invocation | +num_txns=100, +UVM_TESTNAME=my_test |
| Item | Key fact |
|---|---|
| Config object base class | uvm_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 config | Cannot use `uvm_field macros — exclude from automation region |
| Hierarchy mirroring rule | One config object per component level. Env config nests agent configs. |
| Nesting pattern | Test creates all configs, nests agent configs into env config, sets env config once |
| Env re-distributes | Env gets env_cfg, extracts nested agent_cfgs, sets each into config_db for its agents |
| Virtual configure functions | Delegate config field assignments to virtual functions — derived tests override only what they need |
| Sequence config pattern 1 | Set sequence fields directly before seq.start() in run_phase |
| Sequence config pattern 2 | Sequence calls config_db::get() using m_sequencer as context |
| Params package use case | Compile-time constants — bus widths, address maps, DUT parameters |
| Default values | All config fields must have safe defaults — testbench works without any override |