Skip to content

uvm_tlm_fifo

FIFO architecture, producer-consumer decoupling, uvm_tlm_analysis_fifo, sizing, complete patterns.

UVM Fundamentals · Module 14

Why Buffering Is Needed

TLM 1.0 put() is blocking: the producer waits until the consumer is ready. Analysis write() is non-blocking but cannot guarantee the consumer has processed the transaction. Neither handles a mismatch in production rate between producer and consumer.

ScenarioProblem Without FIFOFIFO Solution
Monitor fires fast, scoreboard checks slowlyWith put(): monitor stalls waiting for scoreboard. With write(): scoreboard may miss items if it hasn't finished the previous one.Monitor puts into FIFO instantly. Scoreboard pulls when ready. Buffer absorbs the burst.
Multiple producers, one consumerEach producer blocks waiting for the consumer — they cannot produce in parallel.Each producer puts into its own FIFO. Consumer pulls from each in round-robin.
Decoupling analysis from processingAnalysis write() is synchronous — heavy scoreboard logic runs inline during write(), delaying the monitor.write() puts into FIFO (instant). Scoreboard task pulls and processes asynchronously.

uvm_tlm_fifo Architecture — Built-In Ports

uvm_tlm_fifo #(T) is a uvm_component with built-in TLM ports on both sides. You do not implement any methods — you just connect your components to the FIFO's existing ports. PRODUCER(monitor / driver)put_portput(txn)uvm_tlm_fifo #(T)put_exportget_peek_exportInternal SystemVerilog Queue[ txn_0 | txn_1 | txn_2 | ... | txn_N ]size: 0 = unlimited · FIFO order · thread-safeput_apget_apget(txn)CONSUMER(scoreboard / checker)get_portfires on putfires on get Figure 1 — uvm_tlm_fifo internal structure. The producer connects its put_port to put_export. The consumer connects its get_port to get_peek_export. put_ap and get_ap broadcast notifications on each operation.

Port / ExportTypeConnected ByPurpose
put_exportuvm_put_exportProducer's put_portReceives put() calls and adds to internal queue
get_peek_exportuvm_get_peek_exportConsumer's get_portReturns items from queue on get() / peek() calls
put_apuvm_analysis_portOptional — subscribe for notificationsBroadcasts write(txn) every time an item enters the FIFO
get_apuvm_analysis_portOptional — subscribe for notificationsBroadcasts write(txn) every time an item leaves the FIFO

The Producer-Consumer Pattern — Full Working Example

SystemVerilog — producer, FIFO, consumer complete wiring
// ── Transaction type ──────────────────────────────────────────────────
class my_txn extends uvm_sequence_item;
`uvm_object_utils(my_txn)
rand bit [7:0] data;
rand bit       parity;
function new(string name = "my_txn"); super.new(name); endfunction
endclass
 
// ── Producer ──────────────────────────────────────────────────────────
class my_producer extends uvm_component;
`uvm_component_utils(my_producer)
uvm_blocking_put_port #(my_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);
phase.raise_objection(this);
repeat (5) begin
my_txn t = my_txn::type_id::create("t");
void'(t.randomize());
`uvm_info("PROD", $sformatf("Putting data=0x%0h", t.data), UVM_LOW)
put_port.put(t);   // blocks if FIFO is full
#10;
end
phase.drop_objection(this);
endtask
endclass
 
// ── Consumer ──────────────────────────────────────────────────────────
class my_consumer extends uvm_component;
`uvm_component_utils(my_consumer)
uvm_blocking_get_port #(my_txn) get_port;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
get_port = new("get_port", this);
endfunction
 
task run_phase(uvm_phase phase);
my_txn t;
forever begin
get_port.get(t);   // blocks until FIFO has an item
#30;               // consumer is 3× slower — FIFO absorbs the difference
`uvm_info("CONS", $sformatf("Got data=0x%0h", t.data), UVM_LOW)
end
endtask
endclass
 
// ── Environment: instantiate FIFO and connect ─────────────────────────
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_producer                   prod;
my_consumer                   cons;
uvm_tlm_fifo #(my_txn)        fifo;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
prod = my_producer::type_id::create("prod", this);
cons = my_consumer::type_id::create("cons", this);
// FIFO: size=4 means max 4 items; size=0 means unlimited
fifo = new("fifo", this, 4);
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
prod.put_port.connect(fifo.put_export);
cons.get_port.connect(fifo.get_peek_export);
endfunction
endclass

uvm_tlm_analysis_fifo — Bridging Analysis and TLM Pull

The uvm_tlm_analysis_fifo is a specialised FIFO that has an analysis imp on its input side and a TLM get_peek_export on its output side. It bridges the fire-and-forget analysis world with the pull-based consumer world.

This is the canonical pattern for connecting a monitor (which uses analysis_port) to a scoreboard that prefers to process transactions one at a time using get().

SystemVerilog — uvm_tlm_analysis_fifo connecting monitor to scoreboard
// ── uvm_tlm_analysis_fifo: has analysis_export (input) + get port (output)
//
//   monitor.analysis_port  ──→  analysis_fifo.analysis_export
//                                (buffers transactions)
//                               analysis_fifo.get_peek_export  ←── scoreboard.get_port
 
class my_env extends uvm_env;
`uvm_component_utils(my_env)
 
