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. TLM1 Channels and Port Cardinality

Part 4 · TLM & Analysis · Intermediate

TLM1 Channels and Port Cardinality

Minimum and maximum connection counts and the resolve_bindings errors they produce, uvm_tlm_fifo versus uvm_tlm_analysis_fifo, the request/response and transport channels, and master and slave ports.

On this page

    A VIP drop adds a few ports to a component, and the next regression run stops every test at time zero:

    Diagram
    UVM_ERROR @ 0: uvm_test_top.demo.dangling_export [Connection Error] connection count of 0 does not meet required minimum of 1
    UVM_ERROR @ 0: uvm_test_top.demo.greedy_port [Connection Error] connection count of 2 exceeds maximum of 1
    UVM_ERROR @ 0: uvm_test_top.demo.lonely_port [Connection Error] connection count of 0 does not meet required minimum of 1
    UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errors

    The component has five ports. Two of them were also left unconnected, an analysis port and the kind of pull port a driver uses, and neither complained. Why do some ports fail the run and others not, and what are the library's ready-made channels for?

    The idea: every connector declares how many partners it needs

    Each port, export and imp is built with a minimum and a maximum connection count. As end_of_elaboration starts, the root calls resolve_bindings() on every one of them. That walks each connector's network down to the imps it finally reaches, counts them, and reports a Connection Error if the count is below the minimum or above the maximum. Think of seats at a table: some reservations are for exactly one guest, some say any number, including none.

    This is the rule, stated once for the course: an unconnected port (non-analysis) is an error at end of elaboration (resolve_bindings, min size 1). An analysis port with no subscribers is legal. A monitor with no listeners is normal; a put port with no one to accept the put can never work, so the library refuses to start.

    Connector

    Min

    Max

    Left unconnected

    uvm_*_port (put, get, peek, transport, master, slave)

    1

    1

    Error

    uvm_*_export of those families

    1

    1

    Error

    uvm_analysis_port

    0

    unbounded

    Legal; writes reach nobody

    uvm_analysis_export

    1

    unbounded

    Error, unlike the port

    Any imp

    1

    1

    Legal: an imp counts itself

    uvm_seq_item_pull_port (a driver's seq_item_port)

    0

    1

    Legal

    TLM 2.0 initiator sockets

    1

    1

    Error (a target socket counts itself, like an imp)

    Two rows deserve a second look. The maximum counts imps reached, not ports connected in: any number of ports may connect to one imp (fan-in), but a put port that reaches two imps exceeds its maximum of 1. And the analysis export has a minimum of 1, so converting a subscriber's imp into an export during a refactor, and forgetting to connect the export down, fails the run even though the same mistake with a port is silent. Ports and exports accept explicit counts in their constructor, new(name, parent, min_size, max_size), with UVM_UNBOUNDED_CONNECTIONS for no maximum; analysis ports and imps fix their own.

    The picture: the library's channels between two components

    A requester with put, master and transport ports and a responder with get and two slave ports, each pair joined through a uvm_tlm_fifo, a request/response channel or a transport channel.
    Every arrow points from a port to an imp on a channel component. The requester only calls put, transport or master methods; the responder only calls get or slave methods. Neither knows the other exists.

    Build it: FIFOs and channels

    uvm_tlm_fifo and uvm_tlm_analysis_fifo

    uvm_tlm_fifo #(T)

    uvm_tlm_analysis_fifo #(T)

    Depth

    Constructor argument, default 1; 0 means unbounded

    Always unbounded; size() returns 0

    Input

    put_export (and blocking/non-blocking variants), all imps

    analysis_export, an imp whose write() does try_put (always succeeds)

    Output

    get_export, peek_export, get_peek_export, all imps

    Same, inherited

    Backpressure

    Yes: put blocks when full

    None: the publisher never waits

    Introspection

    used(), size(), is_empty(), is_full(), flush()

    Same; watch used() grow if the reader stalls

    Taps

    put_ap and get_ap publish every item put and got

    Same

    flush() empties the FIFO, but reports an error and does nothing if a reader is blocked in get while items are queued. The analysis FIFO is the bridge between a monitor's zero-time write() and a consumer that wants to pull at its own pace; imp_decl vs FIFOs builds a scoreboard on three of them.

    Interview

    "Why use uvm_tlm_fifo instead of a SystemVerilog mailbox?" A mailbox is a fine queue, but it is a shared handle: both sides must be given the same object, nothing checks that they were, and nothing can tap it. A uvm_tlm_fifo is a component with exports, so producer and consumer only hold ports, the connection is checked by resolve_bindings at end of elaboration, a test can swap the channel without touching either side, and put_ap/get_ap let a third component watch the traffic. Inside one component, a mailbox (or a queue) is still the simpler choice.

    Master and slave ports

    A master port bundles the requester's half of a request/response protocol: put(REQ) to send a request, get(RSP) and peek(RSP) to collect a response. A slave port bundles the responder's half: get(REQ) and peek(REQ) to take a request, put(RSP) to answer. Each comes in blocking, non-blocking and combined forms (uvm_blocking_master_port, uvm_master_port, and so on), with matching exports and imps.

    uvm_tlm_req_rsp_channel and uvm_tlm_transport_channel

    The request/response channel is two FIFOs, requests one way and responses the other, each with a default depth of 1 (new(name, parent, request_fifo_size, response_fifo_size)). It offers master_export and slave_export for master and slave ports, separate put_request_export, get_peek_response_export, get_peek_request_export and put_response_export if you prefer single-direction ports, and request_ap and response_ap taps. The transport channel extends it with depths fixed at 1 and adds transport_export: its transport(req, rsp) puts the request and then blocks in get for the response, so a caller gets one-call semantics while the responder still runs its own get/put loop.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: from tlm_tb.sv in the course bench
    // responder: one loop serves any slave port
    task serve(uvm_slave_port #(apb_item, apb_item) p);
      forever begin
        apb_item req, rsp;
        p.get(req);                               // take the next request
        #10ns;
        $cast(rsp, req.clone());
        rsp.rdata = 32'hC0DE_0000 | req.addr;
        p.put(rsp);                               // answer it
      end
    endtask
    
    // test connect_phase
    req.mport.connect(rr_ch.master_export);       // put REQ, get RSP
    rsp.sport.connect(rr_ch.slave_export);        // get REQ, put RSP
    req.tport.connect(tr_ch.transport_export);    // transport(req, rsp) in one call
    rsp.tsport.connect(tr_ch.slave_export);

    Watch it run

    tlm_channel_test connects one requester and one responder through both channels. The requester sends one request each way:

    Diagram
    UVM_INFO tlm_tb.sv(141) @ 10000: uvm_test_top.req [MASTER] response for 10: rdata=c0de0010
    UVM_INFO tlm_tb.sv(145) @ 20000: uvm_test_top.req [TRANSPORT] response for 04: rdata=c0de0004
    • The master port's put returned at once (the request FIFO had room); its get waited the responder's 10 ns. COUNT is at offset 0x10, so the response carries c0de0010.

    • The transport call took the same 10 ns, but as one blocking call. STATUS is at 0x04.

    • The opening errors came from tlm_cardinality_test in the same file: a component with an unconnected put port, a put port connected to two imps, an analysis export connected to nothing, an analysis port with no subscribers and an unconnected uvm_seq_item_pull_port. Only the first three are errors, exactly as the table predicts.

    What breaks: one optional port stops two thousand tests

    A protocol VIP release added a uvm_blocking_get_port to its monitor, for an optional feed of configuration updates. Nobody in the integrating team used the feature, so nobody connected the port. Every test in the regression now stopped at time 0 with connection count of 0 does not meet required minimum of 1 and BUILDERR. The VIP's own test bench had always connected the port, so its authors never saw it.

    There are three clean fixes, in order of preference. The VIP can build the port only when its configuration enables the feature. It can construct the port with a minimum of 0, new("cfg_port", this, 0, 1), and check cfg_port.size() before calling it. Or the integrator connects it to a harmless provider such as an empty uvm_tlm_fifo. What the old course used to claim, that an unconnected port drops transactions silently, is the one outcome that cannot happen for a non-analysis port.

    Interview

    "What happens if you leave a TLM port unconnected?"

    A weak answer: "the transactions are silently dropped." For a put, get or transport port the run never starts.

    A strong answer: "It depends on the connector's minimum count, checked by resolve_bindings as end_of_elaboration starts. Non-analysis ports and exports need exactly one imp, so an unconnected one is a Connection Error and the root stops with BUILDERR. An analysis port's minimum is 0, so it is legal and its writes reach nobody. Careful: an analysis export's minimum is 1. A driver's seq_item_port is a pull port with minimum 0. The maximum counts imps reached, so fan-in to an imp is unlimited."

    Expect the follow-ups: What is the default depth of uvm_tlm_fifo, and of the analysis FIFO? (1, and unbounded) and What is the difference between a master and a slave port? (who puts requests and who puts responses).

    Check yourself A subscriber's uvm_analysis_imp is changed to a uvm_analysis_export so a child can implement it later, but the child is not written yet. The monitor still connects to it. What happens?

    The export reaches no imp, and an analysis export has a minimum of 1, so resolve_bindings reports connection count of 0 does not meet required minimum of 1 and the run stops with BUILDERR. The same gap with an analysis port would be silent.

    Check yourself With a uvm_tlm_req_rsp_channel at its default depths, a requester puts four requests before it reads any response. The responder loops get-request, put-response. What happens?

    Deadlock on the fourth put. Each FIFO holds 1. The first response fills the response FIFO, so the responder blocks putting the second one, while the requester is blocked putting request 4, which needs the responder to take request 3 first. Each waits for the other. Read responses in a separate thread, or size the FIFOs for the number of outstanding requests.

    Check yourself Why can a uvm_tlm_transport_channel offer transport() to the caller while the responder uses ordinary slave-port get and put?

    Inside the channel, transport puts the request into the request FIFO and then blocks in get on the response FIFO. The responder sees a normal request queue and answers into the response queue, so the channel adapts a one-call interface on one side to a loop on the other.

    Next: you have seen an analysis FIFO as a component. In imp_decl vs FIFOs the DMA scoreboard is rebuilt on three of them and run beside the original, and the two designs part ways at end of test.

    Related topics

    • put, get, peek and transport

    • imp_decl vs FIFOs

    • connect() rules

    • End-of-elaboration checks

    Previous

    64 / 280

    Next: Analysis Connectivity Patterns: imp_decl vs FIFOs

    Up next

    Up nextAnalysis Connectivity Patterns: imp_decl vs FIFOs

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Who Calls, Who Implements: Ports, Exports and Imps
    • connect() Rules: Which Way Each Link Points
    • put, get, peek and transport: Flow-Controlled TLM