Okay, so this question comes up in almost every UVM interview, and here's the thing nobody says out loud: most people have written a build_phase and a connect_phase a hundred times without ever really knowing why there are two of them. They copied a testbench, renamed a few things, it ran, done. And that's exactly what the interviewer is fishing for when they ask this. In about five seconds they can tell whether you built your environment or just inherited it.
So let me actually walk you through it the way I wish someone had walked me through it.
The whole idea in one line: build_phase hires the team and gives everyone a desk. connect_phase plugs in the phone lines between them.
That's it. You can't wire up communication between people who aren't hired and sitting down yet, right? So UVM does it in two passes. First it builds the entire team every component then, once everyone exists, it connects them. Two jobs. Two phases. In that order. On purpose.
build_phase this is where everything gets created
Think of build_phase as the moment your testbench comes to life. Two things happen here: you create your components, and you hand them their config.
And you always create them through the factory new():
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agt = my_agent::type_id::create("agt", this);
sb = my_scoreboard::type_id::create("sb", this);
endfunctionHere's why that matters, and honestly it's the follow-up question that trips people up more than the main one: type_id::create is what lets a test swap my_agent out for my_error_agent without ever touching this file. That's the whole point of the factory. The second you write new() instead, all those overrides quietly stop working and nothing warns you. Your test just doesn't do what you told it to.
The other thing that happens in build is grabbing config. Anything a component needs while it's building itself the virtual interface, an active/passive flag, how many agents you pull it from the config DB right here:
if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("CFG", "no virtual interface set")And one more thing to tuck away: build_phase runs top-down. Parent first, then its kids. That's not random it means a parent can create its children and drop their settings into the config DB before those children build, so the config is already waiting for them when they wake up. Top-down build is the reason "configure from the top" even works.
connect_phase now you wire it all together
By the time connect_phase runs, the whole tree already exists. Everyone's hired and seated. So now you plug in the phone lines:
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
agt.mon.ap.connect(sb.analysis_export); // monitor -> scoreboard
drv.seq_item_port.connect(seqr.seq_item_export); // driver -> sequencer
endfunctionQuick thing that'll save you an afternoon of debugging: a port connects to an export, and the direction matters. The driver's seq_item_port goes to the sequencer's seq_item_export. The monitor's analysis port goes to the scoreboard's export. Get that backwards and elaboration yells at you. The way I remember it: the port is the one asking, the export is the one providing.
And connect_phase runs bottom-up the little leaf components wire up first, then their parents. Makes sense when you think about it: everything already exists, so the connections just resolve from the inside out.
So where do these two actually sit?
Both of them are functions they run in zero time. Nothing's simulating yet. They're part of the setup that happens before the clock ever ticks:
build_phase (top-down, 0 time)
connect_phase (bottom-up, 0 time)
end_of_elaboration
start_of_simulation
run_phase <-- a task. the only one that burns time.
extract / check / report / finalThis is actually the cleanest way to frame your whole answer. All the build-and-wire stuff is functions that set up the static structure instantly. Then run_phase is a task, and that's where stimulus actually flows and time moves. If they hit you with "why is run_phase a task but build_phase a function?" β there's your answer. You build structure in an instant; you run behavior over time.
The stuff that quietly gives you away
These are the mistakes that scream "I copied this":
- Trying to connect inside
build_phase. The components don't all exist yet, so you get a null handle crash. Connections live inconnect_phasebecause that's the first moment the whole tree is guaranteed to be there. - Forgetting
super.build_phase(phase). The base call does the automatic field config for you. Skip it and your auto-config just⦠doesn't happen. Brutal bug to track down. - Using
new()instead of the factory. Compiles fine, runs fine, silently kills every override in every test. - Doing your config
get()in connect. Too late the component already built itself without it. Build-time config goes inbuild_phase.
What to actually say in the interview
Say this in one breath and you're done:
"build_phase creates the components through the factory and pulls their config from the config DB it runs top-down so a parent can configure its children before they build. connect_phase then wires the TLM and analysis ports, bottom-up, once everything already exists. Both are functions that run during elaboration, before run_phase, which is the only phase that actually consumes time."
And if you want to really land it, add the why: "They're separate because you can't connect components that don't exist yet so UVM builds the whole tree first, then connects it."
One picture to add
Draw the same component tree twice. First pass build, arrows pointing down the hierarchy (env makes the agent and scoreboard, agent makes the driver, monitor, sequencer). Second pass connect, arrows pointing sideways (driver β sequencer, monitor β scoreboard), with a little note: "everything already exists." Show those two passes side by side and the whole thing just clicks.
Bottom line
These two phases aren't UVM being fussy. They're UVM enforcing one simple rule that never breaks: build the structure first, wire it second. Create through the factory, configure top-down, connect bottom-up, and remember both are zero-time functions that finish before run_phase ever starts. Get that, and you're not reciting UVM anymore you're reasoning about it. Which is the whole thing the question is checking for.
If UVM interview prep is where you're at right now, this is part of a series I'm putting on SiliconDV the phases, the factory, sequences, the config DB each one explained the way I actually wish someone had explained it to me.