Part 4 · TLM & Analysis · Intermediate

Debug Macros and Tools: uvm_tlm_if and Verbosity Control

Tooling for TLM diagnosis: endpoint-level traces, uvm_tlm_if-style interfaces, focused report IDs, and runtime verbosity controls.

Instrumentation principles

Effective TLM debug requires targeted visibility: publish points, consume points, and translation boundaries should emit concise traces with stable IDs.

Avoid global noise by using report IDs and component-local verbosity controls. Keep trace content structured so logs can be diffed across seeds.

diagram
[UVM][TLM] instrumentation layers

Layer 1: structural
  print_topology / print_connections

Layer 2: flow
  publish + consume + queue depth logs

Layer 3: semantics
  payload fields and comparison decisions

enable progressively to control noise
diagram
[UVM] recommended report IDs

TLM_FLOW   - publish/consume events
TLM_TYPE   - type/cast info
TLM_QUEUE  - fifo depth and timing
TLM_AUDIT  - structural checks
  • Use layered instrumentation to scale from quick checks to deep forensics.

  • Prefer stable, grep-friendly report IDs over ad-hoc free text.

  • Turn on only the minimum verbosity needed for current hypothesis.


Using uvm_tlm_if-style hooks and endpoint wrappers

Projects often wrap TLM interactions behind interface-like helpers to centralize logging and policy checks. This keeps endpoint code consistent.

systemverilog
class tlm_trace_if #(type T = uvm_sequence_item) extends uvm_object;
  `uvm_object_param_utils(tlm_trace_if#(T))

  function new(string name = "tlm_trace_if");
    super.new(name);
  endfunction

  virtual function void on_publish(string src, T t);
    `uvm_info("TLM_FLOW",
      $sformatf("publish src=%s type=%s", src, t.get_type_name()), UVM_HIGH)
  endfunction

  virtual function void on_consume(string dst, T t);
    `uvm_info("TLM_FLOW",
      $sformatf("consume dst=%s type=%s", dst, t.get_type_name()), UVM_HIGH)
  endfunction
endclass
systemverilog
class tracing_monitor extends uvm_component;
  `uvm_component_utils(tracing_monitor)
  uvm_analysis_port #(txn_item) ap;
  tlm_trace_if #(txn_item) tr_if;

  function void publish(txn_item t);
    if (tr_if != null) tr_if.on_publish(get_full_name(), t);
    ap.write(t);
  endfunction
endclass
diagram
[TLM] wrapper benefits

single policy point for:
  - connection guards
  - type traces
  - payload digests
  - drop counters

result:
  less duplicated debug code across components
  • Interface-style wrappers keep debug behavior consistent across endpoints.

  • Hook publish/consume events without changing every sink implementation.

  • Use wrappers to inject policy toggles from config_db.


Verbosity control for focused TLM tracing

UVM verbosity should be tuned to the failing flow, not increased globally for all components. Set per-ID and per-component where possible.

systemverilog
function void start_of_simulation_phase(uvm_phase phase);
  super.start_of_simulation_phase(phase);

  // Keep default noise low.
  uvm_top.set_report_verbosity_level_hier(UVM_LOW);

  // Raise only for selected components/IDs in debug mode.
  if ($test$plusargs("TLM_TRACE")) begin
    mon.set_report_id_verbosity("TLM_FLOW", UVM_HIGH);
    sb.set_report_id_verbosity("TLM_FLOW", UVM_HIGH);
    sb.set_report_id_verbosity("TLM_TYPE", UVM_HIGH);
  end
endfunction
diagram
[UVM] runtime knobs

+UVM_VERBOSITY=UVM_MEDIUM
  raises global level

+uvm_set_verbosity=<comp>,<id>,<verbosity>,<phase>
  targeted control for noisy IDs

+TLM_TRACE
  project-local plusarg for enabling TLM wrappers
systemverilog
`define TLM_DBG(ID, FMT, ARG...)   `uvm_info(ID, $sformatf(FMT, ##ARG), UVM_HIGH)

function void write(txn_item t);
  `TLM_DBG("TLM_FLOW", "recv name=%s id=%0d", t.get_name(), t.id)
  // checker logic...
endfunction
  • Default to low verbosity and elevate only targeted IDs.

  • Gate deep traces behind explicit plusargs for reproducibility.

  • Prefer macros/helpers to standardize trace message structure.


Tool-assisted triage workflow

Combine structural printouts, flow IDs, and type IDs into a fixed triage script so every engineer debugs missing transactions the same way.

diagram
[UVM][TLM] 5-step tooling flow

1) print_connections for producers/sinks
2) enable TLM_AUDIT and TLM_FLOW at UVM_HIGH
3) run one deterministic transaction
4) inspect TLM_TYPE and cast diagnostics
5) downgrade verbosity once fault is found
diagram
[TLM] expected log chain (single transaction)

TLM_AUDIT : mon.ap subscribers=2
TLM_FLOW  : publish src=env.agt.mon type=pkt_axi
TLM_FLOW  : consume dst=env.sb type=pkt_axi
TLM_QUEUE : depth after push=1
TLM_CHECK : compare passed txn_id=42

Key takeaways

  • Debug wrappers and report IDs make TLM behavior observable without chaos.

  • uvm_tlm_if-style helper hooks centralize trace and policy logic.

  • Per-ID verbosity targeting is critical for scalable debug.

  • Use a repeatable tool-assisted workflow for consistent triage quality.

Common pitfalls

  • Turning global UVM_HIGH on permanently and drowning useful signals.

  • Embedding custom trace strings without IDs, making CI parsing brittle.

  • Copy-pasting instrumentation differently across monitors/sinks.

  • Debugging without deterministic single-transaction runs.