Configuration paths and virtual interfaces

Deliver a virtual interface and per-component settings through uvm_config_db, predict which setting wins, and track down a setting that never arrives.

Lesson
U2.3
Time
30 min
Runs on
Verilator with UVM
Check
5 questions

A colleague renames the incrementer bench's agent from agent to in_agent to make room for a second one. Every test now stops at time 0 with UVM_FATAL ... uvm_test_top.env.in_agent [NOVIF] no virtual interface for this agent. The top module still stores the interface for the old path, uvm_test_top.env.agent, and nothing in the language connects a string in one file to a component name in another.

The run ends during the build phase; no item is ever sent.
UVM_INFO @ 0: reporter [RNTST] Running test incr_test...
UVM_FATAL main.sv(94) @ 0: uvm_test_top.env.in_agent [NOVIF] no virtual interface for this agent
UVM_ERROR :    0
UVM_FATAL :    1

A lookup table keyed by path

uvm_config_db is a table of settings. Each entry has a scope (a component path, which may contain wildcards), a field name, a type and a value. A component asks for a value by its own path and the field name, and gets the best matching entry.

The two calls look alike but read differently. uvm_config_db #(T)::set(cntxt, inst_name, field, value) stores an entry whose scope is the full name of cntxt, a dot, then inst_name. With null as the context, the scope is inst_name alone, read from the root. uvm_config_db #(T)::get(this, "", field, var) looks up the caller's own full name.

CallMade fromScope stored or looked up
set(null, "uvm_test_top.env.agent", "vif", bus)top moduleuvm_test_top.env.agent
set(this, "env.agent", "vif", bus)the testuvm_test_top.env.agent
set(this, "env.*", "count", 5)the testuvm_test_top.env.*
get(this, "", "vif", vif)the agentuvm_test_top.env.agent

How the context and instance name combine into a scope.

The #(T) parameter is part of the key. An entry stored as int is invisible to a get for int unsigned, and an entry for virtual incr_if is invisible to a get for a differently parameterised interface. The field name must match exactly, including case.

get returns 1 when it found an entry and 0 when it did not, leaving the variable unchanged. Always test the result. For a required setting, such as a virtual interface, stop with uvm_fatal; for an optional one, the variable keeps the default you gave it.

Getting the interface into the classes

The design's signals live in a module, and classes cannot instantiate modules or interfaces. A virtual interface is a class variable that refers to an interface instance, and it is the only bridge from class code to pins.

The top module owns the interface instance, so it stores a handle to it before the test starts: uvm_config_db #(virtual incr_if)::set(null, "uvm_test_top.env.agent", "vif", bus); followed by run_test. Putting both in one initial block guarantees the order. The agent fetches the handle in its build_phase and hands it to its driver and monitor.

Settings for a sequence work the same way once it is running. A sequence is an object, but after start its full name extends its sequencer's path, for example uvm_test_top.env.agent.sqr.seq. So get(null, get_full_name(), "count", count) inside body() reads a setting the test stored for env.agent.sqr.*.

Which setting wins

Several entries can match one lookup. The test may set count for the agent while the environment sets it too. The rule during the build phase is that the setting made higher in the tree wins, so a test can always overrule the environment it builds.

Two settings for the same agent field during build
012build steptestenvagentsetting madecount=5count=9-setter depth12-agent would read5later, but lower: losesget returns 5

The environment's setting comes later, but it was made lower in the tree, so the test's value is the one the agent reads.

When two settings come from the same component, the later one wins. After the build phase, every new setting has the same weight, so the most recent one wins wherever it came from.

The top module counts as the root of the tree, the highest place of all. A value it stores before run_test therefore beats a setting the test makes with set(this, ...) for the same path during build. That surprises people who expect the test to have the last word. If a test must be able to change a value, store it from the test.

Where did my config go: a debug drill

A missing required setting fails loudly, as in the opening story. A missing optional setting is worse: the component quietly uses its default, and the test runs the wrong scenario. The example shows exactly that. Work through these steps whenever a setting seems lost.

  1. Find the reader's full name

    The uvm_fatal line, or get_full_name(), tells you the exact path the component looked up.

  2. Turn on tracing

    Call uvm_config_db_options::turn_on_tracing(); before run_test. Every set prints a CFGDB/SET line with its scope, and every get prints a CFGDB/GET line with the path it searched.

  3. List unread settings

    Call check_config_usage() in report_phase. It prints every entry that was written but never read, which is where a typo ends up.

  4. Compare type and timing

    Check that both sides use the same #(T), and that the set happens before the get, which in practice means before or during the reader's parent's build.