apb_monitor                          mon;
my_scoreboard                        scb;
uvm_tlm_analysis_fifo #(apb_txn)    ap_fifo;
 
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);
ap_fifo = new("ap_fifo", this);   // size=0 (unlimited) by default
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Monitor's analysis_port writes into the FIFO via its analysis_export
mon.analysis_port.connect(ap_fifo.analysis_export);
// Scoreboard pulls from the FIFO at its own pace
scb.get_port.connect(ap_fifo.get_peek_export);
endfunction
endclass
 
// ── Scoreboard now uses get_port instead of analysis_imp ──────────────
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
uvm_blocking_get_port #(apb_txn) 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_txn txn;
forever begin
get_port.get(txn);        // blocking — waits if FIFO is empty
check_transaction(txn);   // can take as long as needed
end
endtask
task check_transaction(apb_txn txn); /* ... */ endtask
endclass

FIFO Sizing and the Control API

SystemVerilog — FIFO sizing, query, and control methods
// ── Construction with size ────────────────────────────────────────────
// new(name, parent, size)
//   size = 0  : UNLIMITED — never blocks the producer on put()
//   size = N  : bounded — put() blocks when N items are already buffered
 
uvm_tlm_fifo #(my_txn) fifo_unlimited = new("fifo", this, 0);   // default: unlimited
uvm_tlm_fifo #(my_txn) fifo_bounded   = new("fifo", this, 8);   // max 8 items
 
// ── Query methods (call anywhere after build) ─────────────────────────
int  sz  = fifo.size();       // configured capacity (0 = unlimited)
int  used = fifo.used();      // current number of items in the FIFO
bit  emp = fifo.is_empty();   // 1 if no items
bit  ful = fifo.is_full();    // 1 if at capacity (always 0 for unlimited)
 
// ── flush() — drain all items ─────────────────────────────────────────
fifo.flush();   // removes all items — useful in reset sequences
 
// ── Monitoring FIFO depth during simulation ───────────────────────────
function void check_phase(uvm_phase phase);
if (!ap_fifo.is_empty()) begin
`uvm_error("SCB", $sformatf(
"FIFO has %0d unprocessed transactions at end of simulation",
ap_fifo.used()))
end
endfunction
 
// ── peek() — read without consuming ───────────────────────────────────
uvm_blocking_peek_port #(my_txn) peek_port;
// peek_port.connect(fifo.get_peek_export);
// peek_port.peek(txn); → txn is populated but item stays in FIFO
// Useful for look-ahead logic without consuming the item

Ready-to-Run Simulator Example

Copy the code below into a single file tlm_fifo_demo.sv and run it with the commands shown. It demonstrates a 3× speed mismatch between producer and consumer, buffered by a 4-deep FIFO. Ready to Run — Questa / VCS / Xcelium

SystemVerilog — tlm_fifo_demo.sv (complete, copy and run)
// tlm_fifo_demo.sv — complete ready-to-run UVM TLM FIFO demonstration
// Compile and simulate:
//   Questa : vlog -sv tlm_fifo_demo.sv && vsim -c tlm_fifo_demo_top -do "run -all; quit"
//   VCS    : vcs -sverilog -ntb_opts uvm tlm_fifo_demo.sv && ./simv
//   Xcelium: xrun -sv -uvm tlm_fifo_demo.sv
 
`include "uvm_macros.svh"
import uvm_pkg::*;
 
// ═══════════════════════════════════════════════════════════════════════
//  TRANSACTION
// ═══════════════════════════════════════════════════════════════════════
class pkt extends uvm_sequence_item;
`uvm_object_utils(pkt)
rand bit [7:0] data;
rand bit [3:0] id;
function new(string name = "pkt"); super.new(name); endfunction
function string convert2string();
return $sformatf("id=%0d data=0x%0h", id, data);
endfunction
endclass
 
// ═══════════════════════════════════════════════════════════════════════
//  PRODUCER — generates 8 packets every 10ns
// ═══════════════════════════════════════════════════════════════════════
class fast_producer extends uvm_component;
`uvm_component_utils(fast_producer)
uvm_blocking_put_port #(pkt) put_port;
int num_pkts = 8;
 
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);
pkt p;
phase.raise_objection(this);
for (int i = 0; i < num_pkts; i++) begin
p    = pkt::type_id::create($sformatf("p%0d", i));
p.id   = i;
p.data = $urandom_range(0, 255);
`uvm_info("PROD", $sformatf("PUT  @ %0t → %s", $time, p.convert2string()), UVM_LOW)
put_port.put(p);   // will block if FIFO is full (size=4)
#10;
end
phase.drop_objection(this);
endtask
endclass
 
// ═══════════════════════════════════════════════════════════════════════
//  CONSUMER — processes one packet every 30ns (3× slower)
// ═══════════════════════════════════════════════════════════════════════
class slow_consumer extends uvm_component;
`uvm_component_utils(slow_consumer)
uvm_blocking_get_port #(pkt) get_port;
int processed = 0;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
get_port = new("get_port", this);
endfunction
 
