Configuring components in UVM means giving a test control over how the environment is built and how its components behave, without the test needing to know how those components are connected inside. UVM offers three tools for this: configuration knobs on the components, the uvm_config_db database that carries values down the hierarchy, and configuration objects that bundle related settings into one class.

A test writer usually wants to say things like "make this agent passive" or "turn coverage off for this run". They should not have to edit the environment to do it. Good configuration design is what makes one environment serve hundreds of tests.

8 min read | Intermediate Level

What You'll Learn

  • Add configuration knobs such as active/passive mode to verification components
  • Pass a setting from a test to a component with uvm_config_db
  • Get scope strings right so the setting reaches the intended components
  • Bundle related settings into configuration objects and pass them down level by level

Configuration Knobs

A knob is a variable in a component that changes how it is built or how it behaves. The most common one is built into uvm_agent: is_active, of type uvm_active_passive_enum.

  • UVM_ACTIVE - the agent builds a driver, a sequencer and a monitor, and drives transactions into the DUT.
  • UVM_PASSIVE - the agent builds only the monitor and observes the interface. This is how an agent is reused at a higher level of integration, where another block already drives that interface.

Other typical knobs enable coverage collection or protocol checks in a monitor, or set design-specific options such as the number of slave agents in a bus environment, or the speed and mode of an interface. The component reads each knob in its build_phase and decides what to create.

Passing a Setting with uvm_config_db

uvm_config_db is a database that any component can write to and read from. A setting is stored with a scope - a hierarchical path that says which components may see it - plus a field name, a type and a value.


// In the test: make the agent passive
uvm_config_int::set(this, "m_env.m_agent", "is_active", UVM_PASSIVE);

// In the top-level module: share the virtual interface with everything below the test
uvm_config_db #(virtual bus_if)::set(null, "uvm_test_top.*", "vif", bus_if0);

