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.
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 : 1A 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.
| Call | Made from | Scope stored or looked up |
|---|---|---|
set(null, "uvm_test_top.env.agent", "vif", bus) | top module | uvm_test_top.env.agent |
set(this, "env.agent", "vif", bus) | the test | uvm_test_top.env.agent |
set(this, "env.*", "count", 5) | the test | uvm_test_top.env.* |
get(this, "", "vif", vif) | the agent | uvm_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.
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.
Find the reader's full name
The
uvm_fatalline, orget_full_name(), tells you the exact path the component looked up.Turn on tracing
Call
uvm_config_db_options::turn_on_tracing();beforerun_test. Every set prints aCFGDB/SETline with its scope, and every get prints aCFGDB/GETline with the path it searched.List unread settings
Call
check_config_usage()inreport_phase. It prints every entry that was written but never read, which is where a typo ends up.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.
[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.
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 : 0Check yourself
Remember
setstores 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
getreturn 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_fatalwhen 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()andcheck_config_usage().