Here is a test that passes: 8 words matched, 0 mismatched, UVM_ERROR : 0. Its topology shows something it should not: the output agent, configured as passive, has a driver and a sequencer sitting on a port the DUT drives. The only change from the smoke test is one missing line in the agent's build_phase: super.build_phase(phase).
It is tempting to think of super.build_phase(phase) as politeness, since most library phase methods are empty. build_phase is the exception. Think of a new employee's first-day checklist that HR runs before the manager's own onboarding: badge, laptop, access rights. Skip the HR part and the manager's onboarding still runs, and the new hire still shows up, but nothing they need is switched on.
Two things happen in the base calls of an agent's build:
uvm_component::build_phase calls the old build() hook, and build() calls apply_config_settings(). That is automatic configuration: every field registered with the field macros (uvm_field_int and friends) is filled from matching config_db settings. In IEEE 1800.2 it happens only when use_automatic_config() returns 1, which is the default.
uvm_agent::build_phase calls that, then looks up is_active for this agent in the configuration database. This lookup works whether or not your agent uses field macros.
Skip super.build_phase in your agent and neither happens. Field-macro fields keep their default values, and is_active keeps its default, UVM_ACTIVE.
// snippet: from the course DMA bench
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_sequencer sqr;
apb_driver drv;
apb_monitor mon;
uvm_analysis_port #(apb_item) ap; // the agent's public output
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
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
endfunction
function void connect_phase(uvm_phase phase);
ap = mon.ap;
if (get_is_active() == UVM_ACTIVE)
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclassThe comment on super.build_phase(phase) says it plainly: that line reads is_active from config_db. Everything after it relies on that: get_is_active() returns the configured value only because the base build ran first. The env's uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE) is read in exactly this call for the out_agt instance.
Automatic configuration is a larger topic with its own trade-offs (field macros, the cost of matching every field against the database, and why many teams switch it off). This lesson only needs the fact that it lives in build(). The full story is in automatic configuration in the Factory & Configuration section.
is_active itself is an ordinary public field of uvm_agent, defaulting to UVM_ACTIVE, and get_is_active() simply returns it. The value is only as good as the lookup that filled it. The library treats the setting as if it were declared with a field macro, so a config_db set of type uvm_active_passive_enum, int or string is accepted; the bench uses the enum, which is the clearest.
build_phase is not the only base method with content. In the Accellera library:
Class | Method | What the base does |
|---|---|---|
|
| Calls |
|
| Calls super, then looks up |
|
| Sets up and connects the sequencer's response FIFO behind |
|
| Warns |
|
| Call the old no-argument hooks ( |
|
| Nothing. |
So skipping super.connect_phase in a custom sequencer breaks responses, and skipping super.end_of_elaboration_phase in a driver loses a useful warning. The bench's agents and env skip super.connect_phase; that is harmless today because uvm_agent and uvm_env add nothing there, but it is the kind of shortcut that breaks when a team-wide base class is inserted between them and the library.
A copy of the smoke test overrides axis_agent with a version whose build_phase omits the super call and is otherwise identical. The test passes:
out_agt dma_skipsuper_agent - @272
drv axis_driver - @809
...
mon axis_monitor - @669
...
sqr uvm_sequencer - @686
...
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
UVM_ERROR : 0
UVM_FATAL : 0out_agt now has a driver and a sequencer: is_active was never read, so it stayed UVM_ACTIVE.
The extra driver drives tvalid <= 0 once on the output interface, then waits forever for items that never come. On this run that was harmless; on another simulator, or with a DUT that samples its own outputs, it fights the DUT for the bus.
No error, no warning. The only evidence is the topology, which is why reading it is a habit, not a formality.
At SoC level, a team reused block-level agents in passive mode to watch internal buses. A cleanup pass created a shared base_agent class whose build_phase started with the agent's own creates and "didn't need super, since uvm_agent's build is empty". Every passive agent in the chip bench quietly became active. On most buses the new drivers sat idle, but on one shared bus an idle driver held its valid low against the real master, and a week of failures was blamed on the interconnect RTL.
The fix was one line, super.build_phase(phase), in base_agent. The team also added an end_of_elaboration check that every agent configured passive reports get_is_active() == UVM_PASSIVE, so the next refactor would fail at time 0 instead of in the lab.
num_retries is registered with uvm_field_int and set through config_db by the env. The agent's build_phase skips super. What value does num_retries have?Its default. apply_config_settings runs inside build(), called from uvm_component::build_phase, so without the super chain no field-macro field is filled from config_db.
super.build_phase?Safe today only if no class between it and uvm_component adds work in build_phase, and it is not an agent. It costs nothing to call super, and it protects against base classes added later. If the goal is to avoid the cost of automatic configuration, override use_automatic_config() to return 0 instead (IEEE 1800.2).
Read the topology (a passive agent has only a monitor), or add an end_of_elaboration check that compares each agent's get_is_active() with its intended mode and reports an error. Both run before any traffic.
Next: the last phase question is the oldest one: how does it all end? run_test, Timeouts and How a Simulation Ends covers every exit, including the one that skips report_phase.