task run_phase(uvm_phase phase);
pkt p;
forever begin
get_port.get(p);   // blocks until FIFO has something
#30;               // simulate slow processing
processed++;
`uvm_info("CONS", $sformatf("GOT  @ %0t → %s  [total=%0d]",
$time, p.convert2string(), processed), UVM_LOW)
end
endtask
endclass
 
// ═══════════════════════════════════════════════════════════════════════
//  ENVIRONMENT
// ═══════════════════════════════════════════════════════════════════════
class demo_env extends uvm_env;
`uvm_component_utils(demo_env)
fast_producer              prod;
slow_consumer              cons;
uvm_tlm_fifo #(pkt)        fifo;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
prod = fast_producer::type_id::create("prod", this);
cons = slow_consumer::type_id::create("cons", this);
fifo = new("fifo", this, 4);   // capacity = 4 packets
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
prod.put_port.connect(fifo.put_export);
cons.get_port.connect(fifo.get_peek_export);
endfunction
 
function void check_phase(uvm_phase phase);
if (fifo.used() != 0)
`uvm_error("ENV", $sformatf("FIFO not empty at end: %0d items remain", fifo.used()))
else
`uvm_info("ENV", "All packets processed — FIFO empty ✓", UVM_LOW)
endfunction
endclass
 
// ═══════════════════════════════════════════════════════════════════════
//  TEST
// ═══════════════════════════════════════════════════════════════════════
class fifo_demo_test extends uvm_test;
`uvm_component_utils(fifo_demo_test)
demo_env env;
 
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = demo_env::type_id::create("env", this);
endfunction
 
function void end_of_elaboration_phase(uvm_phase phase);
`uvm_info("TEST", "Producer: 10ns/pkt   Consumer: 30ns/pkt   FIFO size: 4", UVM_LOW)
endfunction
endclass
 
// ═══════════════════════════════════════════════════════════════════════
//  TOP MODULE
// ═══════════════════════════════════════════════════════════════════════
module tlm_fifo_demo_top;
initial run_test("fifo_demo_test");
endmodule
 
// ═══════════════════════════════════════════════════════════════════════
//  EXPECTED OUTPUT (abridged):
//
//  UVM_INFO TEST: Producer: 10ns/pkt   Consumer: 30ns/pkt   FIFO size: 4
//  UVM_INFO PROD: PUT  @ 0 → id=0 data=0xA2
//  UVM_INFO PROD: PUT  @ 10 → id=1 data=0x3F
//  UVM_INFO PROD: PUT  @ 20 → id=2 data=0xC8
//  UVM_INFO PROD: PUT  @ 30 → id=3 data=0x15    ← FIFO fills; producer now stalls
//  UVM_INFO CONS: GOT  @ 30 → id=0 data=0xA2  [total=1]
//  UVM_INFO PROD: PUT  @ 30 → id=4 data=0xE1   ← producer unblocked
//  UVM_INFO CONS: GOT  @ 60 → id=1 data=0x3F  [total=2]
//  ... (consumer drains FIFO after producer finishes)
//  UVM_INFO ENV:  All packets processed — FIFO empty ✓
// ═══════════════════════════════════════════════════════════════════════

Common Patterns and Pitfalls

Pattern: Dual-FIFO Scoreboard (Request + Response)

SystemVerilog — dual analysis_fifo: separate req and rsp paths
// ── Scoreboard receives separately from two monitors ──────────────────
class dual_fifo_scoreboard extends uvm_scoreboard;
`uvm_component_utils(dual_fifo_scoreboard)
 
uvm_blocking_get_port #(apb_req_txn) req_get_port;
uvm_blocking_get_port #(apb_rsp_txn) rsp_get_port;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
req_get_port = new("req_get_port", this);
rsp_get_port = new("rsp_get_port", this);
endfunction
 
task run_phase(uvm_phase phase);
apb_req_txn req;
apb_rsp_txn rsp;
forever begin
req_get_port.get(req);   // get from req FIFO
rsp_get_port.get(rsp);   // get matching response
compare(req, rsp);
end
endtask
task compare(apb_req_txn req, apb_rsp_txn rsp); /* ... */ endtask
endclass
 
// ── Environment wiring ────────────────────────────────────────────────
uvm_tlm_analysis_fifo #(apb_req_txn) req_fifo;
uvm_tlm_analysis_fifo #(apb_rsp_txn) rsp_fifo;
// connect_phase:
req_mon.ap.connect(req_fifo.analysis_export);
rsp_mon.ap.connect(rsp_fifo.analysis_export);
scb.req_get_port.connect(req_fifo.get_peek_export);
scb.rsp_get_port.connect(rsp_fifo.get_peek_export);

Pattern: FIFO with Depth Monitoring

SystemVerilog — monitoring FIFO depth with put_ap / get_ap
// FIFO's put_ap fires on every put() — use it to monitor FIFO depth
class fifo_monitor extends uvm_component;
`uvm_component_utils(fifo_monitor)
uvm_analysis_imp #(my_txn, fifo_monitor) put_imp;
int depth = 0;
int max_depth = 0;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_imp = new("put_imp", this);
endfunction
 
