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.
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.
The three roles in any TLM1 connection:
| Object type | Role | Where declared | Method 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 implementation | Forwards 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 |
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
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 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 family | Blocking variant | Non-blocking variant | Combined |
|---|---|---|---|
| Put | uvm_blocking_put_portmethod: put(t) | uvm_nonblocking_put_portmethods: try_put(t), can_put() | uvm_put_port |
| Get | uvm_blocking_get_portmethod: get(t) | uvm_nonblocking_get_portmethods: try_get(t), can_get() | uvm_get_port |
| Peek | uvm_blocking_peek_portmethod: peek(t) | uvm_nonblocking_peek_portmethods: try_peek(t), can_peek() | uvm_peek_port |
| Get+Peek | uvm_blocking_get_peek_port | uvm_nonblocking_get_peek_port | uvm_get_peek_port |
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
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
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. 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.| Rule | Detail |
|---|---|
| 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 only | Never connect in build_phase — the connected-to component may not exist yet. Always connect_phase. |
| Point-to-point: exactly one export per port | Unlike 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 compatibility | Port and export must be parameterised on the same transaction type T. Mismatched types cause a compile error. |
| Blocking vs non-blocking compatibility | A 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. |
| Scenario | Use | Rationale |
|---|---|---|
| Monitor broadcasts to multiple observers | analysis_port | One-to-many, non-blocking — exactly what analysis was designed for |
| Scoreboard receives from two sources (expected + actual) | Two analysis_imps via `uvm_analysis_imp_decl | Each stream needs a separately named write() method |
| Driver gets items from sequencer | seq_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 pace | put_port → tlm_fifo → get_port | FIFO decouples rates; consumer calls blocking get() when ready |
| Component needs a transaction from another, on demand | blocking_get_port | Pull model — initiator controls timing |
| Request-response in one step (no pipelining) | blocking_transport_port | Single call sends request and receives response atomically |
| Driver needs idle-cycle driving when no items available | try_next_item() via seq_item_port | Non-blocking — returns null immediately if queue empty |
| Component needs to look at next item without consuming it | blocking_peek_port or peek via tlm_fifo | peek() is non-destructive — item remains available |
| Object | Key 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_port | Built into uvm_driver — uvm_seq_item_pull_port. Connect to m_seqr.seq_item_export in agent connect_phase. |
| Connection syntax | port.connect(export_or_imp) — always in connect_phase |
| Point-to-point limit | Exactly one connected export per non-analysis port. Zero or more than one → fatal at end_of_elaboration. |
| Construction | Always new("name", this) — not type_id::create(). TLM objects are infrastructure, not factory-registered. |