Skip to content

TLM 1.0: Ports, Exports, Imps

Three actors, push and pull models, blocking vs non-blocking, sequencer-driver TLM.

UVM Fundamentals · Module 12

What TLM Solves — Decoupled Communication

Without TLM, a monitor that needs to report transactions to a scoreboard holds a direct handle to the scoreboard. The monitor class must import the scoreboard's package. Changing the scoreboard requires editing the monitor. The monitor cannot be tested without the scoreboard present. Reuse is impossible.

With TLM, the monitor writes to an analysis port — a standard interface it owns. The scoreboard declares an analysis implementation — a standard interface it responds to. They are connected once, in connect_phase, by whoever built both of them. Neither component knows the other's type.

  • Decoupling — The monitor only knows it has an analysis_port. The scoreboard only knows it implements write(). Neither imports the other's package. Each can be reused independently.
  • Standard Interfaces — TLM defines standard methods: put(), get(), peek(), write(). Any component implementing the same interface is interchangeable — the factory override then becomes trivially easy.
  • Type Safety — TLM classes are parameterised by transaction type T. A put_port #(apb_txn) can only connect to a put_imp #(apb_txn, ...). Type mismatch is caught at compile time.

The Three TLM Actors — Port, Export, Implementation

Every TLM connection involves exactly three roles. Understanding what each one is and who owns it is the foundation of all TLM in UVM. SIMPLE CONNECTION — Port directly to ImpProducer (Initiator)e.g. apb_monitorput_portuvm_blocking_put_port #(T)connect_phase:port.connect(imp)Consumer (Target)e.g. my_scoreboardput_impuvm_blocking_put_imp #(T, scb)Consumer implements task put(T txn); directlyHIERARCHICAL CONNECTION — Port to Export to Imp (wrap through parent)Producerput_portport.connect(outer.export)Parent Component (env or agent)put_exportouter interfaceSub-componentput_imp (implements put())export.connect(imp)resultUse Export when:• Sub-component has the IMP• Parent needs to expose it• connect_phase: parent export → child imp (in parent) Figure 1 — TLM connection patterns. Simple: port connects directly to imp (most common). Hierarchical: port connects to export which a parent component provides, passing through to an inner subcomponent's imp.

RoleDeclared ByClassKey Characteristic
PortThe initiator — the component that INITIATES communicationuvm_blocking_put_port #(T)Must be connected before simulation starts. Calling methods on an unconnected port causes a fatal error at start_of_simulation.
ExportA pass-through wrapper — used when the IMP is inside a subcomponentuvm_blocking_put_export #(T)Delegates to an IMP. A parent component uses an export to expose its child's IMP to external connections. Not always needed.
Imp (Implementation)The responder — the component that IMPLEMENTS the methoduvm_blocking_put_imp #(T, comp_type)The second parameter is the component class that will implement the put() / get() method. The method is called on the containing component directly.

The Push Model — Initiator Drives the Transaction

In the push model, the initiator calls put(txn) on its port. The call flows through the TLM chain and arrives at the implementation's put() task. The initiator controls timing. The target receives and processes.

SystemVerilog — complete push model: producer → consumer
// ── PRODUCER — declares a put_port ────────────────────────────────────
class apb_monitor extends uvm_monitor;
`uvm_component_utils(apb_monitor)
 
// Port: "I need someone to receive my transactions"
uvm_blocking_put_port #(apb_seq_item) put_port;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_port = new("put_port", this);  // ports use new(), not factory create()
endfunction
 
task run_phase(uvm_phase phase);
apb_seq_item txn;
forever begin
collect_transaction(txn);       // observe bus, build transaction object
put_port.put(txn);              // push to whoever is connected
end
endtask
task collect_transaction(ref apb_seq_item txn); /* ... */ endtask
endclass
 
// ── CONSUMER — declares a put_imp and implements put() ────────────────
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
 
// Imp: "I will respond to put() calls"
// Second parameter = this class (so put() is called on THIS component)
uvm_blocking_put_imp #(apb_seq_item, my_scoreboard) put_export;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_export = new("put_export", this);
endfunction
 
// This task is called by the TLM framework when monitor calls put_port.put()
task put(apb_seq_item txn);
`uvm_info("SCB", $sformatf("Received txn: addr=0x%0h", txn.addr), UVM_MEDIUM)
check_transaction(txn);
endtask
task check_transaction(apb_seq_item txn); /* ... */ endtask
endclass
 