function void write(my_txn t);
depth++;
if (depth > max_depth) max_depth = depth;
`uvm_info("FIFO_MON", $sformatf("depth=%0d max=%0d", depth, max_depth), UVM_HIGH)
endfunction
endclass
 
// Connect: fifo.put_ap.connect(fifo_mon.put_imp);
// fifo.get_ap.connect(fifo_mon.get_imp); // separate imp for get side
 
// ── Common Pitfalls ───────────────────────────────────────────────────
//
// PITFALL 1: Using flush() during run_phase while consumer is waiting
//   → Consumer's get() may hang indefinitely after flush()
//   Fix: Only flush() when you know the consumer is not blocked in get()
//
// PITFALL 2: Bounded FIFO + fast producer causes deadlock
//   → Producer's put() blocks waiting for consumer to get()
//   → If consumer never runs (wrong phasing), simulation hangs
//   Fix: Use size=0 (unlimited) unless you specifically need backpressure
//
// PITFALL 3: Forgetting to check is_empty() in check_phase
//   → Missed transactions left in FIFO — silent failure
//   Fix: Always check fifo.is_empty() in check_phase and error if not

Quick Reference

TaskCall / Declaration
Create unlimited FIFOuvm_tlm_fifo #(T) fifo = new("fifo", this, 0)
Create bounded FIFO (N items)uvm_tlm_fifo #(T) fifo = new("fifo", this, N)
Connect producerprod.put_port.connect(fifo.put_export)
Connect consumercons.get_port.connect(fifo.get_peek_export)
Analysis FIFO (monitor → scoreboard)uvm_tlm_analysis_fifo #(T)mon.ap.connect(af.analysis_export)scb.get_port.connect(af.get_peek_export)
Get item countfifo.used()
Check empty / fullfifo.is_empty() / fifo.is_full()
Flush all itemsfifo.flush()
Monitor put/get eventsfifo.put_ap.connect(mon_imp) / fifo.get_ap.connect(mon_imp)
Shell — compile and run commands for all major simulators
## ── Questa / ModelSim ────────────────────────────────────────────────
vlog -sv -timescale 1ns/1ps tlm_fifo_demo.sv
vsim -c tlm_fifo_demo_top -do "run -all; quit -f"
 
## With verbosity control:
vsim -c tlm_fifo_demo_top +UVM_VERBOSITY=UVM_LOW -do "run -all; quit -f"
 
## ── VCS ───────────────────────────────────────────────────────────────
vcs -sverilog -ntb_opts uvm -timescale=1ns/1ps tlm_fifo_demo.sv -o simv
./simv +UVM_TESTNAME=fifo_demo_test +UVM_VERBOSITY=UVM_LOW
 
## ── Xcelium ───────────────────────────────────────────────────────────
xrun -sv -uvm -timescale 1ns/1ps tlm_fifo_demo.sv \
-input "run; exit" \
+UVM_TESTNAME=fifo_demo_test +UVM_VERBOSITY=UVM_LOW
 
## ── Expected key output lines: ───────────────────────────────────────
## UVM_INFO PROD: PUT  @ 0 → id=0 data=0x??
## UVM_INFO PROD: PUT  @ 10 → id=1 data=0x??
## UVM_INFO PROD: PUT  @ 20 → id=2 data=0x??
## UVM_INFO PROD: PUT  @ 30 → id=3 data=0x??   ← FIFO full (4 items)
## UVM_INFO CONS: GOT  @ 30 → id=0 ...          ← consumer unblocks producer
## ... (all 8 packets processed)
## UVM_INFO ENV:  All packets processed — FIFO empty ✓
## UVM_INFO @ 0: UVM_ERROR :   0   UVM_FATAL :   0

§9 — Code Examples

Example 1 — Beginner: Backpressure — Bounded FIFO Slowing the Producer

The most important FIFO behaviour to understand first: when the FIFO is full, the producer's put() blocks. This is backpressure — the consumer controls how fast the producer runs. The simulation timeline shows this clearly.

SystemVerilog — backpressure: bounded FIFO stalls the producer
// ── Transaction ────────────────────────────────────────────────────────
class data_pkt extends uvm_sequence_item;
`uvm_object_utils(data_pkt)
int id;
function new(string n="data_pkt"); super.new(n); endfunction
endclass
 
