Part 4 · TLM & Analysis · Intermediate

transport(): Combined Request + Response

Using transport interfaces when a caller needs a direct response to each request, with memory-model style examples and integration guidance.

Why transport exists

The transport family models request-response coupling in one API call. It is ideal when every request naturally yields a response object and the caller should not manually orchestrate separate put/get channels.

A classic case is memory/register abstraction: caller sends a read/write request and receives status/data response. The method encapsulates both direction and synchronization contract.

diagram
Legend: [TLM] [UVM]

separate channels approach:
  put(req)
  get(rsp)
  (caller coordinates pairing)

transport approach:
  transport(req, rsp)
  (pairing guaranteed by interface contract)
  • transport simplifies per-request response pairing.

  • Useful for functional models that behave like callable services.

  • Avoids accidental request/response stream desynchronization.


Blocking transport example: memory service

systemverilog
class mem_req extends uvm_sequence_item;
  `uvm_object_utils(mem_req)
  rand bit        write;
  rand bit [15:0] addr;
  rand bit [31:0] data;
  function new(string name = "mem_req");
    super.new(name);
  endfunction
endclass

class mem_rsp extends uvm_sequence_item;
  `uvm_object_utils(mem_rsp)
  bit ok;
  bit [31:0] data;
  function new(string name = "mem_rsp");
    super.new(name);
  endfunction
endclass

class mem_model extends uvm_component;
  `uvm_component_utils(mem_model)
  uvm_transport_imp #(mem_req, mem_rsp, mem_model) tr_imp;
  bit [31:0] mem [bit[15:0]];

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

  task transport(mem_req req, output mem_rsp rsp);
    rsp = mem_rsp::type_id::create("rsp");
    rsp.ok = 1'b1;

    if (req.write) begin
      mem[req.addr] = req.data;
      rsp.data = req.data;
    end
    else begin
      rsp.data = mem.exists(req.addr) ? mem[req.addr] : '0;
    end
    #5ns; // service latency
  endtask
endclass

class mem_client extends uvm_component;
  `uvm_component_utils(mem_client)
  uvm_transport_port #(mem_req, mem_rsp) tr_port;

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

  task run_phase(uvm_phase phase);
    mem_req req;
    mem_rsp rsp;

    req = mem_req::type_id::create("wreq");
    req.write = 1;
    req.addr  = 'h40;
    req.data  = 32'hCAFE_BABE;
    tr_port.transport(req, rsp);

    req = mem_req::type_id::create("rreq");
    req.write = 0;
    req.addr  = 'h40;
    req.data  = '0;
    tr_port.transport(req, rsp);
    `uvm_info("MEM_CLIENT", $sformatf("readback=0x%08h", rsp.data), UVM_MEDIUM)
  endtask
endclass
diagram
[UVM] connect_phase

function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  client.tr_port.connect(model.tr_imp);
endfunction

Non-blocking transport and concurrency

For poll-driven services, nb_transport variants allow immediate return. The caller can continue with other work and retry or check completion state later.

systemverilog
class service extends uvm_component;
  `uvm_component_utils(service)
  uvm_nonblocking_transport_imp #(mem_req, mem_rsp, service) nb_tr_imp;

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

  function bit nb_transport(mem_req req, output mem_rsp rsp);
    rsp = mem_rsp::type_id::create("rsp");
    if (resource_busy(req.addr))
      return 0;

    rsp.ok = 1;
    rsp.data = req.write ? req.data : do_read(req.addr);
    return 1;
  endfunction
endclass
diagram
[TLM] selection heuristic

use blocking transport when:
  caller naturally waits for response
  latency is part of modeled behavior

use non-blocking transport when:
  caller must stay reactive
  service availability is opportunistic
  • Keep request-response pairing explicit regardless of blocking style.

  • Log request IDs/addresses for easy pairing debug.

  • Avoid hybrid hidden side channels that bypass transport responses.


Debugging response mismatch issues

  1. Attach an ID field in req/rsp for traceability in logs.

  2. Log at call entry and return with addr/kind/data/status.

  3. Check for stale response object reuse across calls.

  4. Verify one target implementation per call path in connect topology.

diagram
[UVM][TLM] transport debug table

Symptom                          Likely cause
-------------------------------------------------------------
response appears from old req    rsp object reused incorrectly
wrong read data                  address decode mismatch
always timeout/busy              target never leaves busy state
sporadic mismatches              shared mutable req object race

Key takeaways

  • transport is the cleanest abstraction for tightly coupled request-response calls.

  • Blocking and non-blocking variants address different scheduling needs.

  • Trace IDs and explicit response lifecycle handling make debug deterministic.

Common pitfalls

  • Reusing response objects across requests without reset.

  • Mixing transport with ad-hoc side-channel responses.

  • Ignoring non-blocking transport failure returns.