Verification

UVM TLM FIFO and analysis FIFO

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: uvm_tlm_fifo and uvm_tlm_analysis_fifo are bounded, thread-safe queues that let a fast producer and a slower consumer run at their own pace without hand-written synchronization, which is why they are the backbone of nearly every UVM scoreboard.

    In a well-built UVM environment, monitors broadcast transactions the moment they see them, but the checker that compares expected against actual rarely wants data at that exact instant. The TLM FIFO is the piece that absorbs this mismatch. It is one of the most common and most interview-relevant constructs in SystemVerilog verification, yet many engineers only ever use uvm_tlm_analysis_fifo by reflex without understanding its ports, its blocking semantics, or when a plain uvm_tlm_fifo is the better choice.

    Why decouple producers and consumers at all?

    A monitor on a 2 GHz AXI interface can produce a read-response transaction every few clock cycles. A reference model that runs a C function or walks a register map might take far longer per item. If you connected them directly with a blocking put/get handshake, the monitor's sampling thread would stall whenever the checker was busy, and you would start dropping real bus activity. The FIFO breaks that coupling: the producer writes into a queue and moves on; the consumer drains it when ready.

    A uvm_tlm_fifo #(T) is created with a depth (default effectively unbounded at 0, or a fixed integer such as 16). A bounded FIFO applies back-pressure: put() blocks when full, which is exactly how you model a real hardware queue's occupancy.

    The two flavors: uvm_tlm_fifo vs uvm_tlm_analysis_fifo

    Both classes extend from the same base and store a mailbox-backed queue, but they expose different input ports:

    Featureuvm_tlm_fifouvm_tlm_analysis_fifo
    Input portput_export (blocking put)analysis_export (non-blocking write)
    Producer connects viauvm_blocking_put_portuvm_analysis_port
    Behavior when fullProducer blocksNever blocks — unbounded
    Typical producerSequencer / driver, generatorMonitor (broadcast)
    Fan-outOne writerMany (analysis port can feed several FIFOs)

    The key distinction: a monitor's analysis_port must never block, because a monitor models passive observation of a bus and cannot stop time. So you connect it to uvm_tlm_analysis_fifo, whose analysis_export accepts a non-blocking write() and quietly grows as needed. When you want genuine back-pressure between two active components, you use the plain uvm_tlm_fifo with a finite depth.

    Monitor to scoreboard through an analysis FIFO Monitor analysis_port tlm_analysis_fifo analysis_export, unbounded Scoreboard blocking get() write() get() CourseTron · coursetron.com
    A monitor's non-blocking write() fills the analysis FIFO; the scoreboard drains it with a blocking get() on its own thread.

    get, peek, try_get and the FIFO's full API

    Inside the scoreboard you have a choice of retrieval methods, and understanding the difference is where interviews often probe:

    • get(t) — blocking; waits until an item is available, then removes it from the FIFO.
    • peek(t) — blocking; waits for an item but leaves it in place. Useful when a protocol decision depends on the next item before you commit to consuming it.
    • try_get(t) / try_peek(t) — non-blocking; return 1 if data was available, 0 otherwise. These let you poll both an expected-FIFO and an actual-FIFO in one forever loop without deadlocking.
    • can_get(), can_peek(), used(), is_empty(), is_full() — introspection you use to guard comparisons.

    A classic bug: calling blocking get() on two FIFOs sequentially in one thread. If the actual-FIFO is empty but the expected-FIFO has data, the thread blocks on the actual side forever even though a comparison was due. Use try_get() plus @(posedge) pacing, or give each FIFO its own consuming thread with fork...join_none.

    What each call does to the FIFOget(t)beforeABCreturns AafterBCitem removedpeek(t)beforeABCreturns AafterABCitem stays in placetry_get(t)beforereturns 0afterempty: never blocksCourseTron · coursetron.com
    Blocking get() removes the head item, peek() leaves it in place, and try_get() returns 0 on an empty FIFO instead of blocking.

    Building an in-order scoreboard

    The canonical pattern: the reference model writes expected transactions into one uvm_tlm_analysis_fifo, the DUT monitor writes actual transactions into a second one, and a run_phase task loops forever, pulling one from each and calling compare().

    Two FIFOs drained into compare() expected FIFO actual FIFO compare() get() get() Each get() pulls one item per side, then compares CourseTron · coursetron.com
    A packet drains from the FIFO into the comparator every cycle — the FIFO smooths bursty monitor output into a steady consumer rhythm.

    In Synopsys VCS, Cadence Xcelium, and Siemens Questa, this structure behaves identically because TLM FIFOs are pure SystemVerilog library code with no simulator-specific timing — a major reason the UVM reference flow is portable across all three tools. When debugging, remember that an analysis FIFO growing without bound (watch used() climb into the thousands) is a reliable symptom that your consumer thread has died or deadlocked.

    When to reach for a FIFO vs an analysis_imp

    If your scoreboard reacts to each transaction immediately and statelessly, a uvm_analysis_imp with a write() callback is simpler — no FIFO needed. Reach for a FIFO when you must buffer, re-order, or pair streams that arrive at different rates. For out-of-order protocols (tagged AXI, PCIe), you typically combine a FIFO front-end with an associative array keyed on the transaction ID. These patterns scale from a single block test up to full SoC environments of the kind built in professional design-verification service teams.

    For the deeper TLM port/export/imp mechanics underneath all of this, see our companion article on TLM communication in UVM, and for the checker architecture that consumes these FIFOs, read UVM scoreboard patterns.

    Ready to build real scoreboards hands-on? The CourseTron UVM Verification track walks you from a first analysis FIFO to a full out-of-order SoC checker with live simulator labs.

    FAQ

    Is uvm_tlm_analysis_fifo bounded?

    No. Its analysis_export accepts a non-blocking write() that never fails, so the FIFO grows as needed. That is deliberate — a monitor cannot block. If you need back-pressure, use a depth-limited uvm_tlm_fifo instead.

    What is the difference between get and peek?

    Both block until an item is available, but get() removes the item from the FIFO while peek() leaves it in place. Use peek() to inspect the head without committing to consume it.

    Why does my scoreboard deadlock on get()?

    Usually because one thread calls blocking get() on two FIFOs in sequence. If the first is empty the thread stalls even when the second has data. Fix it with try_get() polling or a separate consuming thread per FIFO.

    Can one analysis port feed several FIFOs?

    Yes. A single uvm_analysis_port supports fan-out: connect it to multiple analysis_exports and every write() is broadcast to all of them, which is how you feed a scoreboard and a coverage collector from one monitor.

    Advertisement