Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
    • The Phase System
    • UVM Phases: Build, Connect, Elaborate and Run
      • Why build_phase Is Top-Down
      • config_db Gets and Conditional Construction
      • Connect Phase: Bottom-Up TLM Wiring
      • Elaboration, Start and the Pre-Time-Zero Contract
      • The Run Phase: Parallel Execution and Duration
      • Runtime Sub-Phases: Reset, Configure, Main and Shutdown
      • What super.build_phase() Really Does
      • run_test, Timeouts and How a Simulation Ends
    • Objections Deep Dive: Drain Time and Propagation
    • Phase Debugging and End-of-Test Signoff
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹UVM Phases: Build, Connect, Elaborate and Run
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. UVM Phases: Build, Connect, Elaborate and Run
  5. Connect Phase: Bottom-Up TLM Wiring

Part 2 · Phases & Lifecycle · Intermediate

Connect Phase: Bottom-Up TLM Wiring

Port, export and imp rules, handle forwarding through the agent, and when a binding error is reported.

On this page

    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.

    The idea: the RSVP list and the seating check

    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.

    The picture: the DMA bench's links

    Diagram: inside the APB agent the driver port connects to the sequencer export and the monitor port is forwarded as the agent's ap; the env connects three agent ports to three scoreboard imps.
    Links in the DMA bench. Inside the agent, the driver's pull port connects to the sequencer's export; the agent forwards its monitor's analysis port as its own ap; the env connects each agent's ap to one of the scoreboard's three imps.

    Build it: the agent owns its inside, the env owns the links between

    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:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 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
    endclass

    Note 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

    drv.seq_item_port.connect(sqr.seq_item_export)

    Port to port (going up)

    Child port to parent port

    mon.ap.connect(ap) inside an agent

    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.

    Watch it run

    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:

    Diagram
    sb                     dma_scoreboard          -     @280
            apb_export           uvm_analysis_imp_apb    -     @288
            in_export            uvm_analysis_imp_in     -     @298
            out_export           uvm_analysis_imp_out    -     @307

    If 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.

    Seeing the links

    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.

    What breaks: the guard that was deleted

    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.

    Common pitfall

    Connecting to a handle that build left null fails at the connect() call itself, in connect_phase, with a null-object access: it is not a binding error. Binding errors (counts) are reported later, when end_of_elaboration starts.

    Interview

    "What does connect() actually do, and when would you see an error from it?"

    A weak answer: "It links the two ports so transactions can flow; a bad connection errors immediately."

    A strong answer: "connect() records the link. At the start of end_of_elaboration the library resolves all recorded links and checks each port's connection count against its min and max. Too many connections on a seq_item_port gives 'connection count of 2 exceeds maximum of 1', and since any UVM_ERROR before end_of_elaboration stops the test with BUILDERR, it fails at time 0. A null handle is different: that fails at the connect() call. Port-to-port goes child to parent, export-to-export parent to child, and an analysis port can fan out to any number of imps."

    Follow-ups: Is an analysis port with no subscriber an error? (no) and Why does an unconnected driver only warn? (seq_item_port has minimum 0; uvm_driver warns DRVCONNECT).

    Check yourself A monitor's analysis port is connected to the scoreboard and, later, to a coverage collector. Does the second connect replace the first?

    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.

    Check yourself An agent creates its own 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.

    Check yourself Why can the env's connect_phase use 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.

    Related topics

    • Elaboration, Start and the Pre-Time-Zero Contract

    • Ports, exports and imps

    • Agent architecture

    Previous

    27 / 280

    Next: Elaboration, Start and the Pre-Time-Zero Contract

    Up next

    Up nextElaboration, Start and the Pre-Time-Zero Contract

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why build_phase Is Top-Down
    • The Run Phase: Parallel Execution and Duration
    • Runtime Sub-Phases: Reset, Configure, Main and Shutdown