Part 4 · TLM & Analysis · Intermediate
Non-blocking put/get: try_* and can_* Flow Control
How try_put/try_get and can_put/can_get support polling loops, arbitration logic, and latency-insensitive progress without blocking threads.
Non-blocking contract
Non-blocking methods return immediately with success/failure status. They never wait for readiness. This is useful when a thread must remain responsive, arbitrate among multiple channels, or make progress elsewhere when one path is temporarily unavailable.
Typical pairs are can_put + try_put and can_get + try_get. The can_* query is advisory; real correctness still depends on checking try_* return values.
Legend: [TLM] [UVM]
[TLM] non-blocking decision loop
if can_put():
ok = try_put(item)
if !ok -> resource changed between check and action
else:
do_other_work()
always check try_* return.
can_* is a hint, not a guarantee.try_* return values are mandatory correctness signals.
can_* helps scheduling decisions but does not reserve resources.
Non-blocking APIs are ideal for cooperative multitasking in run_phase loops.
Producer-side non-blocking pattern
class nb_producer extends uvm_component;
`uvm_component_utils(nb_producer)
uvm_nonblocking_put_port #(pkt) nb_put_port;
pkt backlog[$];
function new(string name, uvm_component parent);
super.new(name, parent);
nb_put_port = new("nb_put_port", this);
endfunction
task run_phase(uvm_phase phase);
pkt p;
forever begin
// generate traffic
p = pkt::type_id::create("p");
assert(p.randomize());
backlog.push_back(p);
// attempt drain
if (backlog.size() > 0) begin
if (nb_put_port.can_put()) begin
if (nb_put_port.try_put(backlog[0])) begin
`uvm_info("NB_PROD", "sent one item", UVM_HIGH)
void'(backlog.pop_front());
end
else begin
`uvm_info("NB_PROD", "try_put failed despite can_put", UVM_HIGH)
end
end
end
#5ns; // cooperative pacing; avoid zero-time busy spins
end
endtask
endclass[UVM] producer policy options
on try_put failure:
- keep item in backlog and retry later
- reroute to secondary path
- count/drop under controlled stress policy
all policies should be explicit and loggedMaintain a backlog queue to preserve order when retries are needed.
Pace loops with delay or event waits to avoid busy-spin overhead.
Differentiate transient not-ready from structural wiring failures in logs.
Consumer-side non-blocking pattern
class nb_consumer extends uvm_component;
`uvm_component_utils(nb_consumer)
uvm_nonblocking_get_port #(pkt) nb_get_port;
int unsigned idle_cycles;
function new(string name, uvm_component parent);
super.new(name, parent);
nb_get_port = new("nb_get_port", this);
endfunction
task run_phase(uvm_phase phase);
pkt p;
forever begin
if (nb_get_port.can_get()) begin
if (nb_get_port.try_get(p)) begin
idle_cycles = 0;
process_item(p);
end
end
else begin
idle_cycles++;
if ((idle_cycles % 100) == 0)
`uvm_info("NB_CONS", $sformatf("idle cycles=%0d", idle_cycles), UVM_LOW)
end
#2ns;
end
endtask
task process_item(pkt p);
#3ns;
`uvm_info("NB_CONS", $sformatf("processed 0x%08h", p.data), UVM_HIGH)
endtask
endclass[TLM] liveness guidance
non-blocking loops need progress policy:
- retry interval
- alternate work while idle
- watchdog or timeout
- optional escalation after prolonged starvationSeparate temporary no-data idle from permanent starvation.
Add counters/telemetry for can_get false streaks.
Fail with clear message when liveness thresholds are exceeded.
Flow-control correctness checklist
[UVM][TLM] non-blocking checklist
[ ] every try_put/try_get return checked
[ ] no silent drop path unless explicitly intentional
[ ] loop pacing prevents zero-time spin
[ ] backlog growth monitored
[ ] timeout/watchdog for stuck conditions
[ ] can_* used as optimization, not sole correctness gateNon-blocking TLM is flexible, but flexibility requires explicit policy. Unchecked failure returns and unbounded retry queues are common sources of hidden regressions.
Key takeaways
Non-blocking methods keep threads responsive and composable.
Always validate try_* return values, even after can_* checks.
Pair polling loops with pacing and liveness instrumentation.
Common pitfalls
Assuming can_* guarantees success on subsequent try_*.
Retrying forever without timeout or diagnostic counters.
Dropping failed transactions silently under load.