Skip to main content

Blocking Put & Get Ports

Analysis ports, from the previous page, are one-to-many broadcasts with no concept of the receiver's readiness — ap.write(txn) fires and moves on, whether zero or a dozen imps are connected. Plenty of TLM communication needs the opposite: exactly one producer, one consumer, and a real handshake where the producer actually waits until the consumer is ready to accept. Blocking put and blocking get are the simplest pair of patterns for that.

Blocking put: push, and wait until it's accepted​

Say a logging component needs every write transaction handed to it directly, and needs to apply real backpressure — if it's still processing the last one, the sender should genuinely wait, not drop or queue silently.

class fifo_write_logger extends uvm_component;
`uvm_component_utils(fifo_write_logger)
uvm_blocking_put_imp #(fifo_txn, fifo_write_logger) put_imp;

function new(string name, uvm_component parent);
super.new(name, parent);
put_imp = new("put_imp", this);
endfunction

// The imp side implements the actual behavior — this task can
// take real simulation time before returning, and the sender waits for it
task put(fifo_txn txn);
#20; // e.g. writing to a log file, or some other slow operation
`uvm_info("LOG", $sformatf("Logged write: data=%0h", txn.wr_data), UVM_HIGH)
endtask
endclass
class fifo_monitor extends uvm_monitor;
// ... vif, ap, build_phase as in The Monitor & Subscriber ...
uvm_blocking_put_port #(fifo_txn) log_port;

function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
log_port = new("log_port", this);
endfunction

task run_phase(uvm_phase phase);
forever begin
// ... reconstruct txn from vif as before ...
ap.write(txn); // broadcast, fire-and-forget
if (txn.op == fifo_txn::WRITE)
log_port.put(txn); // blocks here until fifo_write_logger.put() returns
end
endtask
endclass
  • uvm_blocking_put_port #(fifo_txn) on the sender, uvm_blocking_put_imp #(fifo_txn, fifo_write_logger) on the receiver — the imp's second type parameter is the implementing class itself, needed so the port knows which class's put() method it's ultimately calling into.
  • Both port and imp are new()'d in the constructor, the same convention as the analysis port from the previous page — they're plain TLM objects, not factory-registered components, so there's nothing for type_id::create() to do here.
  • Connected in connect_phase, same as every other TLM wiring in this section: monitor.log_port.connect(logger.put_imp).
  • The blocking part is real: the #20 delay inside put() genuinely stalls log_port.put(txn) in the monitor's run_phase for 20 time units before it returns — unlike ap.write(), which always completes within the same delta cycle no matter what's connected to it.

Blocking get: the same idea, direction reversed​

Blocking get mirrors put exactly, but the consumer pulls instead of the producer pushing:

class fifo_replay_source extends uvm_component;
`uvm_component_utils(fifo_replay_source)
uvm_blocking_get_imp #(fifo_txn, fifo_replay_source) get_imp;
fifo_txn recorded_q[$];

function new(string name, uvm_component parent);
super.new(name, parent);
get_imp = new("get_imp", this);
endfunction

// Called whenever something on the other end requests the next item
task get(output fifo_txn txn);
wait (recorded_q.size() > 0);
txn = recorded_q.pop_front();
endtask
endclass

A uvm_blocking_get_port #(fifo_txn) on the consuming side calls get_port.get(txn), which blocks until fifo_replay_source::get() actually has something to return — control flow is inverted from put (the implementer here is the data source, not the data sink), but the port/imp/connect mechanics are identical.

What's next​

Blocking ports are the simplest correct choice whenever waiting is actually fine. The next page covers the non-blocking variants, for the cases where a component genuinely cannot afford to stall waiting on the other end.