The short answer: In UVM, functional coverage usually lives in a coverage collector that extends uvm_subscriber and is connected to a monitor’s analysis port, so it samples covergroups on every observed transaction. Assertions usually live in the SystemVerilog interface (or are bound to the RTL), where they check protocol rules on every clock in both active and passive mode. Make assertion failures report through `uvm_error so they count in the UVM summary, and let configuration objects switch coverage on or off.
Coverage and assertions are covered in depth in my SystemVerilog posts on functional coverage and assertions. This post is about where they fit in a UVM testbench. It combines my older posts on UVM coverage and UVM assertions.
- Where they belong
- A coverage collector
- Coverage for DUT-level scenarios
- Assertions in the interface
- Switching checks off when needed
- Closing coverage in a UVM project
Table of Contents
Part 1: Where they belong

Part 2: A coverage collector
class bus_cov extends uvm_subscriber #(bus_item);
`uvm_component_utils(bus_cov)
bus_item t;
covergroup cg;
cp_kind: coverpoint t.kind;
cp_addr: coverpoint t.addr[15:12] { bins region[] = {[0:15]}; }
cp_kind_x_region: cross cp_kind, cp_addr;
endgroup
function new(string name, uvm_component parent);
super.new(name, parent);
cg = new();
endfunction
function void write(bus_item t);
this.t = t;
cg.sample();
endfunction
endclass
// env: only build it if coverage is enabled
if (cfg.has_coverage) begin
cov = bus_cov::type_id::create("cov", this);
end
...
if (cfg.has_coverage) agt.mon.ap.connect(cov.analysis_export);
Covergroups declared inside a class must be constructed in the class constructor, as above.
Part 3: Coverage for DUT-level scenarios
Agent-level coverage tells you what happened on each interface. Scenario coverage (combinations across interfaces, register settings during traffic, error cases) usually lives in the environment, often in the scoreboard or a dedicated coverage component that subscribes to several monitors and the register model.
Part 4: Assertions in the interface
interface bus_if (input logic clk, rst_n);
import uvm_pkg::*;
`include "uvm_macros.svh"
logic valid, ready, write;
logic [31:0] addr, wdata, rdata;
a_stable: assert property (@(posedge clk) disable iff (!rst_n)
valid && !ready |=> valid && $stable(addr))
else `uvm_error("BUS_IF", "addr changed while valid and not ready")
a_known: assert property (@(posedge clk) disable iff (!rst_n) !$isunknown(valid))
else `uvm_error("BUS_IF", "valid is X")
endinterface
Reporting through UVM means assertion failures count in the final error total and respect UVM’s report controls.
Part 5: Switching checks off when needed
Some tests deliberately break the protocol, such as error-injection tests. Add a checks_enabled bit to the interface (set from the test through the virtual interface) and include it in disable iff, rather than deleting assertions.
Part 6: Closing coverage in a UVM project
- Merge coverage from every seed and test in the regression.
- Review holes per agent and per scenario, and decide: tune constraints, add a directed sequence, or exclude with a written reason.
- Track coverage over time; a sudden drop usually means a broken sequence or a configuration change.
Practise this on VLSI Forge
I built VLSI Forge so you can write RTL in your browser, run it on a real simulator and check every signal in the waveform. Free, nothing to install.
Previous in my UVM series: Scoreboards. Next: The register model (RAL).
FAQ
Where should functional coverage go in UVM?
In a coverage collector that extends uvm_subscriber and receives transactions from a monitor’s analysis port, plus environment-level coverage for cross-interface scenarios.
Where should assertions go in UVM?
In the SystemVerilog interface for protocol rules, or bound to the RTL for internal checks, so they run in every mode.
How do assertion failures show up in UVM reports?
Use `uvm_error or `uvm_fatal in the assertion’s else action block, so they’re counted by the UVM report server.
Can coverage be turned off for some tests?
Yes. Put a has_coverage field in the agent or env configuration and only build and connect the collector when it’s set.
