Someone tidies axis_agent::build_phase and deletes the super.build_phase(phase) line, because "the base class doesn't do anything". The smoke test now fails with scoreboard mismatches on the output side. The topology explains it: out_agt has a driver and a sequencer. The env still sets is_active to UVM_PASSIVE for it, and nobody reads that setting anymore. The base class did something after all.
Most config is pulled explicitly: a component calls get for what it needs. Auto-config is the exception: for members you declared with field macros (` uvm_field_int `, uvm_field_enum `, uvm_field_object ` and so on, inside uvm_component_utils_begin ` / uvm_component_utils_end `), the base uvm_component` looks up a setting with the member's name at the component's own path and, if one exists, assigns it. It is like a secretary who sorts your mail into labelled trays before you arrive, but only for the trays you put on your desk, and only if you let the secretary into the office.
Letting the secretary in is super.build_phase(phase). uvm_component::build_phase calls build(), which calls apply_config_settings() (in IEEE 1800.2, only when use_automatic_config() returns 1, the default). uvm_agent adds one more lookup of its own in its build_phase: is_active, accepted as the enum, an int, an int unsigned, a bitstream or a string. Skip super.build_phase and none of it happens.
Both bench agents call super.build_phase first, and then ask get_is_active(). The comment in the APB agent says why:
// snippet: from apb_agent::build_phase in the bench
function void build_phase(uvm_phase phase);
super.build_phase(phase); // reads is_active from config_db
mon = apb_monitor::type_id::create("mon", this);
if (get_is_active() == UVM_ACTIVE) begin
sqr = apb_sequencer::type_id::create("sqr", this);
drv = apb_driver::type_id::create("drv", this);
end
endfunctionField macros are legal on components, inside the _begin/_end form of the registration macro. Each declared member becomes auto-configurable under its own name. A driver variant with a configurable idle cap:
// snippet: a component member configured by auto-config
class axis_capped_driver extends axis_driver;
int max_idle = 8;
`uvm_component_utils_begin(axis_capped_driver)
`uvm_field_int(max_idle, UVM_DEFAULT)
`uvm_component_utils_end
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase); // max_idle is assigned here if a setting exists
`uvm_info("CFG", $sformatf("max_idle=%0d", max_idle), UVM_MEDIUM)
endfunction
function axis_item pre_drive(axis_item it);
axis_item b;
if (it.idle <= max_idle) return it;
$cast(b, it.clone());
b.idle = max_idle;
return b;
endfunction
endclass
// In a test's build_phase:
uvm_config_db#(int)::set(this, "env.in_agt.drv", "max_idle", 2);No explicit get anywhere. Auto-config for an integral field accepts settings of type int, int unsigned, uvm_integral_t and uvm_bitstream_t, which is more forgiving than an explicit get. A member flagged UVM_READONLY is skipped by auto-config.
The command-line forms +uvm_set_config_int=<path>,<field>,<value> and +uvm_set_config_string become config_db sets from the top during build. The integer form stores a uvm_bitstream_t. Who sees it:
Reader | Sees |
|---|---|
Auto-config of a | Yes |
| Yes |
| Yes |
| No: config_db matches the exact type |
So +uvm_set_config_int=uvm_test_top.env.in_agt.drv,max_idle,0 configures the driver above with no code change, while the same plusarg aimed at a member read with config_db#(int)::get does nothing. The command-line forms are covered in Command-line overrides.
Timing follows from where auto-config runs. It happens at the start of the component's own build, so a setting made by the parent in the parent's build_phase, before it creates the child, arrives in time. An initial value given in the declaration or the constructor is only a default: if a setting exists, auto-config overwrites it during super.build_phase, and code after that line sees the configured value.
Auto-config is convenient and quiet, and the quiet part is the problem. An explicit get is greppable, typed, and can fail loudly when a required setting is missing; auto-config never reports a miss, and it costs one lookup per field-macro member per component during build. Many teams therefore allow field macros on components only for a few simple, optional knobs, and use explicit, checked gets (usually of one config object) for anything that matters. The DMA bench takes that line: no component uses field macros, and every required setting is a checked get. Whatever your team decides, uvm_agent::is_active is read by the base class either way, so the super.build_phase rule applies to every agent.
Auto-config's effect on the shipped bench is in the topology printed at the start of every run: out_agt holds only mon and its analysis port, because uvm_agent::build_phase read UVM_PASSIVE before the agent decided what to build. The run then checks every word:
+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 : 0To see auto-config decisions themselves, set the static bit uvm_component::print_config_matches = 1 (UVM 1.2) before build; components then report each setting they apply.
Back to the opening, in detail. Without super.build_phase, is_active keeps its declared default, UVM_ACTIVE, so out_agt builds a sequencer and a driver. The driver starts by driving TVALID low on the output interface, the port the DUT drives. The output monitor no longer sees the DUT's words cleanly, and the scoreboard reports mismatches or leftover predictions. The env's setting was correct the whole time; nobody read it.
The same bug has a quieter twin. A team's scoreboard had a field-macro member strict_mode configured by tests. A later refactor gave the scoreboard its own build_phase that created two FIFOs and forgot the super call. Every test that turned strict_mode off silently ran strict, and a class of legitimately reordered transactions started failing intermittently. The fix both times is the rule: every `build_phase` override starts with `super.build_phase(phase)`, even when the parent "does nothing" today. Lint for it.
is_active with uvm_config_db#(int)::set(this, "out_agt", "is_active", 0) instead of the enum. Is out_agt passive?Yes. uvm_agent's lookup accepts is_active as the enum, int, int unsigned, a bitstream or a string. 0 is UVM_PASSIVE. An explicit uvm_config_db#(uvm_active_passive_enum)::get would have missed the int setting, but the agent does not use one.
axis_capped_driver declares max_idle with a field macro, and the test sets it with uvm_config_db#(int unsigned)::set. Does auto-config apply it? Would uvm_config_db#(int)::get in the driver see it?Auto-config applies it: integral field macros accept int, int unsigned, uvm_integral_t and uvm_bitstream_t settings. An explicit uvm_config_db#(int)::get would miss it, because config_db requires the identical type.
+uvm_set_config_int=uvm_test_top.env.in_agt,is_active,0. What happens to in_agt, and which setting would it beat?in_agt becomes passive: the plusarg is a bitstream set from the top during build (precedence 1000), and uvm_agent accepts bitstreams for is_active. It would beat any is_active set made by the test (999) or the env (998) during build. With no driver, the input stream gets no stimulus and the test's sequences cannot start on in_agt.sqr.
Next: components read config by path. Sequences have no place in the hierarchy, yet they need knobs too. Configuring sequences shows how they borrow a path.