// ── CONNECTION — in the parent component's connect_phase ──────────────
class my_env extends uvm_env;
apb_monitor    mon;
my_scoreboard  scb;
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.put_port.connect(scb.put_export);   // PORT connects to IMP
endfunction
endclass

The Pull Model — Initiator Requests the Transaction

In the pull model, the initiator calls get(txn) on its port. The call blocks until the target has a transaction available. The initiator controls when it wants data. The target produces on demand. The driver-sequencer relationship is the canonical pull model in UVM. PUSH MODEL — put()MONITORput_portSCOREBOARDput_impControl: put(txn)Data: txn →Initiator decides WHEN to push.Control and data flow in same direction.Initiator: monitor, driver (response)PULL MODEL — get()DRIVERget_portSEQUENCERget_impControl: get(txn)← Data: txn returnedInitiator decides WHEN to pull.Control and data flow in opposite directions.Initiator: driver requesting next txn Figure 2 — Push model (left): initiator calls put(), data flows from initiator to target. Pull model (right): initiator calls get(), data flows from target back to initiator. The driver-sequencer connection is always pull.

SystemVerilog — pull model: get() port and implementation
// ── INITIATOR — calls get() to request a transaction ──────────────────
class my_consumer extends uvm_component;
`uvm_component_utils(my_consumer)
 
uvm_blocking_get_port #(apb_seq_item) get_port;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
get_port = new("get_port", this);
endfunction
 
task run_phase(uvm_phase phase);
apb_seq_item txn;
forever begin
get_port.get(txn);   // blocks until producer provides a transaction
process(txn);
end
endtask
task process(apb_seq_item txn); /* ... */ endtask
endclass
 
// ── TARGET — implements get() ──────────────────────────────────────────
class my_producer extends uvm_component;
`uvm_component_utils(my_producer)
 
uvm_blocking_get_imp #(apb_seq_item, my_producer) get_export;
apb_seq_item txn_queue[$];
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
get_export = new("get_export", this);
endfunction
 
// Called by TLM when consumer calls get_port.get()
task get(output apb_seq_item txn);
wait (txn_queue.size() > 0);   // block until item available
txn = txn_queue.pop_front();
endtask
endclass

Connection Rules — What Can Connect to What

FromToValid?Notes
uvm_*_portuvm_*_impYESMost common. Direct connection from initiator to responder.
uvm_*_portuvm_*_exportYESUsed when the imp is inside a subcomponent exposed via export.
uvm_*_portuvm_*_portNOPorts cannot connect to each other. One side must be an imp or export.
uvm_*_exportuvm_*_impYESUsed inside a component to chain export → imp. Called in the parent's connect_phase.
uvm_*_exportuvm_*_exportYESFor multi-level hierarchical passing through intermediate components.
uvm_analysis_portMultiple impsYESAnalysis ports support fanout — one port, many subscribers. Covered in Module 13.
SystemVerilog — connection in connect_phase
// ── connect_phase: correct placement ──────────────────────────────────
class my_env extends uvm_env;
apb_monitor   mon;
my_scoreboard scb;
 
// build_phase: create components
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = apb_monitor::type_id::create("mon", this);
scb = my_scoreboard::type_id::create("scb", this);
endfunction
 
// connect_phase: wire up TLM — called AFTER all build_phases complete
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.put_port.connect(scb.put_export);   // ← correct place
endfunction
endclass
 
// ── Checking port connectivity ────────────────────────────────────────
// At start_of_simulation_phase, UVM automatically checks that all ports
// with min_size > 0 are connected. If a required port is not connected:
// UVM_FATAL: Port "mon.put_port" [INVRST] has 0 connections — 1 required

Blocking vs. Non-Blocking Interfaces

TLM interfaces come in three flavours. The choice determines whether the caller must wait for the operation to complete.