// ── Producer: puts a packet every 10ns ────────────────────────────────
task run_phase(uvm_phase phase);
data_pkt p;
phase.raise_objection(this);
for (int i = 0; i < 6; i++) begin
p    = data_pkt::type_id::create($sformatf("p%0d",i));
p.id = i;
`uvm_info("PROD", $sformatf("Attempting put(%0d) @ %0t", i, $time), UVM_LOW)
put_port.put(p);   // BLOCKS if FIFO has 2 items (size=2)
`uvm_info("PROD", $sformatf("put(%0d) returned @ %0t", i, $time), UVM_LOW)
#10;
end
phase.drop_objection(this);
endtask
 
// ── Consumer: gets one packet every 40ns (4× slower) ─────────────────
task run_phase(uvm_phase phase);
data_pkt p;
forever begin
get_port.get(p);  // waits for item
#40;              // slow processing
`uvm_info("CONS", $sformatf("Processed(%0d) @ %0t", p.id, $time), UVM_LOW)
end
endtask
 
// ── FIFO: size=2 — forces backpressure ───────────────────────────────
uvm_tlm_fifo#(data_pkt) fifo = new("fifo", this, 2);  // max 2 items
 
// Simulation timeline (FIFO size=2, producer 10ns, consumer 40ns):
// @  0: put(0) → FIFO=[0]     — FIFO has 1 item, put returns immediately
// @  0: put(1) → FIFO=[0,1]   — FIFO has 2 items (FULL), put returns
// @ 10: put(2) → FIFO=[0,1,?] — FIFO FULL! put() BLOCKS here
// @ 40: Cons gets(0) → FIFO=[1] — put(2) unblocked! FIFO=[1,2]
// @ 50: put(3) returns immediately (FIFO had space)
// Consumer controls producer throughput — backpressure in action

Example 2 — Intermediate: Analysis FIFO Decoupling Monitor from Scoreboard

The canonical production pattern. Monitor uses analysis_port (fire-and-forget). Scoreboard uses get_port (pull-when-ready). The analysis FIFO bridges them.

SystemVerilog — analysis_fifo: monitor writes, scoreboard pulls
// ── Monitor: uses standard analysis_port — unchanged ──────────────────
class apb_monitor extends uvm_monitor;
uvm_analysis_port#(apb_txn) ap;
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
ap = new("ap", this);
endfunction
 
task run_phase(uvm_phase phase);
apb_txn txn;
forever begin
@(posedge vif.clk iff vif.psel);
txn      = apb_txn::type_id::create("txn");
txn.addr = vif.paddr;
txn.data = vif.pwdata;
ap.write(txn);   // non-blocking — into the FIFO instantly
end
endtask
virtual apb_if vif;
endclass
 
// ── Scoreboard: uses get_port — can take as long as needed per item ────
class apb_scoreboard extends uvm_scoreboard;
uvm_blocking_get_port#(apb_txn) 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_txn txn;
forever begin
get_port.get(txn);       // waits if FIFO is empty
do_reference_model(txn);  // can be slow — doesn't affect monitor
compare_and_report(txn);
end
endtask
 
task do_reference_model(apb_txn txn); #5; endtask  // expensive
task compare_and_report(apb_txn txn); /* ... */ endtask
endclass
 
// ── Env: the FIFO is the glue ──────────────────────────────────────────
class apb_env extends uvm_env;
apb_monitor                         mon;
apb_scoreboard                      scb;
uvm_tlm_analysis_fifo#(apb_txn)   af;  // the bridge
 
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);
af  = new("af", this);   // size=0: unlimited — monitor never stalls
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.ap.connect(af.analysis_export);     // analysis → FIFO input
scb.get_port.connect(af.get_peek_export); // FIFO output → scoreboard
endfunction
 
function void check_phase(uvm_phase phase);
if (af.used() != 0)
`uvm_error("ENV", $sformatf(
"Analysis FIFO has %0d unprocessed items at test end", af.used()))
endfunction
endclass

Example 3 — Verification: peek() Before get() for Order-Sensitive Checking

peek() reads the next item without removing it. Useful when you need to inspect the next transaction before deciding whether to consume it — for example, in an out-of-order protocol where you need to scan for a specific ID.

