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.

diagram
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 continues
  • Blocking 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

systemverilog
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
diagram
[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.

systemverilog
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
diagram
[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 naturally

Guidelines, observability, and failure modes

  1. Log before and after blocking calls when debugging throughput stalls.

  2. Keep heavy target-side work in dedicated run threads when possible.

  3. Do not call blocking methods from function contexts.

  4. Treat long waits as potentially expected back-pressure before assuming deadlock.

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