InterfaceMethodsReturnsUse When
Blocking uvm_blocking_put_*task put(T txn) task get(output T txn)Task — can consume time. Caller blocks until complete.The default for most connections. Driver blocks waiting for next item. Monitor blocks waiting for bus idle.
Non-Blocking uvm_nonblocking_put_*function bit try_put(T txn) function bit try_get(output T txn) function bit can_put()Function — returns immediately. Returns 1 on success, 0 if busy/empty.Polling patterns. Try to put without blocking. Check if data is available before committing.
Combined uvm_put_*All of the above — both blocking and non-blockingProvides both task and function interfaces.When the target needs to support both blocking and non-blocking callers.
SystemVerilog — non-blocking interface: try_put and can_put
// ── Non-blocking put: try without blocking ────────────────────────────
uvm_nonblocking_put_port #(apb_seq_item) nb_put_port;
 
task run_phase(uvm_phase phase);
apb_seq_item txn;
forever begin
@(posedge vif.clk);
if (nb_put_port.can_put()) begin   // check before trying — function, no time
build_txn(txn);
if (!nb_put_port.try_put(txn))  // returns 0 if target is busy — function
`uvm_warn("TLM", "try_put failed — target busy")
end
end
endtask
 
// ── Implementation of non-blocking interface ───────────────────────────
// Must implement BOTH can_put() and try_put() when using uvm_nonblocking_put_imp
function bit can_put();
return (queue.size() < 10);   // 1 = there is space, 0 = full
endfunction
 
function bit try_put(apb_seq_item txn);
if (queue.size() >= 10) return 0;   // 0 = rejected
queue.push_back(txn);
return 1;                             // 1 = accepted
endfunction

The Sequencer-Driver TLM Connection — The Most Important Pattern

The sequencer-driver handshake is a specialised TLM pull connection built into UVM. The driver pulls items from the sequencer using a request-response protocol: get_next_item() blocks until an item is available, and item_done() signals completion.

SystemVerilog — complete sequencer-driver TLM pattern
// ── DRIVER — uses seq_item_port (inherited from uvm_driver) ──────────
class apb_driver extends uvm_driver #(apb_seq_item);
`uvm_component_utils(apb_driver)
// uvm_driver already declares:
//   uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;
 
task run_phase(uvm_phase phase);
apb_seq_item req;
forever begin
// Pull: blocks until sequencer has an item for us
seq_item_port.get_next_item(req);
 
// Drive the DUT signals based on req
@(posedge vif.clk);
vif.paddr   = req.addr;
vif.pwdata  = req.data;
vif.pwrite  = req.write;
vif.psel    = 1;
vif.penable = 1;
@(posedge vif.clk iff vif.pready);
 
// Signal completion — sequencer can release the item
seq_item_port.item_done();
// Optional: fill response fields before item_done if RSP != REQ
end
endtask
endclass
 
// ── SEQUENCER — instantiated, connected in the agent ──────────────────
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
 
apb_driver                            drv;
uvm_sequencer #(apb_seq_item)         seqr;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv  = apb_driver::type_id::create("drv", this);
seqr = uvm_sequencer#(apb_seq_item)::type_id::create("seqr", this);
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connect driver's pull port to sequencer's export
drv.seq_item_port.connect(seqr.seq_item_export);
endfunction
endclass
 
// ── get_next_item() vs try_next_item() ────────────────────────────────
//   get_next_item(req)  — blocks until item available (most common)
//   try_next_item(req)  — returns null if no item available (non-blocking)
//   item_done()         — must be called after processing (always)
//   item_done(rsp)      — optional response field back to sequence

Quick Reference — TLM Class Names and Patterns

