Part 4 · TLM & Analysis · Intermediate
Multiple Subscribers: One Monitor, Many Analysis Consumers
Scaling fan-out from one monitor to many consumers, organization patterns, performance cautions, and practical governance of analysis networks.
One producer, N consumers is normal
Large environments commonly attach many consumers to one monitor stream: scoreboard, functional coverage, latency tracker, protocol checker, audit logger, and predictor.
This is exactly where analysis broadcast shines. The monitor remains singular and protocol-focused while consumers evolve independently.
[MON][TLM][CHECK] scaled fan-out example
[MON] axi_monitor.ap
│
├─► scoreboard
├─► functional coverage
├─► latency metrics
├─► protocol assertions subscriber
├─► trace logger
└─► predictor
one observation source feeds many analysis needsfunction void connect_phase(uvm_phase phase);
agt.mon.ap.connect(sb.in);
agt.mon.ap.connect(cov.analysis_export);
agt.mon.ap.connect(lat.analysis_export);
agt.mon.ap.connect(proto_chk.analysis_export);
agt.mon.ap.connect(trace.analysis_export);
agt.mon.ap.connect(pred.bus_in);
endfunctionAnalysis fan-out reduces duplicate monitor implementations.
Each consumer can be enabled/disabled independently by configuration.
Monitor remains reusable because consumer policies live elsewhere.
Performance and maintainability when subscribers grow
As consumer count rises, throughput concerns shift to consumer write() cost and logging volume. Keep each consumer lightweight and avoid redundant heavy transforms.
[CHECK] scaling pressure points
CPU cost:
many write() handlers called per observed transaction
memory cost:
multiple consumers cloning and queuing every item
debug cost:
noisy logs from every subscriber on every sample
mitigation:
short write(), selective logging, filtered subscribers[MON][TLM] subscriber budget heuristic
for each subscriber:
- constant-time write() preferred
- avoid per-sample expensive formatting
- gate detailed logs by verbosity or sampled windows
if heavy work required:
enqueue and process asynchronouslyfunction void write(my_txn t);
if (!enabled) return;
if (sample_ratio > 1 && (sample_count % sample_ratio) != 0) begin
sample_count++;
return;
end
sample_count++;
q.push_back(t.clone());
endfunctionSubscriber count is cheap; heavy subscriber behavior is expensive.
Use enable/sample controls for non-critical analytics consumers.
Move expensive checks to dedicated threads when possible.
Topology organization patterns
When many subscribers exist, explicit grouping and naming conventions keep connect_phase readable and reduce miswires.
[TLM] organization pattern
monitor domain:
agt0.mon.ap
agt1.mon.ap
consumer domains:
checkers/
coverage/
debug/
modeling/
connect helpers:
connect_checkers()
connect_coverage()
connect_debug()function void connect_checkers();
agt.mon.ap.connect(sb.in);
agt.mon.ap.connect(proto_chk.analysis_export);
endfunction
function void connect_coverage();
agt.mon.ap.connect(cov.analysis_export);
agt.mon.ap.connect(lat_cov.analysis_export);
endfunction
function void connect_debug();
if (cfg.enable_trace) agt.mon.ap.connect(trace.analysis_export);
endfunction[CHECK] naming guidance
good:
sb_actual_in, cov_apb_main, trace_axi_rx
weak:
in0, in1, streamA, streamB
semantic names reduce accidental swapsUse helper connect functions when fan-out grows large.
Separate core checkers from optional debug subscribers.
Prefer semantic endpoint names over numeric placeholders.
Walkthrough: introducing a new subscriber safely
When adding a new consumer to an existing monitor stream, validate incrementally: connection, ingestion counters, and no regressions in existing consumers.
[MON][CHECK] safe add procedure
1) add new subscriber component in build_phase
2) connect monitor.ap to new analysis endpoint
3) add ingestion counter in subscriber write()
4) run smoke test and confirm counter > 0
5) ensure existing scoreboard/coverage outputs unchangedtask verify_new_subscriber(my_env env);
int old_sb = env.sb.actual_seen;
int old_cov = env.cov.sample_count;
run_smoke_sequence();
if (env.new_sub.sample_count == 0)
`uvm_error("NEW_SUB", "new subscriber saw no traffic")
if (env.sb.actual_seen < old_sb || env.cov.sample_count < old_cov)
`uvm_warning("REGRESSION", "existing consumers look stalled")
endtaskKey takeaways
One monitor feeding many consumers is the intended analysis architecture at scale.
Scale safely by keeping write() lightweight and organizing connections clearly.
Use semantic names and grouped connect helpers to prevent wiring errors.
Add new subscribers with simple ingestion counters and regression smoke checks.
Common pitfalls
Adding many consumers with noisy per-sample logs and hurting runtime.
Hiding optional debug subscribers among mandatory checker wiring.
Using ambiguous endpoint names that cause channel swap mistakes.
Introducing new consumers without verifying existing ones still ingest correctly.