The TLM FIFO
The previous two pages solved rate-mismatch two different ways: block and wait (simple, but stalls the caller), or poll with try_put()/can_put() (never stalls, but the caller has to actively manage retrying). uvm_tlm_fifo offers a third option — a small buffer sitting between producer and consumer, so a fast producer can keep going even while a slow consumer is still catching up, without either side needing retry logic of its own.
Buffering between a fast monitor and a slow logger
Revisiting fifo_write_logger from the blocking put/get page — instead of the monitor blocking on every single write while the logger's #20 delay runs, a uvm_tlm_fifo absorbs a burst of writes and lets the logger drain them at its own pace:
class fifo_env extends uvm_env;
// ... agent, scoreboard as before ...
uvm_tlm_fifo #(fifo_txn) log_fifo;
fifo_write_logger logger;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// ...
log_fifo = new("log_fifo", this, 4); // depth 4 — buffers up to 4 pending writes
logger = fifo_write_logger::type_id::create("logger", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// ...
agent.monitor.log_port.connect(log_fifo.put_export);
logger.get_port.connect(log_fifo.get_export);
endfunction
endclass
- The depth is a real constructor argument —
new("log_fifo", this, 4)— not just "unbounded by default." A depth of0(or omitted) means unbounded; a concrete number like4gives the FIFO a genuine capacity, after which a blockingput()into it stalls the same wayfifo_write_logger::put()did directly, just with several writes' worth of slack absorbed first. put_export/get_export, not ports — the FIFO is the middle link in the chain, so it exposes exports (things a port connects into) on both ends rather than ports of its own.agent.monitor.log_port(a port) connects tolog_fifo.put_export;logger.get_port(a port on the consumer side) connects tolog_fifo.get_export.- The monitor's
log_port.put(txn)call from two pages ago doesn't change at all — it still callsput(), exactly as before. It's now just talking to the FIFO's export instead of the logger's imp directly, and the FIFO is what decides whether that call blocks (buffer full) or returns immediately (room available). is_full(), callable on the FIFO directly, lets a producer check remaining capacity without attempting a put — useful inside arun_phasethat wants to poll rather than risk blocking at all:if (!log_fifo.is_full()) log_port.put(txn);.put_ap/get_ap— everyuvm_tlm_fifoalso carries two built-inuvm_analysis_ports of its own, firing automatically whenever an item is put into the FIFO (put_ap) or retrieved from it (get_ap) — no extra wiring in the producer or consumer required. A coverage collector that wants to observelog_fifo's traffic can simply.connect()tolog_fifo.get_ap(orput_ap) directly, the same one-to-many broadcast TLM Basics & Analysis Ports covers, without eitherfifo_monitororfifo_write_loggerneeding to know it's being observed.
Three more query/reset methods, and a specialized variant
Besides is_full(), uvm_tlm_fifo exposes a few more zero-time query methods worth knowing:
is_empty()— returns 1 when there are no entries queued, the mirror image ofis_full().used()— returns the current number of entries actually in the FIFO, useful for logging or asserting the buffer never grew past an expected watermark.flush()— discards every entry currently queued, after whichused()returns 0 andis_empty()returns 1. This is a hard reset of the buffer's contents, not something reached for during ordinary operation — clearing stale data after a mid-test reset is the usual reason.
uvm_tlm_analysis_fifo is a specialized variant built for a different connection shape than everything above: instead of exposing a put_export a port connects into, it implements the analysis write() interface directly — so it can sit anywhere a uvm_analysis_imp would, receiving broadcast traffic from a uvm_analysis_port the same one-to-many way TLM Basics & Analysis Ports described, while still letting a consumer get()/peek() it like an ordinary FIFO. It's also unbounded by default, matching how analysis broadcast is meant to never block the producer. This combination — analysis-style write() in, FIFO-style get() out — is exactly why it shows up constantly in scoreboards: a monitor's analysis port connects straight to it, and the scoreboard drains it at its own pace without either side needing the depth-argument/export-wiring dance the plain uvm_tlm_fifo above requires.
What a FIFO changes, and what it doesn't
Nothing about fifo_write_logger::put() or fifo_monitor::run_phase needed to change to introduce this buffer — only the environment's connect_phase and build_phase changed, which is the same "only the environment has to know" property TLM has provided throughout this section. What does change is timing: the monitor can now push several writes in a tight sequence without stalling on the logger's #20 delay each time, at the cost of introducing a bounded amount of slack between when a write is observed and when it's actually logged — worth being deliberate about when a scoreboard or coverage collector's timing assumptions depend on immediacy.
What's next
Ports, exports, imps, and now a FIFO between them — the final TLM page covers sockets, which fuse port and export into a single object, and the _decl macro for the specific case of one component needing more than one imp of the same transaction type.