NeedPort (initiator)Imp (target)Method implemented
Push a transactionuvm_blocking_put_port #(T)uvm_blocking_put_imp #(T, comp)task put(T txn)
Pull a transactionuvm_blocking_get_port #(T)uvm_blocking_get_imp #(T, comp)task get(output T txn)
Peek without consuminguvm_blocking_peek_port #(T)uvm_blocking_peek_imp #(T, comp)task peek(output T txn)
Non-blocking pushuvm_nonblocking_put_port #(T)uvm_nonblocking_put_imp #(T, comp)bit try_put(T), bit can_put()
Broadcast (one-to-many)uvm_analysis_port #(T)uvm_analysis_imp #(T, comp)function void write(T txn)
Sequencer-driverseq_item_port (in uvm_driver)seq_item_export (in uvm_sequencer)Built-in TLM FIFO internally
SystemVerilog — TLM cheat sheet: rules in comments
// ── TLM Rules ─────────────────────────────────────────────────────────
//
//  1. PORT declares "I initiate"   IMP declares "I respond"
//  2. PORT connects to IMP or EXPORT — never PORT to PORT
//  3. All .connect() calls go in connect_phase(), not build_phase()
//  4. Ports with min_connections > 0 must be connected — fatal if not
//  5. Both PORT and IMP are parameterised by T — types must match exactly
//  6. IMP second parameter is the component class that implements the method
//  7. Analysis ports support fanout; all other ports are point-to-point
//  8. For seqr-driver: always call item_done() after get_next_item()
 
// ── Naming convention (used in all UVM VIPs) ─────────────────────────
// Ports ending in _port are initiator side (driver, monitor's output)
// Ports ending in _export are target side (scoreboard's input face)
// Despite name: uvm_blocking_put_imp IS the export for the scoreboard
 
// ── Minimum required code to add TLM to a component ──────────────────
 
// Producer side:
uvm_blocking_put_port #(my_txn) put_port;
// in build_phase:
put_port = new("put_port", this);
// in run_phase:
put_port.put(txn);
 
// Consumer side:
uvm_blocking_put_imp #(my_txn, my_consumer) put_export;
// in build_phase:
put_export = new("put_export", this);
// implement the method:
task put(my_txn txn); /* process txn */ endtask
 
// Connection (in parent component's connect_phase):
producer.put_port.connect(consumer.put_export);

§9 — Code Examples

Example 1 — Beginner: Push Model, Monitor → Scoreboard

The most common TLM connection in any testbench. Monitor observes the bus, pushes transactions to a scoreboard for checking. Neither component holds a reference to the other after the connection is made.

SystemVerilog — push model: monitor to scoreboard, complete
// ── Transaction type ──────────────────────────────────────────────────
class apb_txn extends uvm_sequence_item;
`uvm_object_utils(apb_txn)
rand logic [31:0] addr;
rand logic [31:0] data;
rand logic        write;
function new(string name = "apb_txn"); super.new(name); endfunction
endclass
 
// ── Monitor: observes bus, pushes transactions ─────────────────────────
class apb_monitor extends uvm_monitor;
`uvm_component_utils(apb_monitor)
uvm_blocking_put_port#(apb_txn) put_port;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_port = new("put_port", this); // ports use new(), NOT factory create()
endfunction
 
task run_phase(uvm_phase phase);
apb_txn txn;
forever begin
@(posedge vif.clk iff (vif.psel && vif.penable && vif.pready));
txn       = apb_txn::type_id::create("txn");
txn.addr  = vif.paddr;
txn.data  = vif.pwrite ? vif.pwdata : vif.prdata;
txn.write = vif.pwrite;
put_port.put(txn); // blocks until scoreboard's put() returns
end
endtask
endclass
 
// ── Scoreboard: implements put() — receives transactions ──────────────
class apb_scoreboard extends uvm_scoreboard;
`uvm_component_utils(apb_scoreboard)
uvm_blocking_put_imp#(apb_txn, apb_scoreboard) put_export;
// ↑ Second param is THIS class — put() is called on this component
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_export = new("put_export", this);
endfunction
 
task put(apb_txn txn); // TLM calls this when monitor does put_port.put()
`uvm_info("SCB", $sformatf("Checking: addr=0x%0h data=0x%0h write=%0b",
txn.addr, txn.data, txn.write), UVM_MEDIUM)
// ... compare against expected ...
endtask
endclass
 
