You add a log line to a component's build, rerun, and it is not there. The component is in the topology, the test compiles cleanly, other components' messages appear. After an hour you notice the method is called buid_phase. Nobody told you, because from SystemVerilog's point of view nothing is wrong: you declared a new method, and nobody calls it.
The phaser does not know your class. It holds a uvm_component handle and calls, for example, comp.build_phase(phase). Because the base class declares build_phase as virtual, the call reaches your override, but only if your method has exactly the same name, kind (function or task) and argument list. It is like a dispatcher calling out a call sign over the radio: misspell your call sign and you never hear your call, while the base class quietly answers in your place.
Phase | Exact declaration |
|---|---|
build |
|
connect |
|
end of elaboration |
|
start of simulation |
|
run |
|
extract, check, report, final |
|
the twelve runtime phases |
|
Three ways to get it wrong, and what each one does:
// bad-example
function void buld_phase(uvm_phase phase); // compiles, never called: a brand-new method
`uvm_info("BUILD", "custom build", UVM_LOW)
endfunction
function void run_phase(uvm_phase phase); // compile error: the base run_phase is a task
`uvm_info("RUN", "cannot override a task with a function", UVM_LOW)
endfunction
function void build(uvm_phase phase); // compile error: clashes with the old build() hook
`uvm_info("BUILD", "wrong argument list for build()", UVM_LOW)
endfunctionOnly the first is silent. A function overriding the virtual task run_phase is illegal SystemVerilog, so it fails at compile time. The third clashes with an older method you may not know exists.
Before UVM had _phase methods, OVM components overrode build(), connect(), run() and so on, with no argument. The library still declares them, and the base phase methods call them: uvm_component::build_phase calls build(), connect_phase calls connect(), run_phase calls run(), and likewise for end_of_elaboration, start_of_simulation, extract, check and report. That is true in UVM 1.2 and in the Accellera library for IEEE 1800.2.
Two consequences. A method declared as function void connect(); in your class does run, through the base connect_phase, even though it looks like a typo. And build() is not just an old name: it is where the base class applies automatic configuration, which is the subject of What super.build_phase() Really Does. Never write new code against the old hooks; recognise them when you inherit them.
Every phase method in your class should start with super.<phase>_phase(phase). The reason is not the empty base bodies of today; it is that your parent class may not be the library class tomorrow. In a three-level hierarchy, each super call runs one level up, so a single missing call in the middle silently drops everything above it for every class below it.
// snippet
class base_agent extends uvm_agent; // team-wide base: shared setup
`uvm_component_utils(base_agent)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase); // uvm_agent: config, is_active
// shared monitor, coverage, checkers
endfunction
endclass
class apb_agent_v2 extends base_agent;
`uvm_component_utils(apb_agent_v2)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase); // runs base_agent, which runs uvm_agent
// APB-specific children
endfunction
endclassThere is one deliberate exception to "super first": a test that registers factory overrides for components its parent creates. Those lines must come before super.build_phase. Why build_phase Is Top-Down explains exactly when "before" matters.
When a phase method seems to do nothing, separate "not called" from "called but wrong" before reading any logic. Put a log line as the method's first statement, then rerun:
// snippet
function void build_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%s entered %s", get_full_name(), phase.get_name()), UVM_NONE)
super.build_phase(phase);
// ...
endfunctionUVM_INFO dma_tb.sv(...) @ 0: uvm_test_top.env.in_agt [TRACE] uvm_test_top.env.in_agt entered buildLine present: the method runs; the bug is in what it does.
Line absent, component in the topology: a signature problem. Compare the name letter by letter with the table above.
Line absent, component missing from the topology: the component was never created, or was created as a different type. Print the factory (uvm_factory::get().print()) and look for overrides.
+UVM_PHASE_TRACE on the command line makes the phaser log every phase transition, which helps when the question is "did this phase run at all?" rather than "did my method run?".
The same reasoning applies to the three phase callbacks. phase_started, phase_ready_to_end and phase_ended are virtual functions with one uvm_phase argument, and a misspelled phase_ready_to_ends is just as silent as a misspelled build. Because those callbacks run for every phase, a log line in them is noisy; filter on the phase name before printing.
A team renamed every report() method in their VIP to report_phase(uvm_phase phase) while removing OVM leftovers, and did the same for check(). One scoreboard's check_phase stopped flagging leftovers. Its author had renamed the method to check_phase but typed function void check_phase(); with no argument. That would not have compiled, so someone "fixed" the compile error by renaming it to chk_phase(uvm_phase phase). The method was never called again, and for three weeks the scoreboard could not fail a test.
Their fix had two parts: restore the exact signature, and add a self-test that runs one deliberately broken transaction and expects exactly one error. A checker that has never been seen failing has not been tested.
task run_phase(uvm_phase phase); and also task run();. Which run?Both, if the component's run_phase calls super.run_phase(phase): the base run_phase calls run(). If run_phase does not call super, only run_phase runs. This is a good reason to delete old hooks when you find them.
super.build_phase. Which classes are affected?Every class that extends it, directly or indirectly. Each super call runs one level up, so the chain stops at the missing call: uvm_agent's is_active lookup and the library's automatic configuration never run for any agent built on that base.
function void run_phase(uvm_phase phase); in a component a silent bug or a compile error?A compile error. run_phase is a virtual task in uvm_component, and a function cannot override a task. The silent case is a misspelled name, which simply declares a new method.
Next: so far the phaser has been a black box that calls your methods. Inside the Phaser opens it, which is what you need to debug the callbacks that run around your phase methods.