A new agent is cloned from the APB agent for a sideband bus. In the copy, the if (get_is_active() == UVM_ACTIVE) around the driver connection is dropped, "because this agent is always active". Six months later the same agent is instantiated passive at subsystem level, and every test dies at time 0 inside connect_phase with a null-object access. Connect is the phase where build's decisions come due.
Think of a wedding. During the RSVP period, guests and hosts write names on a list: who sits with whom. Nobody checks the tables yet. Once the list closes, the planner goes through it in one pass, checks every table against its size, and only then sets the room.
connect_phase is the RSVP period. Each connect() call records a link between a port (the side that calls), an export (a pass-through on the way to an implementation) and an imp (the side that implements the method, such as a scoreboard's write). At the start of end_of_elaboration, the library resolves every recorded link in one pass and checks each port's connection count against its minimum and maximum. Errors are reported then, not at the connect() call.
Recording first and checking later is what makes connect order irrelevant for the links themselves: an env can connect its agents in any order, and a port can be connected before or after the export it reaches is itself connected further down. The only order that matters is the one for handle copies, which is why connect walks bottom-up.
Each agent wires its own driver to its own sequencer and publishes one analysis port; you saw that in axis_agent in Traversal Order Rules. The env then wires only between agents and the scoreboard, never reaching inside an agent:
// snippet: from the course DMA bench
class dma_env extends uvm_env;
`uvm_component_utils(dma_env)
apb_agent apb;
axis_agent in_agt, out_agt;
dma_scoreboard sb;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// The output side is observed only: the DUT drives it.
uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE);
apb = apb_agent::type_id::create("apb", this);
in_agt = axis_agent::type_id::create("in_agt", this);
out_agt = axis_agent::type_id::create("out_agt", this);
sb = dma_scoreboard::type_id::create("sb", this);
endfunction
function void connect_phase(uvm_phase phase);
apb.ap.connect(sb.apb_export);
in_agt.ap.connect(sb.in_export);
out_agt.ap.connect(sb.out_export);
endfunction
endclassNote ap = mon.ap in the agents is a handle copy: the agent's ap and the monitor's ap are the same object, so nothing new is connected. The other common style creates a separate analysis port in the agent and connects the monitor's port to it, mon.ap.connect(ap). That is a port-to-port connection, and those always go from the child's port to the parent's port. Both styles let the env say apb.ap without knowing what is inside.
Connection | Direction | Example |
|---|---|---|
Port to export or imp | The caller connects to the implementer |
|
Port to port (going up) | Child port to parent port |
|
Export to export (going down) | Parent export to child export or imp | An env export forwarded to a scoreboard imp |
Analysis port fan-out | One port to any number of imps | One monitor feeding a scoreboard and a coverage collector |
Each port has a minimum and maximum number of connections, checked at resolution. An analysis port has minimum 0 and no maximum: zero subscribers is legal, and a second subscriber is added, never a replacement. The driver's seq_item_port allows at most one connection, and minimum zero. That is why an unconnected driver is not an error: uvm_driver warns about it itself (DRVCONNECT) in its end_of_elaboration_phase.
The topology print lists ports and imps as children of their components. The scoreboard's three inputs are the imps created by its three uvm_analysis_imp_decl macros:
sb dma_scoreboard - @280
apb_export uvm_analysis_imp_apb - @288
in_export uvm_analysis_imp_in - @298
out_export uvm_analysis_imp_out - @307If a port ends up with more connections than its maximum, resolution reports connection count of 2 exceeds maximum of 1 as a UVM_ERROR from that port's component. Because it happens as end_of_elaboration starts, the error is then caught by the build-error check covered in the next lesson, and the test stops at time 0. Connecting the same pair twice is recorded once and is silent.
The topology print shows which ports exist, not what they are connected to. For that, every port, export and imp has debug_connected_to() (what this port reaches, following exports down to imps) and debug_provided_to() (what reaches this export or imp). Called in end_of_elaboration, after resolution, they answer "is the coverage collector really subscribed?" without a waveform. get_connected_to() and size() give the same information to code, for example an audit that every agent's ap has at least one subscriber.
Back to the opening. The passive instance's build skipped the driver and sequencer creates, exactly as designed, so drv and sqr were null. Its connect then ran drv.seq_item_port.connect(sqr.seq_item_export) unconditionally. The fix was to restore the guard, written with the same condition as the create: if (get_is_active() == UVM_ACTIVE). A good review rule: every if around a create in build has a twin around the uses in connect.
A second classic is an env that "helpfully" connects an agent's driver to a different sequencer, for example a virtual sequencer, while the agent also connects it internally. The port now has two connections and resolution reports the error above. Virtual sequencers hold handles to agent sequencers, assigned in connect; they are not connected to drivers.
No. An analysis port has no maximum: both subscribers are kept and each write() calls both. Do not depend on which one is called first.
uvm_analysis_port named ap and connects the monitor to it. Which way round is the call written?mon.ap.connect(ap): port to port goes from the child's port to the parent's port. The env then connects agent.ap to the scoreboard's imp.
apb.ap but not apb.mon.ap in good style, even though both work?Both point to the same object, but apb.mon.ap makes the env depend on the agent's inside: rename the monitor or restructure the agent and the env breaks. The agent's ap is its public interface; bottom-up order guarantees it is set before the env's connect runs.
Next: every link is now recorded. Elaboration, Start and the Pre-Time-Zero Contract shows what the library does with them, and the audit you can add.