// ── Env: creates and connects — note connect_phase, NOT build_phase ───
class apb_env extends uvm_env;
`uvm_component_utils(apb_env)
apb_monitor    mon;
apb_scoreboard scb;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = apb_monitor::type_id::create("mon", this);
scb = apb_scoreboard::type_id::create("scb", this);
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.put_port.connect(scb.put_export); // port → imp: the only valid connection
endfunction
endclass

Example 2 — Intermediate: Hierarchical Export (Passing Through an Agent)

Real VIP structure: the scoreboard is outside the agent. The monitor is inside the agent. The agent needs to expose the monitor's port as its own export, so the env can connect directly to the agent's boundary.

SystemVerilog — hierarchical TLM: monitor port exposed via agent
// ── Inside the agent: expose monitor's put_port as an agent-level port ─
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_driver     drv;
apb_monitor    mon;
uvm_sequencer#(apb_txn) seqr;
 
// Agent re-exports the monitor's TLM port as its own interface
// Env connects to apb_agent.mon_port — not to mon.put_port directly
uvm_blocking_put_port#(apb_txn) mon_port; // ← this IS the agent's outward face
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv  = apb_driver::type_id::create("drv",  this);
mon  = apb_monitor::type_id::create("mon",  this);
seqr = uvm_sequencer#(apb_txn)::type_id::create("seqr", this);
mon_port = new("mon_port", this); // agent's own port wrapping mon's port
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
drv.seq_item_port.connect(seqr.seq_item_export);
// Chain the agent's port to monitor's port
// The actual IMP is in the scoreboard — connected from env
mon.put_port.connect(mon_port); // ← port-to-port OK here? Actually use analysis port for this pattern
endfunction
endclass
 
// NOTE: The cleaner industry approach is to use uvm_analysis_port for monitors
// (covered in Module 13) — it supports fan-out and no direct connection chain needed.
// The above pattern is valid for blocking put TLM where exactly one subscriber exists.
 
// ── Env connects agent's exposed port to scoreboard ────────────────────
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Env sees apb_agent.mon_port — doesn't know about mon directly
apb_agent.mon_port.connect(scb.put_export);
endfunction

Example 3 — Verification: Dual-Monitor Scoreboard with Request-Response

A read-back scoreboard: a write monitor pushes expected results, a read monitor pushes actual results, and the scoreboard uses two separate TLM ports to receive both streams. This is a common pattern in memory controller verification.

SystemVerilog — dual-port scoreboard: write channel + read channel
// ── Scoreboard with two separate TLM ports (one per channel) ─────────
class mem_scoreboard extends uvm_scoreboard;
`uvm_component_utils(mem_scoreboard)
 
// Two separate put_imps — one for write transactions, one for read results
// Can't use two uvm_blocking_put_imp with the same name on same class without macro
// Solution: use uvm_tlm_analysis_fifo (Module 14) OR the analysis port pattern
// For blocking put with two ports, use uvm_tlm_*_imp_decl macros:
 
`uvm_blocking_put_imp_decl(_wr)  // declares uvm_blocking_put_imp_wr
`uvm_blocking_put_imp_decl(_rd)  // declares uvm_blocking_put_imp_rd
 
uvm_blocking_put_imp_wr#(mem_txn, mem_scoreboard) wr_export;
uvm_blocking_put_imp_rd#(mem_txn, mem_scoreboard) rd_export;
 
mem_txn expected_q[$];  // write ops stored as expected reads
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
wr_export = new("wr_export", this);
rd_export = new("rd_export", this);
endfunction
 
// Called when write monitor does wr_port.put(txn)
task put_wr(mem_txn txn);
expected_q.push_back(txn);
`uvm_info("SCB", $sformatf("Expected: addr=0x%0h data=0x%0h",
txn.addr, txn.data), UVM_HIGH)
endtask
 
// Called when read monitor does rd_port.put(txn)
task put_rd(mem_txn actual);
mem_txn expected;
if (expected_q.size() == 0) begin
`uvm_error("SCB", "Read response received but no pending expected")
return;
end
expected = expected_q.pop_front();
if (actual.data !== expected.data)
`uvm_error("SCB", $sformatf("MISMATCH addr=0x%0h exp=0x%0h got=0x%0h",
actual.addr, expected.data, actual.data))
else
`uvm_info("SCB", "MATCH", UVM_HIGH)
endtask
endclass
 