With tracing on, the renamed agent from the opening story produces this pair of lines. The stored scope and the searched path differ by one word.

Trace lines, shortened. "failed lookup" means no entry matched this path, type and field.
[CFGDB/SET] Configuration scope='uvm_test_top.env.agent' name='vif' ... set accessor= ...
[CFGDB/GET] Configuration scope='uvm_test_top.env.in_agent' name='vif' ... read accessor=uvm_test_top.env.in_agent = null (failed lookup)

The library makes lookups of its own, so a real trace holds dozens of CFGDB/GET lines for fields you never set, each ending in a failed lookup. Search the trace for your field name and compare only those lines.

check_config_usage() also lists settings that were read by nobody because a higher setting won. That is useful too: it shows a value you thought was in effect and was not.

Keep scopes narrow

A setting for "*" reaches every component. It works until a second agent arrives, and then both agents read the same interface. Store each setting for the narrowest path that needs it, such as uvm_test_top.env.agent, and use wildcards only for values that really are shared.

When one component needs several settings, put them in one configuration object, a class holding the virtual interface, the item count and any other knobs. The test fills it in and stores it with a single set. The agent reads it with a single get, so there is one path and one type to get right.

Try it

The test asks for five items, but the scoreboard reports three checked: the sequence never found the setting and kept its default. The CFGNRD report from check_config_usage() names the lost entry, and its scope shows the typo, agnet for agent.

Fix the path to "env.agent.sqr.*" and run again: the scoreboard checks five items and the CFGNRD report disappears. Then change the top module's path to "uvm_test_top.env.agnt" and read the fatal error it causes.

RUNVerilator + UVM 2020.3.1 ยท simulator service
Edit the code, then run it
Expected among the UVM messages: UVM_INFO main.sv(81) @ 60: uvm_test_top.env.sb [SB] 3 checked, 0 errors UVM_INFO @ 60: uvm_test_top [CFGNRD] ::: The following resources have at least one write and no reads ::: name: count value: 5 scope: uvm_test_top.env.agnet.sqr.* UVM_ERROR : 0

Check yourself

Predict the outputWhat line does the $display print? UVM's own messages are not part of the answer.
`include "uvm_macros.svh"
import uvm_pkg::*;
class agent_c extends uvm_agent;
  `uvm_component_utils(agent_c)
  int n = -1, m = -1;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    void'(uvm_config_db #(int)::get(this, "", "n", n));
    void'(uvm_config_db #(int)::get(this, "", "m", m));
    $display("n=%0d m=%0d", n, m);
  endfunction
endclass
class env_c extends uvm_env;
  `uvm_component_utils(env_c)
  agent_c agent;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    uvm_config_db #(int)::set(this, "agent", "n", 9);
    uvm_config_db #(int)::set(this, "agent", "m", 1);
    uvm_config_db #(int)::set(this, "agent", "m", 2);
    agent = agent_c::type_id::create("agent", this);
  endfunction
endclass
class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  env_c env;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    uvm_config_db #(int)::set(this, "env.agent", "n", 5);
    env = env_c::type_id::create("env", this);
  endfunction
endclass
module tb;
  initial run_test("my_test");
endmodule
Spot the bugEach option is the line in the top module that stores the virtual interface before run_test("cfg_test"). The agent is uvm_test_top.env.agent and stops with uvm_fatal if its get fails. Which line makes the run fail?
ConceptThe test stores uvm_config_db #(int)::set(this, "env.agent", "count", 5), and the agent reads it with uvm_config_db #(int unsigned)::get(this, "", "count", count). What does the agent get?
Read the waveformThe top module stores count=3 for uvm_test_top.env.agent before run_test. The test then calls uvm_config_db #(int)::set(this, "env.agent", "count", 5) in its build_phase. What does the agent read?
Settings for the agent's count, in the order they are made
0123steptop moduletest buildenv buildagent buildsetting madecount=3count=5-made fromrootuvm_test_top-agent calls get

Both settings have the scope uvm_test_top.env.agent.

ConceptAn agent reads count with get in its build_phase. Where can the test store count so that the agent sees it?

Remember

  • set stores a value under a scope built from the context's full name and the instance name; get(this, "", ...) looks up the caller's own path.
  • The type parameter and field name are part of the key; a mismatch makes get return 0 with no warning.
  • During build, the setting made higher in the tree wins, and the top module counts as the root; between equals, the later setting wins.
  • Fail with uvm_fatal when a required setting such as a virtual interface is missing; give optional settings a default.
  • To find a lost setting, compare the reader's full name with the stored scope, using turn_on_tracing() and check_config_usage().