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
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
    • TLM 1: Ports, Exports and Analysis
      • Who Calls, Who Implements: Ports, Exports and Imps
      • connect() Rules: Which Way Each Link Points
      • put, get, peek and transport: Flow-Controlled TLM
      • Analysis Broadcast: One write(), Many Listeners
      • TLM1 Channels and Port Cardinality
      • Analysis Connectivity Patterns: imp_decl vs FIFOs
      • Connection Triage and FIFO Sizing: A Debug Playbook
      • The TLM Lessons' Bench File: Full Source
    • TLM 2.0 Sockets
  • 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

‹TLM 1: Ports, Exports and Analysis
  1. Topics
  2. UVM Verification
  3. Transaction-Level Modeling (TLM)
  4. TLM 1: Ports, Exports and Analysis
  5. connect() Rules: Which Way Each Link Points

Part 4 · TLM & Analysis · Intermediate

connect() Rules: Which Way Each Link Points

Port-to-port goes child to parent, export-to-export goes parent to child, a port ends at an export or imp: the rules on the DMA bench's real wiring, the agent's ap = mon.ap idiom, and what connect() refuses.

On this page

    You are moving the DMA scoreboard into a small checker component so the subsystem env can reuse it. You add an export to the checker, write the env's connect line the wrong way round, and the run dies at time zero before a single clock. Reproduced in a minimal test (tlm_export_port_test in tlm_tb.sv), the log reads:

    Diagram
    UVM_ERROR @ 0: uvm_test_top.exp [Connection Error] Cannot connect exports to ports Try calling port.connect(export) instead. (You attempted to bind this export to uvm_test_top.prt).
    UVM_ERROR @ 0: uvm_test_top.exp [Connection Error] connection count of 0 does not meet required minimum of 1
    UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errors

    The message even tells you the fix, but only for that one link. A chain that crosses three levels of hierarchy has a direction at every hop. Which way does each connect() point, who calls it, and which mistakes does the library catch?

    The idea: connect() always points from the caller towards the implementer

    A call travels from a port to an imp. Every connect() is called on the end nearer the caller and takes the end nearer the implementer as its argument: requester.connect(provider). Inside a hierarchy that gives three moves. A port goes up: a child's port connects to its parent's port, so the parent can offer the child's call to the outside. A port goes across: it connects to a sibling's export or imp. An export goes down: a parent's export connects to a child's export or imp. The imp at the bottom runs the method and connects to nothing.

    Picture internal mail in an office campus. A letter goes from your desk up to your building's outbox (port to port), across the road to the other building's mail room (port to export), and down to the recipient's desk (export to imp). Nobody walks a letter from the mail room back up into someone's outbox.

    Note

    The rule, stated once for the whole course: port-to-port connections go child port to parent port, child.port.connect(parent.port). Export-to-export goes parent export to child export. A port connects to an export or imp.

    The picture: the bench's links, with pass-through

    Topology: inside the input agent the monitor's port connects up to the agent's port, and the driver's port connects across to the sequencer's imp. Under the env the agent's port connects across to a checker's export, which connects down to the scoreboard's imp.
    Each arrow is one connect() call, made by the component that owns both ends' common parent, and points from the caller towards the implementer. The bench itself uses ap = mon.ap and connects the agent's port straight to the scoreboard's imp; the checker shows where an export goes when the scoreboard moves down a level.

    Read who calls each connect(). The agent connects its children (mon.ap to ap, driver to sequencer). The env connects its children (in_agt.ap to chk.in_export). The checker connects its own export to its child. Each component wires only what it owns, inside its own connect_phase, and because connect_phase runs bottom-up, every child is wired before its parent looks at it.

    Build it: the bench's wiring and the alternatives

    The agent forwards its monitor's port by handle

    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 apb_agent extends uvm_agent;
      `uvm_component_utils(apb_agent)
      apb_sequencer sqr;
      apb_driver    drv;
      apb_monitor   mon;
      uvm_analysis_port #(apb_item) ap;   // the agent's public output
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);         // reads is_active from config_db
        mon = apb_monitor::type_id::create("mon", this);
        if (get_is_active() == UVM_ACTIVE) begin
          sqr = apb_sequencer::type_id::create("sqr", this);
          drv = apb_driver::type_id::create("drv", this);
        end
      endfunction
    
      function void connect_phase(uvm_phase phase);
        ap = mon.ap;
        if (get_is_active() == UVM_ACTIVE)
          drv.seq_item_port.connect(sqr.seq_item_export);
      endfunction
    endclass

    The line ap = mon.ap does not connect anything. It makes the agent's ap variable a second name for the monitor's port object, so apb.ap and apb.mon.ap are the same object. That is why no ap row appears under the agent in the topology table, and why debug output names the port uvm_test_top.env.in_agt.mon.ap. It is cheap and common. Its one requirement is timing: the assignment must run before the env uses apb.ap, which the bottom-up connect_phase guarantees.

    The env connects each agent's port across to a scoreboard imp

    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

    Each line is a port connecting across to a sibling's imp: apb and sb are both children of the env. A port may also be connected to more than one imp, and an imp may be reached by more than one port; analysis broadcast covers that fan-out and fan-in.

    The pass-through alternative: a port of the agent's own

    Instead of borrowing the monitor's object, the agent can own a real port and pass the monitor's port up into it. This is the textbook child-port-to-parent-port move:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: inside axis_agent, a pass-through port instead of ap = mon.ap
    uvm_analysis_port #(axis_item) ap;
    
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      ap  = new("ap", this);                      // the agent's own port object
      mon = axis_monitor::type_id::create("mon", this);
      // sqr and drv as before
    endfunction
    
    function void connect_phase(uvm_phase phase);
      mon.ap.connect(ap);                         // child port up to parent port
      if (get_is_active() == UVM_ACTIVE)
        drv.seq_item_port.connect(sqr.seq_item_export);
    endfunction

    ap = mon.ap (the bench)

    Pass-through port

    Port objects

    One: the monitor's

    Two: the monitor's and the agent's

    In the topology

    Only mon.ap

    mon.ap and ap

    Can the agent add its own traffic to ap?

    Only by writing to the monitor's port

    Yes, it can call ap.write() too

    Typical failure

    Using apb.ap before the agent's connect_phase: a null handle

    Forgetting mon.ap.connect(ap): silent, see below

    Exports go down: moving the scoreboard into a checker

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // snippet
    class dma_checker extends uvm_component;
      `uvm_component_utils(dma_checker)
      uvm_analysis_export #(axis_item) in_export;   // the checker's public input
      dma_scoreboard sb;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
        in_export = new("in_export", this);
      endfunction
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        sb = dma_scoreboard::type_id::create("sb", this);
      endfunction
      function void connect_phase(uvm_phase phase);
        in_export.connect(sb.in_export);            // parent export down to child imp
      endfunction
    endclass
    // env connect_phase: in_agt.ap.connect(chk.in_export);   // port across to sibling export

    What connect() checks, and when

    Mistake

    When it is caught

    What you see

    Different transaction types (apb_item port, axis_item imp)

    Compile time

    A type error on the connect() line

    Different families with the same type (a put port into a get imp)

    At the connect() call

    Connection Error: does not provide the complete interface

    export.connect(port)

    At the connect() call

    Connection Error: Cannot connect exports to ports

    imp.connect(anything)

    At the connect() call

    Connection Error: Cannot call an imp port's connect method

    Provider offers only part of the interface (a full uvm_put_port into a uvm_blocking_put_imp)

    At the connect() call

    Connection Error: does not provide the complete interface

    Connecting at or after end_of_elaboration

    At the call

    Warning Late Connection ... Ignoring

    Wrong hierarchy (a port into its own parent's export)

    At the call, but only if enabled

    Connection Warning; silent by default

    Too few or too many connections

    Start of end_of_elaboration

    Connection Error from resolve_bindings

    Two rows surprise people. The hierarchy checks (up, across, down) exist in the library, but a port silences its own Connection Warning unless check_connection_relationships is set for it in config_db, and analysis ports skip the check entirely. So a wrong-shaped connection usually works without a word, which is one more reason to follow the rules by habit. Connection counts are checked last, by resolve_bindings, in TLM1 channels and port cardinality. Any UVM_ERROR raised before end of elaboration finishes makes the root stop with BUILDERR, as in the opening log.

    Watch it run: the hard error

    The opening log came from a copy of the bench with one deliberate line in a test's connect_phase: exp.connect(prt), an export connected into a port. The library refuses the link on the spot, records nothing, and the root ends the run at time 0 because a build error occurred. The fix is the reverse call, prt.connect(exp), so that the port, the caller's end, is the one that connects.

    What breaks: the agent port nobody fed

    A team switched their stream agent to the pass-through style above, so they could add the agent's own sideband traffic to ap. The new build_phase created ap. The connect_phase kept only the driver line; mon.ap.connect(ap) was lost in the edit. The env still connected in_agt.ap and out_agt.ap to the scoreboard, and both connections were perfectly legal.

    At run time each monitor wrote into its own mon.ap, which now had no subscribers, and the agent's ap had subscribers but no one writing into it. No elaboration error: an analysis port with zero subscribers is legal, and so is one with nobody calling it. The APB stream still worked, so the scoreboard saw KEY writes and nothing else. What saved them was the bench's own check: check_phase reported SB_EMPTY, no words were compared. Without that check the test would have passed. A one-line env.in_agt.mon.ap.debug_connected_to() at end of elaboration, shown in imp_decl vs FIFOs, prints Connected implementations: none and finds it in a minute.

    Interview

    "How does a monitor's analysis port inside an agent reach a scoreboard in the env?"

    A weak answer: "you connect the monitor to the scoreboard in the env." It skips the agent boundary and cannot say which way any connect points.

    A strong answer: "The agent exposes the monitor's port, either as a handle alias, ap = mon.ap, or as its own port with mon.ap.connect(ap), child port up to parent port. The env then connects agt.ap across to the scoreboard's imp, or to an export on a checker that forwards down to the imp. Each component connects only its own children in connect_phase, which runs bottom-up. Exports never connect into ports, and imps connect to nothing."

    Expect the follow-ups: Why might a wrongly shaped connection give no warning? (relationship warnings are off unless check_connection_relationships is set, and analysis ports skip them) and Where would you call `debug_connected_to()`? (end of elaboration or later).

    Check yourself Someone writes chk.in_export.connect(in_agt.ap) in the env. What happens, and what is the fix?

    connect() reports Cannot connect exports to ports and ignores the link, and the root stops with BUILDERR at end of elaboration. The call must start from the caller's end: in_agt.ap.connect(chk.in_export).

    Check yourself The agent uses ap = mon.ap. A new env author connects in_agt.ap to the scoreboard in the env's build_phase. What goes wrong?

    In the env's build_phase the agent object has just been created, but its own build_phase (which runs after the env's, top-down) has not built the monitor, and its connect_phase has not assigned ap. So in_agt.ap is a null handle and the call fails with a null-object error. Connections belong in connect_phase, where children are fully built and wired first.

    Check yourself You move the scoreboard into dma_checker with three exports. Which components make which connect() calls?

    The checker connects its own exports down to its scoreboard's imps (apb_export.connect(sb.apb_export) and so on). The env connects each agent's port across to the checker's matching export. Neither reaches into the other's children.

    Next: the analysis links never wait for anyone. In put, get, peek and transport you will meet the families where the receiver controls the pace, and see a put() that takes 20 ns to return.

    Related topics

    • Who calls, who implements

    • TLM1 channels and port cardinality

    • Connect phase: bottom-up wiring

    • Agent architecture

    Previous

    61 / 280

    Next: put, get, peek and transport: Flow-Controlled TLM

    Up next

    Up nextput, get, peek and transport: Flow-Controlled TLM

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Analysis Broadcast: One write(), Many Listeners
    • TLM1 Channels and Port Cardinality
    • Analysis Connectivity Patterns: imp_decl vs FIFOs