Part 4 · TLM & Analysis · Intermediate
uvm_tlm_analysis_fifo Pattern: Monitor Broadcast to Pull-Based Checker
How to use uvm_tlm_analysis_fifo as a monitor sink with analysis_export, and why scoreboards often consume via get() thread instead of heavy write().
The core pattern
Monitors publish transactions through analysis_port.write(). A scoreboard can receive those directly via write(), but complex checks often need a dedicated run_phase thread. uvm_tlm_analysis_fifo bridges this gap by providing an analysis_export for input and get()/try_get() for pull-based consumption.
class bus_scoreboard extends uvm_scoreboard;
`uvm_component_utils(bus_scoreboard)
uvm_tlm_analysis_fifo #(bus_txn) act_fifo;
function new(string name, uvm_component parent);
super.new(name, parent);
act_fifo = new("act_fifo", this);
endfunction
task run_phase(uvm_phase phase);
bus_txn t;
forever begin
act_fifo.get(t);
compare_against_model(t);
end
endtask
endclass
// env connect_phase:
// mon.ap.connect(scb.act_fifo.analysis_export);[MON]->[TLM]->[SCB] analysis FIFO wiring
monitor.ap.write(txn)
│
▼
scb.act_fifo.analysis_export
│
▼
internal queue
│
▼
scb.run_phase: act_fifo.get(txn) -> compare()Why not do everything in write()
A scoreboard write() must be a function and should return quickly. Heavy matching logic, model updates, logging bursts, or database operations can bloat write() and distort monitor throughput.
write() should capture or enqueue, not run expensive multi-step comparisons.
analysis FIFO allows deterministic pull order in a single checker thread.
run_phase consumer can block, wait for companions, and coordinate multiple streams.
this pattern centralizes timeout and liveness checks in one place.
[SCB] direct write() vs analysis FIFO
DIRECT WRITE:
monitor -> scb.write(txn) -> heavy compare now
risk: monitor path slows when checker heavy
ANALYSIS FIFO:
monitor -> fifo.enqueue(txn) -> write returns quickly
scoreboard thread pulls at controlled pace
benefit: better decoupling and easier liveness instrumentationfunction void write(bus_txn t);
// Keep this tiny if direct analysis_imp is used:
// queue.push_back(t.clone());
//
// Better: avoid this method entirely by wiring monitor to analysis_fifo.
endfunctionDual-stream scoreboards
Many scoreboards compare expected and actual streams. Two analysis FIFOs provide clear ownership boundaries and easier ordering logic.
class compare_scoreboard extends uvm_scoreboard;
`uvm_component_utils(compare_scoreboard)
uvm_tlm_analysis_fifo #(bus_txn) exp_fifo;
uvm_tlm_analysis_fifo #(bus_txn) act_fifo;
function new(string name, uvm_component parent);
super.new(name, parent);
exp_fifo = new("exp_fifo", this);
act_fifo = new("act_fifo", this);
endfunction
task run_phase(uvm_phase phase);
bus_txn exp_t, act_t;
forever begin
exp_fifo.get(exp_t);
act_fifo.get(act_t);
if (!exp_t.compare(act_t))
`uvm_error("MISMATCH", $sformatf("exp=%s act=%s",
exp_t.convert2string(), act_t.convert2string()))
end
endtask
endclass
// env connect examples:
// pred.ap.connect(scb.exp_fifo.analysis_export);
// mon.ap.connect(scb.act_fifo.analysis_export);[TLM] dual FIFO synchronization sketch
expected stream ---> exp_fifo ---> > pair + compare
actual stream -----> act_fifo --->/
Both queues provide explicit buffering and visible occupancy.Design notes for dual FIFOs
Define pairing policy early: strict in-order, keyed by ID, or windowed matching.
Track occupancy asymmetry (exp used vs act used) to catch model drift quickly.
Add timeout watchdog if one stream can pause legitimately.
Operational checklist
Construct analysis FIFO objects in new() or build_phase.
Connect monitor analysis_port to fifo.analysis_export in connect_phase.
Consume via get() in run_phase with clear objection/lifecycle policy.
Instrument used() periodically in long tests to detect unhealthy lag.
Document whether FIFO depth is bounded or intentionally unbounded.
Key takeaways
analysis FIFO is the canonical monitor-to-scoreboard decoupling pattern.
It keeps producer callback paths lightweight and checker logic thread-based.
Dual FIFOs simplify many expected-vs-actual matching architectures.
Common pitfalls
Connecting monitor directly to heavy write() and then debugging phantom monitor stalls.
Forgetting to connect analysis_export; consumer blocks forever with no obvious errors.
Ignoring queue growth metrics until regressions start timing out.