Non-Blocking Put & Get Ports
log_port.put(txn) from the previous page genuinely stalls the monitor's run_phase for however long fifo_write_logger::put() takes to return. That's fine for a logger, but it's a real problem for a component that has its own clock-cycle-accurate timing to maintain and cannot afford to sit blocked waiting on a downstream consumer that might be busy. Non-blocking put and non-blocking get exist for exactly that case: instead of blocking, they return immediately with a pass/fail result, and let the caller decide what to do next.
try_put() and can_put(): functions, not tasks
class fifo_write_logger extends uvm_component;
`uvm_component_utils(fifo_write_logger)
uvm_nonblocking_put_imp #(fifo_txn, fifo_write_logger) put_imp;
bit busy;
function new(string name, uvm_component parent);
super.new(name, parent);
put_imp = new("put_imp", this);
endfunction
// Readiness check — zero time, no side effects
function bit can_put();
return !busy;
endfunction
// Attempt — zero time, returns 1 on success or 0 immediately on failure
function bit try_put(fifo_txn txn);
if (busy) return 0;
busy = 1;
fork
begin
#20; // the same slow logging operation as before, now backgrounded
busy = 0;
end
join_none
return 1;
endfunction
endclass
The defining difference from the previous page: both can_put() and try_put() are functions, not tasks — by definition, a function cannot consume simulation time, so neither one can block even if it wanted to. try_put() either succeeds immediately (returning 1) or fails immediately (returning 0); there is no in-between, and no waiting.
The polling pattern
On the calling side, a uvm_nonblocking_put_port #(fifo_txn) typically drives a small retry loop rather than a single call:
task try_log(fifo_txn txn);
bit accepted = 0;
while (!accepted) begin
if (log_port.can_put())
accepted = log_port.try_put(txn);
if (!accepted)
@(posedge vif.clk); // wait a cycle, then check again
end
endtask
This is the general shape non-blocking TLM is used in: check readiness, attempt, and if it fails, wait something (a clock edge here) before trying again — the caller stays in control of what "waiting" means, rather than being blocked unconditionally inside someone else's task. A monitor with strict per-cycle timing requirements can use exactly this pattern to attempt a hand-off every cycle without ever risking an indefinite stall.
Non-blocking get is the mirror image
Just as with the blocking pair from the previous page, non-blocking get reverses direction: can_get()/try_get() are implemented by whichever component holds the data, and a uvm_nonblocking_get_port on the consuming side polls in the same shape as try_log() above. A consumer that can be fed random busy/not-ready responses from try_get() is also a useful way to deliberately exercise a producer's retry logic during testing — a producer that's never had to handle "not ready yet" will often have a latent bug there, exposed for the first time exactly by this kind of test.
Peek and combined ports
Two related non-blocking primitives round out the picture:
try_peek()/can_peek()— the non-blocking counterpart topeek(): they check and read the next available item without removing it from whatever holds it, using the same zero-time function pair astry_put()/can_put().- Combined ports — rather than wiring up separate put and get ports on a component that needs both directions, UVM offers pre-bundled non-blocking variants like
uvm_nonblocking_transport_port(request out, response back, in one non-blocking call) anduvm_nonblocking_master_port(combining put, get, and peek together). These exist purely as convenience bundles over the same primitive non-blocking calls this page already covers — nothing abouttry_put()/can_put()'s zero-time, function-only nature changes when it's accessed through one of them.uvm_nonblocking_master_porthas a mirror-image counterpart,uvm_nonblocking_slave_port: a master puts requests and gets/peeks the responses that come back, while a slave gets/peeks incoming requests and puts the responses — the same requester/responder split, just from the opposite side of the same bidirectional exchange.
What's next
Blocking and non-blocking ports both assume a direct one-to-one connection between exactly two components. The next page covers uvm_tlm_fifo — a buffer that sits between a producer and consumer running at different, mismatched rates.