// ── Env connection ─────────────────────────────────────────────────────
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
wr_mon.put_port.connect(scb.wr_export);  // write channel
rd_mon.put_port.connect(scb.rd_export);  // read channel
endfunction

Example 4 — Tricky: get_next_item() Without item_done() — The Deadlock

The single most common TLM bug in new driver implementations. The sequence never advances, the simulation hangs, and the timeout message points nowhere useful.

SystemVerilog — missing item_done() deadlock and fix
// ❌ DEADLOCK — item_done() never called ──────────────────────────────
task run_phase(uvm_phase phase);
apb_txn req;
forever begin
seq_item_port.get_next_item(req);   // blocks → gets item 1 → continues
@(posedge vif.clk);
vif.paddr  <= req.addr;
vif.pwdata <= req.data;
@(posedge vif.clk iff vif.pready);
// FORGOT item_done() here!
// Second call to get_next_item() → sequencer sees item 1 still "in flight"
// → deadlock: sequencer holds item 1 locked, never sends item 2
// → simulation runs until UVM_TIMEOUT fires (typically 1ms later)
end
endtask
// UVM_FATAL timeout: simulation timed out after 1ms with objection held
// No useful stack trace — the real cause is inside get_next_item()
 
// ✓ CORRECT — item_done() after every get_next_item() ─────────────────
task run_phase(uvm_phase phase);
apb_txn req;
forever begin
seq_item_port.get_next_item(req);
@(posedge vif.clk);
vif.paddr  <= req.addr;
vif.pwdata <= req.data;
@(posedge vif.clk iff vif.pready);
seq_item_port.item_done();  // ← mandatory: releases item 1, sequencer sends item 2
end
endtask
 
// Rule: every get_next_item() must be paired with exactly one item_done()
// Think of it as a lock: get_next_item() acquires, item_done() releases.
// Missing item_done() leaves the lock held forever.

§10 — Bugs & Debugging

Bug 1 — TLM Connection in build_phase Instead of connect_phase

⚠️ Production Bug — Fatal at start_of_simulation: Target Component Not Yet Built

An engineer calls mon.put_port.connect(scb.put_export) inside build_phase. In build_phase, both components may not be fully constructed yet — their port objects may be null. UVM catches this and fires a null-handle fatal. Even if it doesn't crash immediately, the connection state is undefined because build_phase is not guaranteed to run all components before the first connect call.

SystemVerilog — wrong phase for connect() and the fix
// ❌ WRONG — TLM connect in build_phase ───────────────────────────────
class apb_env extends uvm_env;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = apb_monitor::type_id::create("mon", this);
scb = apb_scoreboard::type_id::create("scb", this);
mon.put_port.connect(scb.put_export); // ← WRONG — connect in build_phase
endfunction
endclass
// Possible result 1: null handle if scb.put_export not yet constructed
// Possible result 2: UVM_FATAL: connect called before end_of_elaboration
// Either way, the simulation doesn't run.
 
// ✓ CORRECT — create in build_phase, connect in connect_phase ─────────
class apb_env extends uvm_env;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = apb_monitor::type_id::create("mon", this);
scb = apb_scoreboard::type_id::create("scb", this);
// NO connect here — just create
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.put_port.connect(scb.put_export); // ← CORRECT place
// By now: ALL build_phases in the hierarchy have completed
// All components and their ports are fully constructed
endfunction
endclass

Bug 2 — Forgetting the Second Parameter of uvm_blocking_put_imp

⚠️ Compile-Time Error — Second Parameter Is the Method Owner

The second template parameter of uvm_blocking_put_imp tells UVM which class implements the put() task. Engineers commonly omit it or use the wrong class. If you use a parent class that doesn't implement put(), the TLM framework can't find the method — compile error or runtime null method dispatch.

SystemVerilog — wrong second parameter in put_imp
// ❌ WRONG — using base class as second parameter ─────────────────────
class my_scoreboard extends uvm_scoreboard;
// Second param = uvm_scoreboard → tries to call put() on uvm_scoreboard
// uvm_scoreboard does not have a put() method → compile error
uvm_blocking_put_imp#(apb_txn, uvm_scoreboard) put_export; // ← WRONG
task put(apb_txn txn); /* ... */ endtask
endclass
// Error: 'put' is not a member of 'uvm_scoreboard'
 
