Part 8 · Checking & Coverage · Intermediate

Coverage Subscriber Setup

uvm_subscriber covergroup class, analysis_export wiring, and per-instance coverage.

uvm_subscriber pattern

A coverage subscriber extends uvm_subscriber#(txn_type). UVM provides the analysis_export and calls your write() function for every transaction the monitor emits. Inside write(), you sample a covergroup with the transaction fields as arguments.

diagram
Legend: [COVER] [UVM]

  COVERGROUP ANATOMY

  covergroup cg with function sample(bus_txn t);
    │
    ├─ cp_dir : coverpoint t.write     ← scalar field  bins
    │     bins read  = {0};
    │     bins write = {1};
    │
    ├─ cp_len : coverpoint t.len       ← range field  named bins
    │     bins single = {1};
    │     bins small  = {[2:4]};
    │     bins large  = {[5:16]};
    │
    └─ x_dir_len : cross cp_dir, cp_len  ← combination bin
          bins wr_4 = binsof(cp_dir.write) && binsof(cp_len.small);

  option.per_instance = 1;   ← separate coverage per subscriber instance
  option.name         = "bus_cov";  ← appears in coverage report

Complete bus_cov subscriber

systemverilog
class bus_cov extends uvm_subscriber #(bus_txn);
  `uvm_component_utils(bus_cov)
  bus_txn tr;
  bit in_reset;

  covergroup cg with function sample(bus_txn t);
    option.per_instance = 1;
    option.name         = "bus_cov";

    cp_dir : coverpoint t.write {
      bins read  = {0};
      bins write = {1};
    }
    cp_len : coverpoint t.len {
      bins single = {1};
      bins small  = {[2:4]};
      bins large  = {[5:16]};
    }
    x_dir_len : cross cp_dir, cp_len;
  endgroup

  function new(string name, uvm_component parent);
    super.new(name, parent);
    cg = new();
  endfunction

  function void write(bus_txn t);
    if (in_reset) return;
    if (t.kind != RESPONSE) return;
    cg.sample(t);
  endfunction

  function void report_phase(uvm_phase phase);
    super.report_phase(phase);
    `uvm_info("COV", $sformatf("%s coverage %0.1f%%",
      get_full_name(), cg.get_inst_coverage()), UVM_LOW)
  endfunction
endclass

Env wiring and per-instance coverage

connect_phase

systemverilog
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  dut_agt = bus_agent::type_id::create("dut_agt", this);
  cov       = bus_cov::type_id::create("cov", this);
endfunction

function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  // [COVER] monitor → subscriber (NOT driver)
  dut_agt.mon.ap.connect(cov.analysis_export);
endfunction

Code walkthrough

  1. extends uvm_subscriber#(bus_txn) — UVM provides analysis_export and write() dispatch.

  2. covergroup with function sample(bus_txn t) — t is the argument passed to cg.sample(t).

  3. option.per_instance = 1 — each subscriber instance gets its own coverage database entry.

  4. write() gates on in_reset and t.kind — see Sampling Discipline sub-topic.

  5. report_phase prints get_inst_coverage() — per-instance % for sign-off.

diagram
MULTI-AGENT PER-INSTANCE COVERAGE [COVER]

  cpu_agt.mon.ap ──► cpu_cov.analysis_export   cpu_cov.cg (instance 1)
  dma_agt.mon.ap ──► dma_cov.analysis_export   dma_cov.cg (instance 2)

  option.per_instance = 1  separate closure tracking per agent
  Merge across seeds AND instances for full sign-off

Key takeaways

  • Subscriber extends uvm_subscriber#(txn) — write() samples covergroup.

  • Connect monitor.ap to analysis_export — never driver.

  • option.per_instance = 1 for multi-agent environments.

Common pitfalls

  • Connecting driver seq_item_port — samples intent, not DUT behavior.

  • Forgetting cg = new() in constructor — null covergroup crash.

  • No in_reset gate — reset-phase transactions pollute closure.