SystemVerilog — peek() for non-destructive inspection
// ── Scoreboard with peek() for look-ahead ─────────────────────────────
class reorder_scoreboard extends uvm_scoreboard;
`uvm_component_utils(reorder_scoreboard)
 
uvm_blocking_get_port#(axi_txn)  get_port;
uvm_blocking_peek_port#(axi_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
 
// connect_phase:
//   get_port.connect(fifo.get_peek_export)
//   peek_port.connect(fifo.get_peek_export)  ← same export serves both
 
task run_phase(uvm_phase phase);
axi_txn  head;
axi_txn  next;
int      expected_id = 0;
forever begin
// Look at the next item WITHOUT consuming it
peek_port.peek(head);
 
if (head.txn_id == expected_id) begin
// It's what we want — consume and process it
get_port.get(next);
process(next);
expected_id++;
end else begin
// Not what we want yet — wait for the DUT to send the right one
`uvm_info("SCB", $sformatf(
"Waiting: head=%0d, want=%0d", head.txn_id, expected_id),
UVM_HIGH)
#10;   // wait and check again
end
end
endtask
task process(axi_txn t); /* ... */ endtask
endclass
 
// Note: get_peek_export handles both get() and peek() from the same FIFO.
// peek() reads the head without advancing the FIFO pointer.
// A subsequent get() then removes that same item.
// Never call peek() and get() on the same item from different threads — race.

Example 4 — Tricky: flush() Mid-Test Reset Sequence

When a DUT reset fires during a test, the FIFO may hold stale transactions that should be discarded. flush() clears the FIFO — but if the consumer is blocking inside get(), it will hang forever after the flush.

SystemVerilog — flush() during reset and the consumer wake-up pattern
// ── Reset handler in the env or test ─────────────────────────────────
task handle_reset();
`uvm_info("ENV", "Reset detected — flushing FIFO", UVM_LOW)
 
// PROBLEM: if scoreboard is blocking inside get_port.get()
// and FIFO becomes empty after flush(), scoreboard hangs forever
 
// ✓ Safe pattern: signal the scoreboard to stop before flushing
scb.handle_reset();    // scoreboard exits its get() loop via a flag
@(posedge vif.clk);    // wait one clock for scoreboard to unblock
af.flush();            // now safe to flush
 
`uvm_info("ENV", $sformatf("FIFO flushed: %0d items discarded",
af.used()), UVM_LOW)    // used() = 0 after flush
endtask
 
// ── Scoreboard with reset-aware get() loop ────────────────────────────
class reset_aware_scoreboard extends uvm_scoreboard;
bit in_reset = 0;
 
function void handle_reset();
in_reset = 1;   // signal to exit get() loop
endfunction
 