// ❌ WRONG — trying to create a put_imp for a different class ─────────
uvm_blocking_put_imp#(apb_txn, other_class) put_export;
// TLM will call put() on 'other_class' — but put_export is inside my_scoreboard
// The second parameter must be the class that CONTAINS this imp and implements put()
 
// ✓ CORRECT — second parameter is THIS class ──────────────────────────
class my_scoreboard extends uvm_scoreboard;
uvm_blocking_put_imp#(apb_txn, my_scoreboard) put_export; // ← CORRECT
// Second param = my_scoreboard → TLM calls my_scoreboard::put()
task put(apb_txn txn); /* called by TLM framework */ endtask
endclass
 
// Rule: the second parameter of *_imp is always the class that:
// 1. Declares the imp object
// 2. Implements the corresponding method (put/get/peek)

Bug 3 — Port-to-Port Connection (Both Sides Are Initiators)

SystemVerilog — port-to-port: what goes wrong and why
// ❌ WRONG — two ports trying to connect to each other ────────────────
// monitor.sv:
uvm_blocking_put_port#(apb_txn) put_port;   // ← port (initiator)
 
// scoreboard.sv (WRONG — should be imp, not port):
uvm_blocking_put_port#(apb_txn) put_port;   // ← also a port — no put() implemented
 
// env.sv:
mon.put_port.connect(scb.put_port);   // ← both initiators — invalid
// Result: UVM_FATAL at start_of_simulation_phase:
// "port 'mon.put_port' has 0 connections to an implementation"
 
// ✓ CORRECT — scoreboard declares an imp, not a port ─────────────────
// scoreboard.sv:
uvm_blocking_put_imp#(apb_txn, apb_scoreboard) put_export;  // ← imp (responder)
task put(apb_txn txn); /* process */ endtask
 
// env.sv:
mon.put_port.connect(scb.put_export);   // ← port → imp: valid
 
// Memory rule:
// _port  = "I start communication" = needs someone to receive
// _imp   = "I receive communication" = implements the method
// If you're implementing put()/get()/write() → you need an _imp, not a _port

§11 — Ready-to-Run Code

A complete, self-contained TLM push model demo. A producer pushes five transactions through a blocking put port to a consumer that implements put(). No UVC, no DUT — just the TLM pattern in isolation so you can study the flow.

tlm_push_demo.sv — compile and run
// tlm_push_demo.sv — complete runnable TLM push model demo
// Compile: vlog -sv tlm_push_demo.sv
// Run:     vsim -c work.tb_tlm_top +UVM_TESTNAME=tlm_push_test -do "run -all; quit"
 
`include "uvm_macros.svh"
import uvm_pkg::*;
 
// ── Simple transaction ─────────────────────────────────────────────────
class simple_txn extends uvm_sequence_item;
`uvm_object_utils(simple_txn)
int id;
int data;
function new(string name = "simple_txn"); super.new(name); endfunction
endclass
 
// ── Producer: pushes transactions through its put_port ─────────────────
class txn_producer extends uvm_component;
`uvm_component_utils(txn_producer)
uvm_blocking_put_port#(simple_txn) put_port;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_port = new("put_port", this);
endfunction
 
task run_phase(uvm_phase phase);
simple_txn txn;
phase.raise_objection(this);
for (int i = 0; i < 5; i++) begin
txn      = simple_txn::type_id::create($sformatf("txn%0d",i));
txn.id   = i;
txn.data = (i + 1) * 16'hAA;
`uvm_info("PROD", $sformatf("Pushing txn[%0d] data=0x%0h", i, txn.data), UVM_LOW)
put_port.put(txn);   // blocks until consumer's put() returns
`uvm_info("PROD", $sformatf("txn[%0d] accepted by consumer", i), UVM_LOW)
end
phase.drop_objection(this);
endtask
endclass
 
// ── Consumer: implements put() ─────────────────────────────────────────
class txn_consumer extends uvm_component;
`uvm_component_utils(txn_consumer)
uvm_blocking_put_imp#(simple_txn, txn_consumer) put_export;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_export = new("put_export", this);
endfunction
 
