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.

diagram
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

systemverilog
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
diagram
[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 logged
  • Maintain 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

systemverilog
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
diagram
[TLM] liveness guidance

non-blocking loops need progress policy:
  - retry interval
  - alternate work while idle
  - watchdog or timeout
  - optional escalation after prolonged starvation
  1. Separate temporary no-data idle from permanent starvation.

  2. Add counters/telemetry for can_get false streaks.

  3. Fail with clear message when liveness thresholds are exceeded.


Flow-control correctness checklist

diagram
[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 gate

Non-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.