UVM-28: TLM Ports — VLSI Trainers
VLSI Trainers UVM Series · UVM-28
UVM Series · UVM-28

TLM Ports

The complete UVM TLM1 port family beyond analysis — blocking and non-blocking put/get/peek, the transport interface, point-to-point FIFOs, port vs export vs imp, connection rules, and the sequencer-driver seq_item_port as the most-used non-analysis TLM connection in every testbench.

📋 TLM1 Port Family Overview

The UVM analysis ports (covered in UVM-17) are one-to-many, non-blocking broadcast channels. The rest of the UVM TLM1 family provides point-to-point, optionally blocking communication — one port connected to exactly one export. This is the communication backbone used everywhere from the driver-sequencer handshake to component-to-component data passing.

All TLM1 objects are parameterised on the transaction type T. The port declares what operations are needed; the export (or imp) provides the implementation.

UVM TLM1 Port Family — Two Groups Analysis (one-to-many, non-blocking) uvm_analysis_port #(T) → write() function, any number of subscribers uvm_analysis_export #(T) → hierarchical forwarder uvm_analysis_imp #(T, C) → direct write() implementation tlm_analysis_fifo #(T) → buffered subscriber Covered in UVM-17 Point-to-Point (one-to-one, blocking/non-blocking) uvm_blocking_put_port #(T) → put(T) task, blocks uvm_blocking_get_port #(T) → get(T) task, blocks uvm_blocking_peek_port #(T) → peek(T) task, no remove uvm_nonblocking_put_port #(T) → try_put(), can_put() functions uvm_transport_port #(REQ,RSP) → transport(req, rsp) task uvm_seq_item_pull_port #(T) → driver↔sequencer handshake vlsitrainers.com
Figure 1 — TLM1 port family split into two groups. Analysis ports (left) are covered in UVM-17 — one-to-many, non-blocking. This post covers the point-to-point family (right) — exactly one port connected to exactly one export, blocking or non-blocking, directional or bidirectional.

📋 Port vs Export vs Imp

The three roles in any TLM1 connection:

Object typeRoleWhere declaredMethod type
uvm_*_port #(T)Initiator — the caller. Holds a reference to the export it is connected to. Calls the method on behalf of the component that owns it.Component that initiates the operation (e.g. the requester)Calls method on connected export
uvm_*_export #(T)Hierarchical pass-through. Forwards calls to a child component’s imp. Used to expose a child’s capability at a parent boundary.Parent component whose child owns the implementationForwards to connected imp
uvm_*_imp #(T, C)Implementation. The actual method body lives in component C. Calling the port ultimately executes the method on C.Component that provides the service (e.g. the responder)Calls C’s put()/get()/etc. directly
Port → Export → Imp Connection Chain Component A put_port connect() Parent Env put_export Component B put_imp calls B.put(t) Component B::put(T t) the actual implementation vlsitrainers.com
Figure 2 — Port → Export → Imp connection chain. Component A calls put() on its port. The port is connected to a hierarchical export at the parent env boundary. The export forwards to Component B’s imp. The imp calls B.put(t) — the actual implementation. The port just knows it can put; the imp actually handles the data.

📋 Blocking Put Interface

The blocking put interface allows one component to send a transaction to another and block until the receiver accepts it. The receiver signals acceptance by returning from the put() task.