task put(simple_txn txn);   // called by TLM when producer calls put_port.put()
`uvm_info("CONS", $sformatf("Received txn[%0d] data=0x%0h — processing",
txn.id, txn.data), UVM_LOW)
// Simulate processing time (2ns per transaction)
#2;
endtask
endclass
 
// ── Test: connects producer to consumer ───────────────────────────────
class tlm_push_test extends uvm_test;
`uvm_component_utils(tlm_push_test)
txn_producer prod;
txn_consumer cons;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
prod = txn_producer::type_id::create("prod", this);
cons = txn_consumer::type_id::create("cons", this);
endfunction
 
function void connect_phase(uvm_phase phase);
prod.put_port.connect(cons.put_export); // THE connection
endfunction
endclass
 
module tb_tlm_top;
initial run_test();
endmodule
 
// Expected output:
// UVM_INFO PROD: Pushing txn[0] data=0xaa — processing
// UVM_INFO CONS: Received txn[0] data=0xaa — processing
// UVM_INFO PROD: txn[0] accepted by consumer     ← put() returned after 2ns
// UVM_INFO PROD: Pushing txn[1] data=0x154
// UVM_INFO CONS: Received txn[1] data=0x154 — processing
// UVM_INFO PROD: txn[1] accepted by consumer
// ... (repeats 5 times total)
// --- UVM Report Summary ---
// UVM_INFO: 15     UVM_ERROR: 0     → PASS
//
// Key observation: producer blocks on each put() until consumer's put() returns.
// Blocking TLM is synchronous — the chain waits at each step.

§12 — Interview Questions

Beginner Level

Intermediate Level

Senior / Architect Level

§13 — Best Practices

RulePracticeWhy It Matters
BP-1Create ports with new(), not type_id::create()TLM ports are not UVM objects and don't go through the factory — using create() silently fails
BP-2Always call connect() in connect_phase(), never in build_phase()build_phase order is not guaranteed to have all children constructed when connect() is called
BP-3Always pair get_next_item() with exactly one item_done()Missing item_done() creates a deadlock on the second transaction — simulation hangs silently
BP-4Second parameter of uvm_*_imp must be the class that declares it and implements the methodUsing the wrong class causes compile errors or null method dispatch at runtime
BP-5Use uvm_analysis_port for monitors, not uvm_blocking_put_portAnalysis ports support fan-out (multiple subscribers), are non-blocking, and don't require a paired imp — the standard for monitor output
BP-6Use blocking TLM for driver-sequencer; non-blocking for polled/queued patternsBlocking TLM matches the natural driver pattern — wait for item, drive, done; non-blocking avoids time-consuming waits in tightly-timed polling loops
BP-7If a scoreboard needs two input channels, use ``uvm_blocking_put_imp_decl` or switch to analysis ports with a FIFOMultiple put_imps in one class require different method names — the suffix macro handles this automatically
BP-8Verify all connections at end_of_elaboration — enable UVM port checking with +UVM_PORT_CHECKUnconnected required ports cause a clean fatal at start_of_simulation; better to catch before simulation starts than during run

§14 — Summary

AspectPush Model (put)Pull Model (get)
Who initiatesProducer (monitor) calls put(txn)Consumer (driver) calls get(txn)
Data flowSame direction as control — initiator to responderOpposite to control — data returns from responder to initiator
Blocking behaviourProducer blocks until consumer's put() returnsConsumer blocks until producer provides data
Port type (initiator)uvm_blocking_put_port #(T)uvm_blocking_get_port #(T)
Imp type (responder)uvm_blocking_put_imp #(T, comp)uvm_blocking_get_imp #(T, comp)
Method implementedtask put(T txn)task get(output T txn)
Canonical UVM useMonitor → Scoreboard, Producer → ConsumerDriver → Sequencer (via seq_item_port)
Real-world alternativeuvm_analysis_port for monitor output (fan-out, non-blocking)seq_item_port / get_next_item() is the standard driver pattern