Part 4 · TLM & Analysis · Intermediate
uvm_tlm_fifo Basics: Blocking put/get and Queue Semantics
Core uvm_tlm_fifo behavior: constructor/depth, put/get/peek/try_get, and how blocking semantics model producer-consumer synchronization.
What uvm_tlm_fifo gives you
A uvm_tlm_fifo #(T) is a transaction queue with standardized TLM semantics. Producers put transactions, consumers get them later, and the FIFO enforces ordering while optionally applying capacity limits.
The queue is temporal decoupling : producer and consumer no longer need to run in lockstep. This is critical whenever producer rate is bursty or consumer processing is variable.
class fifo_demo_comp extends uvm_component;
`uvm_component_utils(fifo_demo_comp)
uvm_tlm_fifo #(bus_txn) req_fifo;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// depth=8 means producer blocks on put() when 8 items are queued
req_fifo = new("req_fifo", this, 8);
endfunction
endclass[TLM] FIFO ordering guarantee
put(txn0) -> put(txn1) -> put(txn2)
│ │ │
▼ ▼ ▼
queue[0] queue[1] queue[2]
│
▼
get() returns txn0, then txn1, then txn2
FIFO preserves enqueue order for a single queue instance.Blocking operations
The most common API pair is put() and get(), both blocking task calls. put() waits when full; get() waits when empty.
task producer_thread();
bus_txn t;
forever begin
t = bus_txn::type_id::create("t");
assert(t.randomize());
req_fifo.put(t); // blocks if FIFO is full
`uvm_info("PROD", $sformatf("enqueued id=%0d used=%0d",
t.id, req_fifo.used()), UVM_MEDIUM)
end
endtask
task consumer_thread();
bus_txn t;
forever begin
req_fifo.get(t); // blocks if FIFO is empty
`uvm_info("CONS", $sformatf("dequeued id=%0d used=%0d",
t.id, req_fifo.used()), UVM_MEDIUM)
process_txn(t);
end
endtask[TLM] blocking behavior timeline
TIME -------------------------------------------------------------------->
producer: put(A) put(B) put(C) [blocks full] ............ resumes put(D)
queue used: 1 2 3 3 (max depth=3) 3
consumer: ............ get(A) get(B) get(C) ................. get(D)
When queue is full, producer naturally back-pressures.
When queue is empty, consumer naturally waits.Non-blocking helper methods
try_put(t) returns 0 immediately if queue is full.
try_get(t) returns 0 immediately if queue is empty.
can_put() and can_get() support polling-style loops.
peek(t) reads front item without removing it.
used() reports current occupancy; size() reports capacity.
Minimal producer-consumer example
This skeleton shows one producer and one consumer running in parallel threads with the FIFO as synchronization boundary.
class pc_env extends uvm_env;
`uvm_component_utils(pc_env)
producer_comp prod;
consumer_comp cons;
uvm_tlm_fifo #(bus_txn) fifo;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
prod = producer_comp::type_id::create("prod", this);
cons = consumer_comp::type_id::create("cons", this);
fifo = new("fifo", this, 16);
// pass FIFO handle via config_db for simplicity
uvm_config_db#(uvm_tlm_fifo#(bus_txn))::set(this, "prod", "fifo_h", fifo);
uvm_config_db#(uvm_tlm_fifo#(bus_txn))::set(this, "cons", "fifo_h", fifo);
endfunction
endclass
class producer_comp extends uvm_component;
`uvm_component_utils(producer_comp)
uvm_tlm_fifo #(bus_txn) fifo_h;
function void build_phase(uvm_phase phase);
if (!uvm_config_db#(uvm_tlm_fifo#(bus_txn))::get(this, "", "fifo_h", fifo_h))
`uvm_fatal("CFG", "missing fifo_h")
endfunction
task run_phase(uvm_phase phase);
repeat (100) begin
bus_txn t = bus_txn::type_id::create("t");
assert(t.randomize());
fifo_h.put(t);
end
endtask
endclass
class consumer_comp extends uvm_component;
`uvm_component_utils(consumer_comp)
uvm_tlm_fifo #(bus_txn) fifo_h;
function void build_phase(uvm_phase phase);
if (!uvm_config_db#(uvm_tlm_fifo#(bus_txn))::get(this, "", "fifo_h", fifo_h))
`uvm_fatal("CFG", "missing fifo_h")
endfunction
task run_phase(uvm_phase phase);
bus_txn t;
forever begin
fifo_h.get(t);
process_txn(t);
end
endtask
endclass[TLM] behavior summary
producer speed > consumer speed:
queue occupancy rises toward depth
producer eventually blocks at put()
consumer speed > producer speed:
queue occupancy frequently 0
consumer blocks at get()
Either way, no busy-wait is required.Key takeaways
uvm_tlm_fifo gives ordered buffering with natural blocking semantics.
put/get model backpressure and waiting without custom synchronization code.
used()/size() should be part of your observability strategy.
Common pitfalls
Using try_get in a tight forever loop and burning simulation time.
Forgetting capacity implications and assuming put never blocks.
Treating FIFO as transport but forgetting to preserve transaction immutability policy.