// ── Sender: declares a put port ──────────────────────────
class data_producer extends uvm_component;
  `uvm_component_utils(data_producer)

  // Port: this component sends T objects to whoever is connected
  uvm_blocking_put_port #(my_txn) put_port;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    put_port = new("put_port", this);
  endfunction

  task run_phase(uvm_phase phase);
    my_txn t = my_txn::type_id::create("t");
    // Blocks until receiver's put() returns
    put_port.put(t);
  endtask
endclass

// ── Receiver: declares a put imp ─────────────────────────
class data_consumer extends uvm_component;
  `uvm_component_utils(data_consumer)

  // Imp: calls this component's put() when connected port sends
  uvm_blocking_put_imp #(my_txn, data_consumer) put_export;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    put_export = new("put_export", this);
  endfunction

  // Implementation — called when producer puts a transaction
  task put(my_txn t);
    // Process the received transaction
    `uvm_info("CON", t.convert2string(), UVM_MEDIUM)
    // Returns when done — unblocks the producer
  endtask
endclass

// ── Env connect_phase: port → imp (or export) ─────────────
function void connect_phase(uvm_phase phase);
  m_producer.put_port.connect(m_consumer.put_export);
endfunction

📋 Blocking Get and Peek

The blocking get interface is the reverse of put: the initiator (port side) pulls a transaction from the responder (imp side). The port owner calls get(t) and blocks until the responder provides a transaction.

peek() is identical to get() except that the transaction is not consumed — the next call to get() or peek() returns the same transaction again. This is useful when you need to inspect the next available item without removing it.

// ── Component that pulls transactions on demand ───────────
class data_requester extends uvm_component;
  `uvm_component_utils(data_requester)

  uvm_blocking_get_port  #(my_txn) get_port;
  uvm_blocking_peek_port #(my_txn) peek_port;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    get_port  = new("get_port",  this);
    peek_port = new("peek_port", this);
  endfunction

  task run_phase(uvm_phase phase);
    my_txn t;
    // Peek: look without consuming
    peek_port.peek(t);
    `uvm_info("REQ", $sformatf("Peeked: %s", t.convert2string()), UVM_MEDIUM)
    // Get: consume the transaction
    get_port.get(t);
    `uvm_info("REQ", $sformatf("Got: %s", t.convert2string()), UVM_MEDIUM)
  endtask
endclass

// ── Provider implements get() and peek() ─────────────────
class data_provider extends uvm_component;
  `uvm_component_utils(data_provider)

  uvm_blocking_get_imp  #(my_txn, data_provider) get_export;
  uvm_blocking_peek_imp #(my_txn, data_provider) peek_export;

  my_txn m_pending;  // next available transaction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    get_export  = new("get_export",  this);
    peek_export = new("peek_export", this);
  endfunction

  // get(): returns and removes the pending transaction
  task get(output my_txn t);
    wait(m_pending != null);
    t = m_pending;
    m_pending = null;    // consume — removes from queue
  endtask

  // peek(): returns without removing
  task peek(output my_txn t);
    wait(m_pending != null);
    t = m_pending;        // no consume — still available
  endtask
endclass

📋 Non-Blocking Interfaces

Non-blocking variants provide two functions instead of one blocking task: try_* attempts the operation and returns 0 if it cannot complete (without blocking); can_* returns 1 if the operation would succeed without blocking.

// ── Non-blocking put ──────────────────────────────────────
uvm_nonblocking_put_port #(my_txn) nb_put_port;

// try_put(): attempt to put — returns 1 on success, 0 if busy
if (!nb_put_port.try_put(t))
  `uvm_info("PROD", "Receiver busy — dropping transaction", UVM_LOW)

// can_put(): returns 1 if try_put would succeed
if (nb_put_port.can_put())
  nb_put_port.try_put(t);

// ── Non-blocking get ──────────────────────────────────────
uvm_nonblocking_get_port #(my_txn) nb_get_port;

my_txn t;
// try_get(): returns 1 and populates t if available, 0 if empty
if (nb_get_port.try_get(t))
  process(t);
else
  drive_idle();   // nothing available — drive idle state

// ── Combined put interface (blocking + non-blocking) ──────
// uvm_put_port #(T) provides both blocking and non-blocking
uvm_put_port #(my_txn) put_port;  // has put(), try_put(), can_put()
Interface familyBlocking variantNon-blocking variantCombined
Putuvm_blocking_put_port
method: put(t)
uvm_nonblocking_put_port
methods: try_put(t), can_put()
uvm_put_port
Getuvm_blocking_get_port
method: get(t)
uvm_nonblocking_get_port
methods: try_get(t), can_get()
uvm_get_port
Peekuvm_blocking_peek_port
method: peek(t)
uvm_nonblocking_peek_port
methods: try_peek(t), can_peek()
uvm_peek_port
Get+Peekuvm_blocking_get_peek_portuvm_nonblocking_get_peek_portuvm_get_peek_port

