Coverage-Driven Verification
Use functional coverage to measure verification completeness and close coverage holes systematically.
Coverage-Driven Verification
Coverage-driven verification (CDV) uses functional coverage metrics to decide when verification is complete. Instead of running a fixed number of tests, you run until coverage goals are met.
The CDV Flow
textDefine Coverage Model │ ▼ Run Constrained Random Tests │ ▼ Measure Coverage ──▶ Met target? ──▶ Done! ✅ │ │ │ No │ Yes ▼ │ Analyze Holes │ │ │ ▼ │ Adjust Constraints ───────┘ or Write Directed Tests
Writing a Coverage Collector
systemverilogclass apb_coverage extends uvm_subscriber #(apb_txn); `uvm_component_utils(apb_coverage) apb_txn txn; covergroup apb_cg; // Cover address ranges addr_cp: coverpoint txn.addr[31:28] { bins low = {[0:3]}; bins mid = {[4:7]}; bins high = {[8:15]}; } // Cover read vs write dir_cp: coverpoint txn.write { bins read = {0}; bins write = {1}; } // Cover special data patterns data_cp: coverpoint txn.data { bins zero = {0}; bins all_ones = {32'hFFFF_FFFF}; bins walking1 = {32'h0000_0001, 32'h0000_0002, 32'h0000_0004, 32'h0000_0008}; bins other = default; } // Cross: every address range × every direction addr_x_dir: cross addr_cp, dir_cp; endgroup function new(string name, uvm_component parent); super.new(name, parent); apb_cg = new(); endfunction // Called automatically for each broadcast transaction function void write(apb_txn t); txn = t; apb_cg.sample(); endfunction function void report_phase(uvm_phase phase); real cov = apb_cg.get_coverage(); `uvm_info("COV", $sformatf("Coverage: %.1f%%", cov), UVM_NONE) endfunction endclass
Connecting to the Monitor
systemverilog// In environment's connect_phase: agent.monitor.ap.connect(coverage.analysis_export);
Coverage Strategies
Transaction-Level Coverage
systemverilogcovergroup txn_cg; op_cp: coverpoint txn.op_type { bins read = {READ}; bins write = {WRITE}; bins rmw = {RMW}; } size_cp: coverpoint txn.size { bins byte_ = {1}; bins half = {2}; bins word = {4}; } // Cross: every operation × every size op_x_size: cross op_cp, size_cp; endgroup
Error Scenario Coverage
systemverilogcovergroup error_cg; resp_cp: coverpoint txn.response { bins okay = {OKAY}; bins slverr = {SLVERR}; bins decerr = {DECERR}; } endgroup
Protocol-Level Coverage
systemverilogcovergroup axi_protocol_cg; burst_cp: coverpoint txn.burst_type { bins fixed = {FIXED}; bins incr = {INCR}; bins wrap = {WRAP}; } len_cp: coverpoint txn.burst_len { bins single = {0}; bins short_ = {[1:3]}; bins medium = {[4:15]}; bins long_ = {[16:255]}; } burst_x_len: cross burst_cp, len_cp { // WRAP only supports 2, 4, 8, or 16 beats ignore_bins invalid = binsof(burst_cp) intersect {WRAP} && binsof(len_cp) intersect {[2:$]}; } endgroup
Closing Coverage Holes
When random tests don't hit all bins, you have two options:
Option 1: Adjust Constraints
systemverilog// Bias toward uncovered scenarios constraint bias_c { burst_type dist { WRAP := 50, // Increase weight for wrap INCR := 30, FIXED := 20 }; }
Option 2: Write Directed Sequences
systemverilogclass wrap_burst_seq extends uvm_sequence #(axi_txn); `uvm_object_utils(wrap_burst_seq) function new(string name = "wrap_burst_seq"); super.new(name); endfunction task body(); axi_txn txn = axi_txn::type_id::create("txn"); start_item(txn); if (!txn.randomize() with { burst_type == WRAP; burst_len inside {1, 3, 7, 15}; }) `uvm_fatal("RAND", "Failed to randomize") finish_item(txn); endtask endclass
Best Practices
| Practice | Why |
|---|---|
| Define coverage goals before writing tests | Focus your effort |
| Use cross coverage | Catches interaction bugs |
| Set realistic targets (90-95%) | 100% is rarely needed |
| Review exclusions carefully | Don't exclude real scenarios |
| Automate regression + coverage merging | Track progress over time |