// In a component: read a value meant for this component
if (!uvm_config_db #(virtual bus_if)::get(this, "", "vif", vif))
  `uvm_fatal("NOVIF", "Virtual interface not found in config_db")

The scope of a set() is the full name of its first argument followed by the second argument. In the first line the test's full name is uvm_test_top, so the setting applies to uvm_test_top.m_env.m_agent. A get(this, "", ...) looks for a setting whose scope matches the caller's own full name.

Scopes are glob patterns, and they match exactly unless you use a wildcard. "uvm_test_top.*" matches every component below the test. "uvm_test_top" on its own matches only the test itself, so a driver deep in the hierarchy would never see it. The full rules are covered in UVM Config Database.

Quick Check

Q: The top module calls uvm_config_db#(virtual bus_if)::set(null, "uvm_test_top", "vif", bus_if0). Can the driver at uvm_test_top.m_env.m_agent.m_driver get it with get(this, "", "vif", vif)?

Show Answer

No. The scope "uvm_test_top" matches only the test. The driver's lookup uses its own full name, which does not match, so get() returns 0. Use "uvm_test_top.*", or a path that names the driver.

Using a Configuration Object

Once a component has more than two or three settings, passing them one at a time becomes hard to manage. The usual solution is a configuration object: a class derived from uvm_object that holds every setting for one component, passed through uvm_config_db as a single item.


class agent_cfg extends uvm_object;
  `uvm_object_utils(agent_cfg)
  uvm_active_passive_enum is_active    = UVM_ACTIVE;
  bit                     has_coverage = 1;
  virtual bus_if          vif;

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

class env_cfg extends uvm_object;
  `uvm_object_utils(env_cfg)
  rand int unsigned num_slaves;
  constraint c_slaves { num_slaves inside {[1:4]}; }
  agent_cfg m_agt_cfg;                     // nested config for the agent

  function new(string name = "env_cfg");
    super.new(name);
    m_agt_cfg = agent_cfg::type_id::create("m_agt_cfg");
  endfunction
endclass

Because it is a class, a configuration object can be randomized with constraints that keep the settings legal, and a derived class can add options for a new project without touching the original.

Passing It Down the Hierarchy

Each level reads its own configuration and hands the next level only the part it needs.


// Test: build and set the environment's configuration
virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  m_env_cfg = env_cfg::type_id::create("m_env_cfg");
  if (!m_env_cfg.randomize())
    `uvm_fatal("CFG", "env_cfg randomization failed")
  m_env_cfg.m_agt_cfg.is_active = UVM_PASSIVE;            // this test only observes
  if (!uvm_config_db #(virtual bus_if)::get(this, "", "vif", m_env_cfg.m_agt_cfg.vif))
    `uvm_fatal("NOVIF", "Virtual interface not found")
  uvm_config_db #(env_cfg)::set(this, "m_env", "cfg", m_env_cfg);
  m_env = my_env::type_id::create("m_env", this);
endfunction

// Environment: read env_cfg, pass the agent its part
virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  if (!uvm_config_db #(env_cfg)::get(this, "", "cfg", m_cfg))
    `uvm_fatal("NOCFG", "env_cfg not found")
  uvm_config_db #(agent_cfg)::set(this, "m_agent", "cfg", m_cfg.m_agt_cfg);
  m_agent = my_agent::type_id::create("m_agent", this);
endfunction

// Agent: read agent_cfg, build only what is needed
virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  if (!uvm_config_db #(agent_cfg)::get(this, "", "cfg", m_cfg))
    `uvm_fatal("NOCFG", "agent_cfg not found")
  m_monitor = my_monitor::type_id::create("m_monitor", this);
  if (m_cfg.is_active == UVM_ACTIVE) begin
    m_driver    = my_driver::type_id::create("m_driver", this);
    m_sequencer = my_sequencer::type_id::create("m_sequencer", this);
  end
endfunction

This works because build_phase runs top-down. The test sets the environment's configuration before it creates the environment, and the environment does the same for the agent, so every get() finds a value that was set moments earlier by its parent.

Calling `uvm_fatal when a required configuration is missing turns a confusing null-handle crash later in the run into a clear message at build time. Use `uvm_warning or a default value only for settings that are genuinely optional.

Reusable verification IP for bus protocols is normally shipped with its own configuration class. An SoC team then instantiates the same agent many times, active in block-level tests and passive in chip-level tests, changing only the configuration object each test passes in.

Common Mistakes

Mistake #1: Using a scope that matches only the parent

Wrong: set(null, "uvm_test_top", "vif", bus_if0) when a driver needs the interface
Right: set(null, "uvm_test_top.*", "vif", bus_if0)

Why? A scope without a wildcard matches one exact path. Components below that path look up their own full names and do not find the setting.

Mistake #2: Setting and getting with different types

A value set with uvm_config_db#(int) is a different entry from one set with uvm_config_db#(bit) or uvm_config_db#(virtual bus_if). The get() must use exactly the type the set() used, or it returns 0.

Mistake #3: Setting a value after the component has read it

A component reads its build-time configuration in build_phase. Setting that value from the test's connect_phase is too late, because every build_phase in the hierarchy has already run by then. Set build-time configuration in the parent's build_phase, which always finishes before its children's build_phase starts.

Mistake #4: Ignoring the return value of get()

get() returns 1 when it finds a value and 0 when it doesn't, and leaves the variable unchanged on a miss. Code that ignores the result carries on with a null handle or a default value, and fails much later in a place that has nothing to do with the real problem.

Summary

In this article, you learned:

  • Knobs such as is_active let a test change how a component is built without editing it
  • uvm_config_db stores values with a scope, a field name and a type, and get() must match all three
  • A scope without a wildcard matches one exact path, and ".*" extends it to everything below
  • Configuration objects bundle related settings, support randomization, and are passed down one level at a time
  • Because build_phase runs top-down, each parent sets its children's configuration before creating them
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5