task run_phase(uvm_phase phase);
apb_txn txn;
forever begin
if (in_reset) begin
in_reset = 0;
`uvm_info("SCB", "Reset acknowledged — restarting", UVM_LOW)
continue;
end
// Non-blocking try_get() allows the loop to check in_reset
if (!get_port.try_get(txn)) begin
#1;   // poll — not ideal but avoids blocking
continue;
end
process(txn);
end
endtask
task process(apb_txn t); /* ... */ endtask
endclass
 
// Alternative: use try_get() instead of get() across all scoreboard loops
// to avoid permanent blocking when the FIFO goes unexpectedly empty.

§10 — Bugs & Debugging

Bug 1 — Not Checking FIFO Empty in check_phase — Silent Transaction Loss

⚠️ Production Bug — Test PASSes with Unprocessed Transactions in the FIFO

The scoreboard's run_phase pulls from the analysis FIFO. The test drops its objection. Simulation ends before the scoreboard has processed all items. The remaining transactions sit in the FIFO untouched — they're never checked. The test reports PASS. The DUT sent 100 transactions, only 97 were verified. Without a check_phase guard, this goes undetected for weeks.

SystemVerilog — missing FIFO drain check and the fix
// ❌ WRONG — no check for unprocessed items ───────────────────────────
class bad_env extends uvm_env;
uvm_tlm_analysis_fifo#(apb_txn) af;
// check_phase: nothing here → FIFO may have items at simulation end
endclass
// Test ends: UVM_ERROR = 0 → CI reports PASS
// But af.used() = 3 → 3 unverified transactions silently lost
 
// ✓ CORRECT — always check FIFO in check_phase ────────────────────────
class good_env extends uvm_env;
uvm_tlm_analysis_fifo#(apb_txn) af;
 
function void check_phase(uvm_phase phase);
if (af.used() != 0) begin
`uvm_error("ENV", $sformatf(
"%0d unprocessed transactions in analysis FIFO at test end."
" Scoreboard may be too slow or dropped its loop early.",
af.used()))
end else
`uvm_info("ENV", "Analysis FIFO drained — all transactions verified", UVM_NONE)
endfunction
endclass
 
// Also add a timeout guard in the scoreboard's run_phase:
// Use a fork-join_any with a time limit to detect stuck consumers.

Bug 2 — Bounded FIFO Causing Simulation Hang (Deadlock)

⚠️ Production Bug — Simulation Hangs at UVM_TIMEOUT with No Useful Stack

A 4-item bounded FIFO fills up. The producer's put() blocks, waiting for the consumer to get(). But the consumer only starts processing in a second phase event or after an external trigger that never fires. The simulation hangs until UVM_TIMEOUT. The log shows the producer's last successful put() but nothing after — it's stuck inside put(), waiting for space that never comes.

SystemVerilog — bounded FIFO deadlock and size=0 fix
// ❌ DEADLOCK — bounded FIFO, consumer starts late ─────────────────────
// env.build_phase:
uvm_tlm_fifo#(my_txn) fifo = new("fifo", this, 4);  // size=4
 
// Producer runs immediately in run_phase at T=0
// Puts items 0,1,2,3 → FIFO FULL at T=0
// Producer blocks on put(item_4)
//
// Consumer triggered by DUT interrupt — which never fires because
// the DUT is also waiting for the producer to drive more transactions.
// DEADLOCK: producer waits for consumer, consumer waits for DUT,
// DUT waits for producer. Simulation hangs.
 
// ✓ FIX — size=0 for analysis FIFOs and most verification uses ────────
uvm_tlm_analysis_fifo#(my_txn) af = new("af", this);
// size=0 (default): unlimited depth → producer NEVER blocks
// Memory usage grows if consumer is perpetually slower, but deadlock impossible
// Use bounded size ONLY when you intentionally want backpressure testing
 
// Diagnosis: if simulation hangs, enable +UVM_OBJECTION_TRACE and look for
// which component is still holding an objection.
// Then grep the log for the last PROD PUT line — the stuck producer.
// Check the FIFO depth with a periodic monitoring task.

Bug 3 — Consumer get() Loop Has No Drain — Test Ends Early

SystemVerilog — scoreboard-side objection for complete FIFO drain
// ✓ Scoreboard raises its own objection and drops it only when FIFO is empty
class draining_scoreboard extends uvm_scoreboard;
uvm_blocking_get_port#(apb_txn) get_port;
uvm_tlm_analysis_fifo#(apb_txn)* af;  // handle to the env's FIFO
 
task run_phase(uvm_phase phase);
apb_txn txn;
phase.raise_objection(this);  // SCB holds objection
forever begin
get_port.get(txn);   // blocks if FIFO empty
process(txn);
// When producer drops its objection, the FIFO will drain.
// Once FIFO is empty AND no more items expected, drop ours.
if (af.is_empty() && producer_done()) break;
end
phase.drop_objection(this);  // only drop when truly done
endtask
 
function bit producer_done();
// Check if the test's objection count has dropped to zero
return (uvm_root::get().get_objection_count(UVM_PHASE_OBJECTION) == 1);
// 1 = only our own objection remains
endfunction
task process(apb_txn t); /* ... */ endtask
endclass

§11 — Ready-to-Run: Analysis FIFO Bridge Demo

A complete, self-contained demo showing uvm_tlm_analysis_fifo bridging a fast producer (analysis write) to a slow consumer (blocking get). The check_phase verifies the FIFO is drained. Run this and observe the timing difference. Ready to Run — Questa / VCS / Xcelium

analysis_fifo_demo.sv — compile and run
// analysis_fifo_demo.sv
// Compile: vlog -sv analysis_fifo_demo.sv
// Run:     vsim -c work.tb_af_top +UVM_TESTNAME=af_demo_test -do "run -all; quit"
 
`include "uvm_macros.svh"
import uvm_pkg::*;
 
class af_txn extends uvm_sequence_item;
`uvm_object_utils(af_txn)
int id;
int data;
function new(string n="af_txn"); super.new(n); endfunction
endclass
 
// ── Fast publisher: writes via analysis_port every 5ns ────────────────
class fast_pub extends uvm_component;
`uvm_component_utils(fast_pub)
uvm_analysis_port#(af_txn) ap;
 
function new(string n, uvm_component p); super.new(n,p); endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
ap = new("ap", this);
endfunction
 
task run_phase(uvm_phase phase);
af_txn t;
phase.raise_objection(this);
for (int i=0; i<6; i++) begin
t      = af_txn::type_id::create($sformatf("t%0d",i));
t.id   = i;
t.data = (i+1) * 16;
`uvm_info("PUB", $sformatf("write(%0d) @ %0t", i, $time), UVM_LOW)
ap.write(t);   // non-blocking — into FIFO instantly
#5;
end
phase.drop_objection(this);  // publisher done — 6 items in FIFO
endtask
endclass
 
// ── Slow subscriber: pulls from FIFO every 20ns ────────────────────────
class slow_sub extends uvm_component;
`uvm_component_utils(slow_sub)
uvm_blocking_get_port#(af_txn) get_port;
int total = 0;
 
function new(string n, uvm_component p); super.new(n,p); endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
get_port = new("get_port", this);
endfunction
 
