A teammate renames the DMA env's input agent from in_agt to s_agt to match the new naming guide. Everything compiles. At time 0 the run dies: UVM_FATAL ... uvm_test_top.env.s_agt.drv [NOVIF] axis_if not set for this driver. The interface is still in the top module, the set call is still there, and nothing about the driver changed. A set and a get that used to meet no longer do. Why?
config_db is a shared notice board. A set pins a note with three things written on it: who it is addressed to (a scope, possibly with wildcards), a label (the field name), and what kind of thing is inside (the type, fixed by the class parameter T). A get walks up to the board and looks for notes addressed to its own full name, with its label, of its type. All three must match. If any one differs, the reader finds nothing, and the board never tells the writer that the note went unread.
Two consequences follow. The scope is a string, so renaming a component silently changes who a note reaches. And the type is part of the key, so virtual axis_if and virtual apb_if, or int and int unsigned, are different boards entirely.
Argument | On set | On get |
|---|---|---|
| Where the path starts. | Usually |
| Appended to the context's full name with a dot; may contain wildcards. | Usually |
| The label. | Must be the same label. |
| Stored under type | Written only on success; left unchanged on a miss. |
The scope of a set is {cntxt.get_full_name(), ".", inst_name}, or just inst_name when the context is null (or just the context's full name when inst_name is empty). The get side builds its key the same way. Keep that formula in your head and most config bugs become string comparisons.
The top module has no component context, so it sets from null with absolute paths. Each set reaches every component under one agent:
// snippet: from the course DMA bench
module top;
import uvm_pkg::*;
import dma_pkg::*;
logic clk = 0;
always #5 clk = ~clk; // 100 MHz
rst_if rst (clk);
wire rst_n = rst.rst_n;
initial begin
rst.rst_n = 1'b0;
repeat (4) @(posedge clk);
rst.rst_n <= 1'b1;
end
apb_if apb (clk, rst_n);
axis_if s_ax (clk, rst_n);
axis_if m_ax (clk, rst_n);
irq_if irqi (clk);
// The output side accepts every beat unless a test adds a reactive slave
// (axis_sink_agent), whose driver then drives TREADY.
initial m_ax.tready = 1'b1;
dma_dut dut (
.clk, .rst_n,
.paddr(apb.paddr), .psel(apb.psel), .penable(apb.penable), .pwrite(apb.pwrite),
.pwdata(apb.pwdata), .prdata(apb.prdata), .pready(apb.pready), .pslverr(apb.pslverr),
.s_tdata(s_ax.tdata), .s_tvalid(s_ax.tvalid), .s_tlast(s_ax.tlast), .s_tready(s_ax.tready),
.m_tdata(m_ax.tdata), .m_tvalid(m_ax.tvalid), .m_tlast(m_ax.tlast), .m_tready(m_ax.tready),
.irq(irqi.irq)
);
initial begin
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.apb.*", "vif", apb);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.in_agt.*", "vif", s_ax);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.out_agt.*", "vif", m_ax);
uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top", "irq_vif", irqi);
uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top.env.irq.*", "vif", irqi);
uvm_config_db#(virtual rst_if)::set(null, "uvm_test_top*", "rst_vif", rst);
run_test("dma_smoke_test");
end
endmoduleA missing interface is not something to tolerate, so the bench's gets are checked: a miss is a fatal error with a message that says what was expected.
// snippet: from axis_driver::build_phase in the bench
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual axis_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "axis_if not set for this driver")
endfunctionOptional settings use the other half of the contract: the value is untouched on a miss, so initialize the variable to its default and let a successful get overwrite it.
To ask whether a setting exists without reading it, use uvm_config_db#(T)::exists(cntxt, inst_name, field_name, spell_chk). With spell_chk set to 1, a miss makes UVM suggest close matches for the field name, which is a cheap way to catch vif versus v_if in a component that has an optional setting.
For a component, get(this, "", ...) and get(null, get_full_name(), ...) build the same key; the first is shorter and is the convention. The second form is what objects such as sequences use, because they cannot be a context (Configuring sequences).
The env tells the output agent to stay passive. It sets relative to itself, before creating the child, so the scope becomes uvm_test_top.env.out_agt:
// snippet: from dma_env::build_phase in the bench
uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE);
out_agt = axis_agent::type_id::create("out_agt", this);The agent never calls get for is_active itself: uvm_agent's own build_phase, reached through super.build_phase, looks it up. That mechanism is auto-config.
When every set and get agree, nothing about config appears in the log, which is the point. The smoke test runs to completion:
+UVM_TESTNAME=dma_smoke_test
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
UVM_ERROR : 0
UVM_FATAL : 0And the topology at the start shows out_agt with only mon under it: the env's set reached the agent. When a get misses, the checked get turns silence into a time-0 fatal with the getter's full path, which is exactly the string you compare against the set's scope.
Back to the opening. After the rename, the driver's key is uvm_test_top.env.s_agt.drv, but the top still sets scope uvm_test_top.env.in_agt.*. The glob does not match, the get returns 0, and the checked get fires. The set was not wrong when it was written; it encoded the env's internal names in the top module, two levels away from the code that chose them.
Two fixes, from quick to robust. Quick: update the top's path. Robust: move knowledge of internal names next to the code that owns them. The top sets each interface once, on the env, under a distinct field name (for example set(null, "uvm_test_top.env", "s_vif", s_ax)), and the env passes it down to in_agt, by config_db or inside an agent config object. Then a rename inside the env is a change inside the env. Config objects shows the object version.
uvm_config_db#(int)::set(this, "in_agt.drv", "gap", 2) and the driver calls uvm_config_db#(int unsigned)::get(this, "", "gap", gap). Does the driver see 2?No. The scope and name match, but the types differ (int versus int unsigned), and config_db only finds resources of the identical type. The get returns 0 and gap keeps its initial value.
uvm_config_db#(int)::set(this, "", "n", 5) create when called from uvm_test_top.env, and who can read it?The scope is uvm_test_top.env (an empty instance name means the context itself). Only a get whose key is exactly uvm_test_top.env sees it, normally the env reading its own setting with get(this, "", "n", n). Its children do not, because their full names are longer.
uvm_fatal instead of a warning?A driver without an interface fails later with a null-handle access deep in run_phase, far from the cause. A fatal at time 0 with the getter's full path and a clear message points straight at the mismatched set.
Next: the scope pattern decides who can read a note. Instance paths and wildcards covers the patterns, including the one that excludes the very component you meant.