📋 Bidirectional Transport Interface

The transport interface combines a request and a response into a single blocking call. The initiator sends a request and receives a response atomically — it blocks until both the send and the receive are complete. This is useful for simple request-response protocols where the round-trip is always one-to-one.

// ── Transport port: sends REQ, receives RSP atomically ────
// uvm_transport_port #(REQ, RSP)

class bus_initiator extends uvm_component;
  `uvm_component_utils(bus_initiator)

  // Parameterised on request and response types
  uvm_blocking_transport_port #(req_txn, rsp_txn) transport_port;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    transport_port = new("transport_port", this);
  endfunction

  task run_phase(uvm_phase phase);
    req_txn req; rsp_txn rsp;
    req = req_txn::type_id::create("req");
    req.addr = 32'h1000;
    // Send and receive in one call — blocks for round-trip
    transport_port.transport(req, rsp);
    `uvm_info("INI", $sformatf("Rsp: %s", rsp.convert2string()), UVM_MEDIUM)
  endtask
endclass

// ── Transport imp: implements the round-trip ──────────────
class bus_target extends uvm_component;
  `uvm_component_utils(bus_target)

  uvm_blocking_transport_imp #(req_txn, rsp_txn, bus_target) transport_export;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    transport_export = new("transport_export", this);
  endfunction

  task transport(req_txn req, output rsp_txn rsp);
    rsp = rsp_txn::type_id::create("rsp");
    rsp.data = fetch_data(req.addr);  // process request
    // Returns when ready — unblocks the initiator
  endtask
endclass

📋 TLM FIFO

The tlm_fifo is a pre-built component that implements both a put export and a get export, with an internal queue between them. It decouples producer and consumer — the producer puts at any rate, the consumer gets when ready. This is the non-analysis equivalent of the tlm_analysis_fifo seen in UVM-17.

// ── TLM FIFO decouples put from get ──────────────────────
class my_env extends uvm_env;
  `uvm_component_utils(my_env)

  data_producer   m_prod;
  data_consumer   m_cons;
  tlm_fifo #(my_txn) m_fifo;   // default depth = 1
  // tlm_fifo #(my_txn, 16) m_fifo;  // depth = 16

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_prod = data_producer::type_id::create("m_prod", this);
    m_cons = data_consumer::type_id::create("m_cons", this);
    m_fifo = new("m_fifo", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    // Producer puts into FIFO
    m_prod.put_port.connect(m_fifo.put_export);
    // Consumer gets from FIFO
    m_cons.get_port.connect(m_fifo.get_export);
  endfunction
endclass

// ── TLM FIFO API ──────────────────────────────────────────
// m_fifo.put_export    — connect producer's put_port here
// m_fifo.get_export    — connect consumer's get_port here
// m_fifo.peek_export   — connect consumer's peek_port here
// m_fifo.size()        — current number of items in FIFO
// m_fifo.is_empty()    — true if FIFO empty
// m_fifo.is_full()     — true if FIFO at capacity
// m_fifo.flush()       — remove all items
Use tlm_fifo when producer and consumer run at different rates. If the producer can generate transactions faster than the consumer can process them, a depth-1 TLM FIFO will stall the producer when full. Choose the FIFO depth to match the maximum burst the producer can generate before the consumer catches up. For unbounded buffering, a depth of 0 creates an infinite-depth FIFO.

📋 seq_item_port — The Hidden TLM Port

The most-used non-analysis TLM port in every UVM testbench is one you have been using since UVM-13 without thinking about it: seq_item_port inside uvm_driver. It is a specialised TLM port — uvm_seq_item_pull_port #(REQ, RSP) — that provides the complete driver-sequencer protocol (get_next_item, item_done, get, put, try_next_item).

// ── seq_item_port is a specialised TLM port ───────────────
// Declared in uvm_driver as:
// uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;

// The sequencer declares the matching export:
// uvm_seq_item_pull_imp #(REQ, RSP, THIS) seq_item_export;

// Connected in agent connect_phase:
function void connect_phase(uvm_phase phase);
  m_drv.seq_item_port.connect(m_seqr.seq_item_export);
endfunction

// ── Methods available on seq_item_port ────────────────────
// get_next_item(req)  — blocking get, sequence stays blocked
// item_done()         — signals completion
// get(req)            — blocking get, sequence immediately unblocked
// put(rsp)            — returns response to sequence
// try_next_item(req)  — non-blocking get (returns null if nothing ready)
// peek_next_item(req) — get without consuming
seq_item_port is always declared and constructed by uvm_driver — you never need to create it. It is a protected member of the uvm_driver base class, always named seq_item_port. The agent’s connect_phase simply calls m_drv.seq_item_port.connect(m_seqr.seq_item_export) to wire the two together. This is covered in detail in UVM-13.

📋 Connection Rules

RuleDetail
Always port.connect(export/imp)The connection is initiated by the port. Passing the export/imp as the argument. Reversing this is a compile error.
Connect in connect_phase onlyNever connect in build_phase — the connected-to component may not exist yet. Always connect_phase.
Point-to-point: exactly one export per portUnlike analysis ports, non-analysis ports require exactly one connected export. Connecting zero or more than one causes a fatal error at end_of_elaboration.
Type compatibilityPort and export must be parameterised on the same transaction type T. Mismatched types cause a compile error.
Blocking vs non-blocking compatibilityA blocking port must connect to a blocking imp. A non-blocking port must connect to a non-blocking imp. Connecting mismatched variants causes a runtime error.
Use new() not type_id::create()All TLM ports, exports, and imps are constructed with new("name", this) — not factory create(). They are infrastructure objects, not user-extensible.

📋 When to Use Which Interface

ScenarioUseRationale
Monitor broadcasts to multiple observersanalysis_portOne-to-many, non-blocking — exactly what analysis was designed for
Scoreboard receives from two sources (expected + actual)Two analysis_imps via `uvm_analysis_imp_declEach stream needs a separately named write() method
Driver gets items from sequencerseq_item_port (built into uvm_driver)Specialised TLM with get_next_item/item_done/get/put protocol
Producer sends transactions to consumer, consumer processes at its own paceput_port → tlm_fifo → get_portFIFO decouples rates; consumer calls blocking get() when ready
Component needs a transaction from another, on demandblocking_get_portPull model — initiator controls timing
Request-response in one step (no pipelining)blocking_transport_portSingle call sends request and receives response atomically
Driver needs idle-cycle driving when no items availabletry_next_item() via seq_item_portNon-blocking — returns null immediately if queue empty
Component needs to look at next item without consuming itblocking_peek_port or peek via tlm_fifopeek() is non-destructive — item remains available

📋 Quick Reference

ObjectKey fact
uvm_blocking_put_port #(T)Initiator calls put(t) — blocks until imp returns
uvm_blocking_get_port #(T)Initiator calls get(t) — blocks until item available
uvm_blocking_peek_port #(T)Like get but non-destructive — item stays in source
uvm_nonblocking_put_port #(T)try_put(t) → returns 0 if busy. can_put() → 1 if would succeed.
uvm_nonblocking_get_port #(T)try_get(t) → returns 0 if empty. can_get() → 1 if available.
uvm_put_port #(T)Combined blocking + non-blocking put
uvm_get_port #(T)Combined blocking + non-blocking get
uvm_blocking_transport_port #(REQ,RSP)transport(req, rsp) — send + receive in one blocking call
uvm_*_imp #(T, C)Implementation — calls method directly on component C. Parameterised on transaction T and owner class C.
uvm_*_export #(T)Hierarchical pass-through — forwards to child’s imp
tlm_fifo #(T)Built-in FIFO — put_export input, get_export output. Depth: default 1, 0 = infinite.
seq_item_portBuilt into uvm_driver — uvm_seq_item_pull_port. Connect to m_seqr.seq_item_export in agent connect_phase.
Connection syntaxport.connect(export_or_imp) — always in connect_phase
Point-to-point limitExactly one connected export per non-analysis port. Zero or more than one → fatal at end_of_elaboration.
ConstructionAlways new("name", this) — not type_id::create(). TLM objects are infrastructure, not factory-registered.
Scroll to Top