Part 4 · TLM & Analysis · Intermediate
Blocking put/get: Back-Pressure by Construction
Semantics of blocking put and get, where simulation time advances, and how blocking calls naturally model ready/valid style flow control.
Semantics first: who waits and why
A blocking put call waits until the receiver accepts the transaction. A blocking get call waits until data is available. This waiting behavior is exactly what models hardware back-pressure where one side cannot proceed yet.
Because waiting may consume simulation time, blocking interfaces belong in tasks and run-phase threads. Using them in pure function contexts is illegal and conceptually wrong.
Legend: [TLM] [UVM]
[TLM] blocking timeline
t0 producer calls put(T0)
t1 target busy -> producer remains blocked
t2 target ready and accepts T0
t3 put() returns; producer continues
t0 consumer calls get(Tx)
t1 queue empty -> consumer remains blocked
t2 producer enqueues T1
t3 get() returns T1; consumer continuesBlocking put models producer-side stall when target cannot accept.
Blocking get models consumer-side stall when source has no data.
Back-pressure appears naturally without ad-hoc wait loops.
Minimal blocking producer-consumer example
class pkt extends uvm_sequence_item;
`uvm_object_utils(pkt)
rand bit [31:0] data;
function new(string name = "pkt");
super.new(name);
endfunction
endclass
class producer extends uvm_component;
`uvm_component_utils(producer)
uvm_blocking_put_port #(pkt) put_port;
function new(string name, uvm_component parent);
super.new(name, parent);
put_port = new("put_port", this);
endfunction
task run_phase(uvm_phase phase);
pkt p;
repeat (4) begin
p = pkt::type_id::create("p");
assert(p.randomize());
`uvm_info("PROD", $sformatf("before put data=0x%08h", p.data), UVM_MEDIUM)
put_port.put(p); // blocks until target accepts
`uvm_info("PROD", "after put returned", UVM_MEDIUM)
end
endtask
endclass
class consumer extends uvm_component;
`uvm_component_utils(consumer)
uvm_blocking_put_imp #(pkt, consumer) put_imp;
time service_delay = 20ns;
function new(string name, uvm_component parent);
super.new(name, parent);
put_imp = new("put_imp", this);
endfunction
task put(pkt p);
#service_delay; // emulate processing latency / downstream busy time
`uvm_info("CONS", $sformatf("accepted data=0x%08h", p.data), UVM_MEDIUM)
endtask
endclass[UVM] connection
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
prod.put_port.connect(cons.put_imp);
endfunction
When service_delay increases, producer throughput automatically drops.
No extra retry code is required.Target latency automatically throttles producer via call blocking.
No polling loops are needed for basic back-pressure behavior.
The same pattern maps well to ready/valid and request queue semantics.
Blocking get for pull-style consumers
With blocking get, the consumer actively pulls and waits if nothing is available. This fits checker threads or post-processing stages that can idle until input arrives.
class source extends uvm_component;
`uvm_component_utils(source)
uvm_blocking_get_imp #(pkt, source) get_imp;
pkt q[$];
function new(string name, uvm_component parent);
super.new(name, parent);
get_imp = new("get_imp", this);
endfunction
task run_phase(uvm_phase phase);
pkt p;
forever begin
p = pkt::type_id::create("p");
assert(p.randomize());
q.push_back(p);
#15ns;
end
endtask
task get(output pkt p);
wait (q.size() > 0);
p = q.pop_front();
endtask
endclass
class sink extends uvm_component;
`uvm_component_utils(sink)
uvm_blocking_get_port #(pkt) get_port;
function new(string name, uvm_component parent);
super.new(name, parent);
get_port = new("get_port", this);
endfunction
task run_phase(uvm_phase phase);
pkt p;
forever begin
get_port.get(p); // blocks if source queue is empty
`uvm_info("SINK", $sformatf("got data=0x%08h", p.data), UVM_LOW)
#10ns;
end
endtask
endclass[TLM] push and pull in one view
push style:
producer calls put() into target
pull style:
consumer calls get() from source
both are blocking and encode waiting naturallyGuidelines, observability, and failure modes
Log before and after blocking calls when debugging throughput stalls.
Keep heavy target-side work in dedicated run threads when possible.
Do not call blocking methods from function contexts.
Treat long waits as potentially expected back-pressure before assuming deadlock.
[UVM] quick stall triage
Symptom: producer appears hung on put()
Check:
1) Is target put() entered at all?
2) Is target itself blocked on downstream resource?
3) Are objections dropped too early, ending run threads?
4) Is there hidden lock/handshake ordering issue?Key takeaways
Blocking put/get expresses flow control directly and clearly.
Waiting behavior should be intentional and visible in logs.
Use blocking interfaces in tasks where simulation time may advance.
Common pitfalls
Interpreting expected back-pressure as a simulator hang.
Missing before/after logs around blocking calls, slowing triage.
Mixing blocking calls with zero-time function-only logic.