task run_phase(uvm_phase phase);
af_txn t;
phase.raise_objection(this);  // SUB holds objection until done
repeat (6) begin
get_port.get(t);  // blocks if FIFO empty
#20;              // slow processing — 4× slower than publisher
total++;
`uvm_info("SUB", $sformatf(
"got(%0d) data=%0d @ %0t [%0d/6]",
t.id, t.data, $time, total), UVM_LOW)
end
phase.drop_objection(this);  // drop only after all 6 processed
endtask
endclass
 
// ── Environment ────────────────────────────────────────────────────────
class af_env extends uvm_env;
`uvm_component_utils(af_env)
fast_pub                          pub;
slow_sub                          sub;
uvm_tlm_analysis_fifo#(af_txn)   af;
 
function new(string n, uvm_component p); super.new(n,p); endfunction
 
function void build_phase(uvm_phase phase);
super.build_phase(phase);
pub = fast_pub::type_id::create("pub", this);
sub = slow_sub::type_id::create("sub", this);
af  = new("af", this);   // unlimited depth
endfunction
 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
pub.ap.connect(af.analysis_export);
sub.get_port.connect(af.get_peek_export);
endfunction
 
function void check_phase(uvm_phase phase);
if (af.used() != 0)
`uvm_error("ENV", $sformatf("FIFO not drained: %0d items remain", af.used()))
else
`uvm_info("ENV", "FIFO fully drained. All items processed.", UVM_NONE)
endfunction
endclass
 
class af_demo_test extends uvm_test;
`uvm_component_utils(af_demo_test)
af_env env;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
env = af_env::type_id::create("env", this);
endfunction
endclass
 
module tb_af_top;
initial run_test();
endmodule
 
// Expected output:
// UVM_INFO PUB: write(0) @ 0     ← publisher fires all 6 by T=25
// UVM_INFO PUB: write(1) @ 5
// UVM_INFO PUB: write(2) @ 10
// UVM_INFO PUB: write(3) @ 15
// UVM_INFO PUB: write(4) @ 20
// UVM_INFO PUB: write(5) @ 25
// UVM_INFO SUB: got(0) data=16 @ 20  [1/6]  ← subscriber starts draining
// UVM_INFO SUB: got(1) data=32 @ 40  [2/6]
// UVM_INFO SUB: got(2) data=48 @ 60  [3/6]
// UVM_INFO SUB: got(3) data=64 @ 80  [4/6]
// UVM_INFO SUB: got(4) data=80 @ 100 [5/6]
// UVM_INFO SUB: got(5) data=96 @ 120 [6/6]
// UVM_INFO ENV: FIFO fully drained. All items processed.
//
// Note: publisher finishes at T=25, subscriber finishes at T=120.
// The FIFO absorbed the burst and let subscriber work at its own pace.

§12 — Interview Questions

Beginner Level

Intermediate Level

Senior / Architect Level

§13 — Best Practices

RulePracticeWhy It Matters
BP-1Always check fifo.used() == 0 in check_phaseTests silently PASS with unprocessed transactions in the FIFO if this check is absent
BP-2Use size=0 (unlimited) for analysis FIFOs connected to monitorsMonitors must never stall; unlimited depth prevents deadlock; use bounded size only for deliberate backpressure testing
BP-3Let the scoreboard hold its own objection until the FIFO is drainedPrevents test ending before all transactions are verified — the producer's objection drop should not terminate the scoreboard's drain
BP-4Call flush() only when the consumer is not blocking in get()flush() after get() leaves the consumer blocking forever on an empty FIFO — simulation hangs until timeout
BP-5Use uvm_tlm_analysis_fifo to decouple monitor write() from scoreboard processingScoreboard task-based processing can take time freely; monitor is never slowed by scoreboard workload
BP-6Connect both get_port and peek_port to the same get_peek_exportThe export handles both — no need for two separate FIFOs; peek() does not consume the item
BP-7Name FIFOs descriptively: req_fifo, rsp_fifo, ap_fifoWhen an env has multiple FIFOs, generic names like "fifo" cause confusion in debug logs and check_phase error messages
BP-8Subscribe to fifo.put_ap and fifo.get_ap for FIFO depth tracking in coverage or protocol checkersThese built-in analysis ports fire on every put and get — a FIFO depth monitor can track max depth as a coverage metric without modifying the FIFO or its connected components

§14 — Summary

Aspectuvm_tlm_fifouvm_tlm_analysis_fifo
Input sideput_export — accepts blocking put() callsanalysis_export — accepts analysis write() calls
Output sideget_peek_export — serves get() and peek()get_peek_export — same
Producer interfaceTLM 1.0 put_port (blocking, can stall)Analysis port (non-blocking, never stalls)
BackpressureYes — bounded FIFO blocks put() when fullNo — analysis write() is non-blocking regardless of depth
Canonical useProducer-consumer with rate control; pipeline stagesMonitor (write) → scoreboard/checker (get); bridge pattern
Size=0Unlimited depth — put() never blocksUnlimited depth — default for analysis FIFOs
Monitoringput_ap, get_ap for event notificationsSame — put_ap fires on write(), get_ap on get()