Part 2 · Phases & Lifecycle · Intermediate
connect vs build Boundary: What Goes Where
Strict separation of construction and wiring — phase contract violations, common anti-patterns, and refactor guidance.
The phase split
build_phase answers what exists ; connect_phase answers how it is linked . Blurring the boundary destroys ordering guarantees and makes debug exponentially harder.
[PHASE][UVM] build vs connect responsibilities
┌─────────────────┬──────────────────────┬──────────────────────┐
│ Responsibility │ build_phase │ connect_phase │
├─────────────────┼──────────────────────┼──────────────────────┤
│ Create children │ YES │ NO │
│ config_db get │ YES (primary) │ rare re-get │
│ factory create │ YES │ NO │
│ port.connect │ NO │ YES │
│ v_sqr map │ NO │ YES (handles) │
│ consume time │ NO │ NO │
│ drive pins │ NO │ NO │
└─────────────────┴──────────────────────┴──────────────────────┘// ANTI-PATTERN — connect in build_phase
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = my_monitor::type_id::create("mon", this);
sb = my_scoreboard::type_id::create("sb", this);
mon.ap.connect(sb.act_imp); // BAD — sb may not be ready; wrong phase
endfunction
// CORRECT — split phases
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = my_monitor::type_id::create("mon", this);
sb = my_scoreboard::type_id::create("sb", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.ap.connect(sb.act_imp);
endfunctionKey takeaways
Never connect in build_phase — sibling endpoints may not exist.
Never create in connect_phase — topology must be frozen at build end.
config_db gets belong in build; connect uses handles already stored.
Common pitfalls
Lazy create in run_phase when connect 'forgot' a component.
Reading vif and driving reset in connect_phase.
Using connect to apply factory overrides.
Refactoring violations
When reviewing legacy code, these moves restore phase discipline without changing behavior.
Move checklist
Move all type_id::create() calls from connect to build.
Move all port.connect() calls from build to connect.
Move virtual sequencer handle assigns from build to connect.
Ensure conditional create and conditional connect use same guard.
Add null-guard fatals before every connect on gated components.
[PHASE] anti-pattern → fix map
create in connect_phase → move to build_phase
connect in build_phase → move to connect_phase
get vif in connect only → get in build, store in member
start seq in connect → move to run_phase
#delay in either phase → move to run_phaseReview gate
// CI lint-style review questions per component:
// Q1: Does build_phase call any .connect()?
// Q2: Does connect_phase call any type_id::create()?
// Q3: Does either phase have # or wait?
// Q4: Does connect guard match build guard for active/passive?Env wrappers should not hide phase violations inside macros.
VIP agents must keep build/connect split for reuse.
Document exceptions explicitly — there should be almost none.
[PHASE][UVM] phase contract summary
build = structure + config
connect = wiring only
run = time + stimulus + objectionsCommon pitfalls
Macro wrappers that hide connect inside `uvm_create macros.
Generated code mixing phases — regenerate with correct templates.
Copy-paste from non-UVM SV testbenches with manual bind order.