In short: TLM (transaction-level modeling) in UVM lets components exchange whole transaction objects through typed ports and exports instead of pin wiggles, so a monitor, scoreboard, or reference model can be wired together with a single connect() call and swapped without touching timing logic.
In a pin-accurate testbench you spend half your life worrying about when a signal changes. UVM's TLM layer throws that away above the driver: components talk in transactions — a uvm_sequence_item carrying address, data, burst length, response — and let the methodology handle the handshake. This is why a well-built UVM environment can route the same stimulus to a DUT and to a C/SystemC reference model in parallel, something that is painful to do at the signal level. If you are preparing for a verification interview, TLM connectivity is one of the three or four topics that comes up every single time.
Ports, exports, and imps — who implements the method?
The single idea that unlocks TLM is this: a port is a caller, an export forwards the call, and an imp actually implements the method. A uvm_blocking_put_port #(txn) declares "I will call put() on someone." A uvm_blocking_put_imp #(txn, my_comp) declares "I provide the put() body." You wire them with port.connect(imp). Every TLM connection is just this: find the side that has the method body, and point everyone else at it.
Rule of thumb: the imp lives in whichever component defines the method body. A monitor has an analysis port (it broadcasts); a scoreboard has an analysis imp (it implements write()). If your connect() fails to compile, you have almost always pointed a port at another port instead of at an imp or export.
Blocking vs non-blocking: the time-consuming question
TLM interfaces come in two flavors. Blocking methods (put, get, peek) are tasks: they can consume simulation time and will suspend the calling thread until the transaction completes. Non-blocking methods (try_put, can_get, try_peek) are functions: they return immediately with a bit telling you whether the operation succeeded, so the caller must poll or react.
| Interface | Method kind | Consumes time? | Typical use |
|---|---|---|---|
put/get/peek | task (blocking) | Yes — waits | Driver pulling items, FIFO full/empty flow control |
try_put/try_get | function (non-blocking) | No — returns bit | Zero-delay models, arbiters that must not stall |
get vs peek | both | — | peek leaves the item in the channel; get removes it |
Choosing wrong is a classic bug: if you call a blocking get() inside a function-context callback, you get an elaboration error because tasks cannot be called from functions. Conversely, using try_get() in a tight loop without a wait can spin the simulator. See how this interacts with sequencer handshaking in our UVM driver and monitor walkthrough.
The analysis port — one-to-many broadcast
The put/get family is strictly point-to-point. For monitoring you want the opposite: one monitor observing the bus, feeding a scoreboard, a coverage collector, and maybe a logger simultaneously. That is the uvm_analysis_port. It is non-blocking by nature, calls a write(txn) on every subscriber, and crucially does not care if zero subscribers are connected — so a monitor never stalls waiting for a listener.
class my_mon extends uvm_monitor;
uvm_analysis_port #(bus_txn) ap;
function void build_phase(uvm_phase phase);
ap = new("ap", this);
endfunction
task run_phase(uvm_phase phase);
forever begin
bus_txn t = collect(); // sample the pins
ap.write(t); // broadcast to all subscribers
end
endtask
endclass
Subscribers extend uvm_subscriber #(bus_txn) (which carries a built-in analysis_imp and forces you to define write()), or use uvm_analysis_imp directly. One monitor port can fan out to as many as you connect — in Synopsys VCS and Cadence Xcelium UVM libraries this is implemented as an internal list that is simply iterated on each write().
ap.write(txn) fans the same transaction out to three independent subscribers in zero time.TLM FIFOs and when to reach for them
When a producer and consumer run at different rates, a uvm_tlm_fifo #(txn) buffers between them. The producer connects its put_port to the FIFO's put_export; the consumer connects its get_port to the FIFO's get_export. Give it a depth of 0 for an unbounded queue, or a finite size to model real backpressure — a blocking put() into a full FIFO suspends until a get() frees a slot, which is exactly how you model a 16-entry hardware queue.
For analysis streams that must be buffered (e.g. a scoreboard that drains slower than the monitor produces), use uvm_tlm_analysis_fifo — it has a write() imp on the input side and a blocking get on the output, so the monitor stays non-blocking while the scoreboard pulls at its own pace.
A worked connection in connect_phase
function void env::connect_phase(uvm_phase phase); // monitor broadcasts to scoreboard agent.mon.ap.connect(sb.analysis_export); // driver gets items from sequencer (seq_item_port <-> seq_item_export) drv.seq_item_port.connect(sqr.seq_item_export); // rate-matched path through a fifo prod.put_port.connect(fifo.put_export); cons.get_port.connect(fifo.get_export); endfunction
Everything happens in connect_phase, which runs top-down after build_phase has constructed the hierarchy. The direction is always initiator.port.connect(target.export_or_imp). Teams building reusable VIP — the kind of work done at design-services shops like Avecas — lean on this so the same agent drops into AXI, APB, or custom-protocol environments with only the sequence item changing.
Common interview traps
- Port vs export vs imp confusion — the imp is where the method body lives; only one imp per method.
- Calling a blocking task from a function —
write()is a function, so you cannot callget()inside it; buffer through an analysis FIFO instead. - Forgetting analysis ports tolerate zero subscribers — a monitor with an unconnected
apstill runs; aput_portwith no export connected throws a fatal atconnect. - Depth semantics —
uvm_tlm_fifodepth 0 means unbounded, not empty.
Want to wire a complete AXI agent with monitor, scoreboard, and TLM FIFOs — and debug the connect errors live in VCS/Xcelium? Build it hands-on in CourseTron's UVM Verification track, or sharpen fundamentals first at our training partner ChipXpert.
FAQ
What is the difference between a TLM port and an export?
A port is the caller side that invokes a method like put() or get(); an export forwards that call toward the component that actually implements it (the imp). You connect a port to an export or directly to an imp.
Is an analysis port blocking or non-blocking?
Non-blocking. uvm_analysis_port::write() is a function that returns immediately and broadcasts to every connected subscriber. It never suspends and works even with zero subscribers connected.
When should I use a uvm_tlm_fifo instead of a direct connection?
Use a FIFO when producer and consumer run at different rates or when you want to model hardware buffering and backpressure. A finite-depth FIFO blocks the producer's put() when full, modeling a real queue.
Why does my connect_phase give a "cannot find implementation" error?
You almost certainly connected a port to another port rather than to an export or imp. Trace the chain until you reach the component that defines the method body, and connect toward it.