Part 4 · TLM & Analysis · Intermediate

analysis_port Broadcast: write() Fan-Out Without Backpressure

How uvm_analysis_port.write() delivers to all connected imps/subscribers, delivery ordering implications, and why the path must stay non-blocking.

write() means publish to everyone connected

A uvm_analysis_port#(T) is a publisher endpoint. When the producer calls write(tr), UVM dispatches that transaction to every connected analysis implementation endpoint.

This is fan-out, not selection. If three consumers are connected, each receives the callback. If none are connected, the call returns and nothing observes the sample.

systemverilog
class apb_monitor extends uvm_monitor;
  `uvm_component_utils(apb_monitor)

  uvm_analysis_port #(apb_txn) ap;

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

  task run_phase(uvm_phase phase);
    apb_txn tr;
    forever begin
      tr = sample_apb();
      ap.write(tr); // broadcast to all connected consumers
    end
  endtask
endclass
diagram
[MON][TLM] fan-out picture

             tr0 tr1 tr2 ...
 [MON] ap.write(tr) each sample
         │
         ├─► sb.write(tr)
         ├─► cov.write(tr)
         └─► pred.write(tr)

 same producer call fans to all connected endpoints
  • Producer writes once; N subscribers receive.

  • Connectivity defines recipients, not producer-side branch logic.

  • Broadcast path keeps monitor reusable and checker composition flexible.


No backpressure is a design feature

Analysis write() is intended to stay non-blocking from the producer perspective. Monitors should not stop observing traffic because a checker is busy.

If a consumer does expensive work or blocks, it can degrade simulation and distort monitor behavior. Use lightweight write() handlers, and offload heavy work to queues/FIFOs when needed.

diagram
[TLM] no-backpressure consequence

 producer perspective:
   monitor keeps sampling bus
   monitor calls ap.write(tr)
   monitor does not negotiate ready/valid with subscribers

 consumer responsibility:
   process quickly in write()
   or enqueue + return
   do heavy compare in another thread
systemverilog
class safe_scoreboard extends uvm_component;
  `uvm_component_utils(safe_scoreboard)
  uvm_analysis_imp #(apb_txn, safe_scoreboard) in;
  apb_txn q[$];

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

  function void write(apb_txn tr);
    q.push_back(tr.clone()); // quick ingestion
  endfunction

  task run_phase(uvm_phase phase);
    forever begin
      wait (q.size() > 0);
      compare_one(q.pop_front()); // heavier work outside write()
    end
  endtask
endclass
diagram
[CHECK] ingestion strategy

  write() path:        fast copy + queue
  processing path:     separate thread
  benefit:             monitor timeline remains stable
  risk if ignored:     long write() stalls global simulation progress
  • Keep write() short and deterministic.

  • Queue-then-process pattern protects monitor observability fidelity.

  • Use analysis_fifo when decoupling/ordering guarantees are needed.


Delivery ordering and transaction identity

Within a producer stream, call order is the natural sequence seen by subscribers. However, each subscriber can process at different speeds after ingestion, so downstream report timing may differ.

Because all consumers may receive the same object handle, mutation discipline matters . If one subscriber edits fields in-place, other consumers may observe changed data unexpectedly.

diagram
[MON][CHECK] shared handle risk

 monitor creates tr
   ├─► subscriber A writes: tr.kind = ERR  (mutates)
   ├─► subscriber B sees kind=ERR (unexpected for raw monitor view)
   └─► subscriber C logs modified form

 fix:
   clone on ingestion before local mutation
systemverilog
function void write(apb_txn tr);
  apb_txn local;
  local = tr.clone();
  local.set_id_info(tr);
  // safe local mutation for checker-specific normalization
  local.addr[1:0] = 2'b00;
  normalized_q.push_back(local);
endfunction
diagram
[TLM] mutation policy options

 Option A: monitor sends immutable objects (team rule)
 Option B: every subscriber clones before mutation
 Option C: dedicated normalizer subscriber outputs new stream

 choose one policy and enforce consistently
  • Do not assume each subscriber receives independent transaction objects.

  • Clone before mutation unless immutability is guaranteed by policy.

  • Sequence order at producer is stable; subscriber completion timing is independent.


Walkthrough: monitor broadcasting to scoreboard and coverage

This walkthrough shows the canonical pattern: monitor writes one transaction stream, scoreboard checks correctness, and coverage bins protocol scenarios from the same passive observations.

systemverilog
class env extends uvm_env;
  `uvm_component_utils(env)
  apb_agent      agt;
  apb_scoreboard sb;
  apb_cov_sub    cov;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agt = apb_agent::type_id::create("agt", this);
    sb  = apb_scoreboard::type_id::create("sb", this);
    cov = apb_cov_sub::type_id::create("cov", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    agt.mon.ap.connect(sb.in);
    agt.mon.ap.connect(cov.analysis_export);
  endfunction
endclass
diagram
[MON][TLM][CHECK] end-to-end timeline

 t0 monitor samples bus -> tr0
 t1 monitor.ap.write(tr0)
 t2 scoreboard ingest tr0
 t3 coverage ingest tr0
 t4 monitor samples bus -> tr1
 ...

 one passive source, multiple analysis consumers

Key takeaways

  • analysis_port.write() is broadcast fan-out and should be treated as non-blocking ingestion.

  • Keep heavy checker logic out of write() to avoid simulation distortion.

  • Define clear transaction mutation/clone policy across subscribers.

  • Monitor broadcast enables consistent checking and coverage from one observation stream.

Common pitfalls

  • Long-running write() functions that hide performance and timing issues.

  • Subscriber-side mutation of shared objects without clone protection.

  • Assuming analysis broadcast provides ready/ack flow control semantics.

  • Connecting only one checker and forgetting other intended consumers.