DV Methodology

Chapter 6 of 15

Coverage-Driven Verification: Closing the Loop

No new syntax -- what a verification engineer actually does with everything ch1-5 found: merge coverage across a regression, judge each gap as genuine, structurally impossible, or low-value, close what's worth closing, and write down the rest as a waiver instead of silently ignoring it.

Chapters 2-5 taught a language feature each: covergroup syntax, line/branch reports, toggle activity, FSM state and transition tracking. Every one of them, applied to the same real axil_regfile, turned up a real gap — writes to STATUS/COUNT/an unmapped address never tested (ch3), aw_have/w_have never toggled (ch4), AW_ONLY/W_ONLY never visited (ch5). None of that is this chapter's job to repeat. This chapter is what happens after a report exists: merging it across more than one test, judging what each gap actually means, closing what's worth closing, and writing down the rest — the "coverage-driven judgment" this track's own description promises, as opposed to chapters 2-5's coverage-the-language-feature.

Merging across a regression

Every report in ch3-5 came from one test at a time. A real regression runs many tests, and a merged coverage database is the union of all of them — a bin, line, branch, state, or transition counts as covered the moment any test in the regression hit it. This is exactly the scenario ch2 flagged when it distinguished get_coverage() from get_inst_coverage(): merging across a regression is the "more than one instance" case that makes the two genuinely different, made concrete instead of hypothetical.

This site actually has more than one test to merge. Four pieces of test code exist across this project's history, each driving axil_regfile on its own: chapter 1's hand-written directed simulation (uvm-advanced ch1), axi_basic_seq behind axi_smoke_test (uvm-advanced ch2), the default scenario in regfile_virtual_seq (uvm-advanced ch4), and regfile_no_clear_seq (uvm-advanced ch6). Merge their coverage and see what changes:

Merged Coverage -- axil_regfile.sv, 4 tests (uvm-advanced ch1, ch2, ch4, ch6)
==============================================================================
Item                                    ch1 alone   Merged (4 tests)
---------------------------------------  ----------  ------------------
Write to STATUS/COUNT (SLVERR)                   0          0
Write to unmapped address (DECERR)               0          0
ctrl_enable: 1->0 transition                     0          0
aw_have / w_have toggle                          0          0
AW_ONLY / W_ONLY state visits                    0          0
Read of unmapped address (DECERR)                1          2  (ch1, ch2)

Only the last row moves at all, and it was already covered by chapter 1 alone. Every gap ch3-5 found is still exactly where it was. That's worth sitting with: this project genuinely has four tests, written across four different chapters, using three different pieces of test infrastructure (a bare directed testbench, a UVM sequence, a virtual sequence) — and along every axis that matters to the open gaps, they're the same test wearing different clothes. Every one of them writes CTRL with ENABLE=1 and never 0. Every one of them drives awvalid/wvalid together, never staggered. None of them ever writes STATUS, COUNT, or an unmapped address. Regression size isn't regression quality — four tests that all exercise the identical scenario merge down to the coverage of one, and the only way to have noticed that without doing the merge is to have already known each test individually landed on the same paths.

Reading the merged report: what's actually open

Pulling every finding from ch2-5 into one place, against the merged 4-test picture above:

  • Functional (ch2): the addr_x_resp cross's legal STATUS×SLVERR, COUNT×SLVERR, and unmapped×DECERR-on-write bins are all unsampled. None of the cross's illegal_bins ever fired — the DUT's decode logic matches the model.
  • Line/branch (ch3): the write case's STATUS/COUNT arm and default arm both read 0 hits.
  • Toggle (ch4): aw_have/w_have never toggle; ctrl_enable only ever rises; s_axi_wstrb is held constant.
  • FSM (ch5): AW_ONLY/W_ONLY are never entered; BOTH_LATCHED is provably unreachable.

Seven distinct items, once duplicates across metrics are collapsed (the STATUS/COUNT/unmapped-write gap shows up in both the functional cross and the line/branch report; the AW/W-independence gap shows up in both toggle and FSM).

Judging each one: three categories, not two

It's tempting to sort every open item into "close it" or "leave it" — closer to it than that split allows for. Three categories cover this report:

  1. A genuine gap. Reachable, meaningful, and simply never exercised. Writing to STATUS/COUNT/an unmapped address, clearing ctrl_enable, and exercising the AW/W-independence path all belong here — every one of them is a real scenario axil_regfile is built to handle, and none of them has ever been tried.
  2. Structurally unreachable. Not a gap at all — provably impossible given the design, the way BOTH_LATCHED was proven impossible directly from write_fire's own logic (ch5), and the reserved response code 2'b01 can never leave this DUT (ch2). Chasing these isn't thoroughness, it's a wasted exercise; a coverage model should exclude them from its own denominator (illegal_bins, ch2) rather than let them silently suppress the percentage forever.
  3. Reachable, but hollow. s_axi_wstrb sits here, and it's the category most easily missed. It's genuinely closeable — a test could vary it tomorrow and the toggle number would rise — but axil_regfile's write logic never reads it (ch4), so closing that gap wouldn't verify anything real. This isn't a judgment about whether the scenario matters; it's a judgment about whether the design currently has any behavior there to check.

Confusing category 3 for category 1 is how a regression ends up "100% covered" on a signal nobody actually tested anything meaningful about. Confusing category 2 for category 1 is how a coverage goal ends up permanently, pointlessly short of 100% with nobody remembering why.

Closing a real gap: constraints, directed tests, or neither

Not every genuine gap closes the same way, and this project's own test history — one hundred percent directed, from chapter 1 straight through chapter 6's capstone, rand fields on axi_txn declared but never once actually randomized — makes a good case study for why.

Three known corners → directed tests are the efficient fix. STATUS/COUNT/an unmapped write, and a CTRL write with ENABLE=0, are four specific values. Constrained-random exists to explore spaces too large to enumerate by hand; this isn't one of those spaces:

write(8'h04, 32'h0);   // STATUS: writable-check -- expect SLVERR
write(8'h0C, 32'h0);   // COUNT:  writable-check -- expect SLVERR
write(8'h20, 32'h0);   // unmapped -- expect DECERR
write(8'h00, 32'h0);   // CTRL: ENABLE=0 -- closes ctrl_enable's 1->0 gap

Four lines, four gaps closed, each one easy to read and easy to debug if it fails — no constraint solver required for a space this small.

A wider, genuinely combinatorial space → constrained-random is the better tool. If this DUT's address space were sixteen registers instead of four, hand-enumerating every corner the way the four lines above do would stop being efficient. That's where axi_txn's rand bit [7:0] addr — declared since uvm-advanced ch2, never once actually exercised via .randomize() anywhere in this project — would finally earn its keep:

constraint addr_dist_c {
  addr dist {
    8'h00 := 30, 8'h08 := 30,     // the two writable registers, weighted heavily
    8'h04 := 15, 8'h0C := 15,     // STATUS/COUNT -- reachable, currently untested on write
    [8'h10:8'hFF] :/ 10           // anything unmapped
  };
}

This particular gap is small enough that the four directed lines above are the better call today — but the constraint is what the same technique looks like at a scale where enumeration stops being practical, and it would be the first time this project's tests actually called .randomize() at all.

A gap the current test infrastructure has no knob for. The AW/W-independence gap is neither of the above. axi_txn has no field for "delay the W beyond the AW," and axi_basic_seq/regfile_virtual_seq's write() tasks (uvm-advanced ch2, ch4) always issue both halves of a write together — there's no rand variable anywhere in this project whose constraint could be loosened to close this gap, because the axis it needs (independent AW/W timing) was never modeled in the first place. Closing it for real needs new sequence or driver capability — a write() variant that can stagger AW and W by a random or directed number of cycles — not a tweak to something that already exists. That's worth separating from the other two: "we haven't written the test yet" and "we haven't built the thing that could write the test" are different amounts of work, and conflating them is how a real, valuable gap gets estimated as a five-minute fix and then quietly slips every regression cycle.

Writing the judgment down: a coverage waiver, not silence

Every judgment above is worth exactly nothing to the next person who reads this report cold, unless it's written down. A coverage waiver is that record — not a spreadsheet for its own sake, but the difference between "we decided this and here's why" and a percentage that never quite reaches 100%, for reasons nobody remembers by the next regression:

Coverage Waiver Log -- axil_regfile.sv
========================================================================================
# Item                                          Category            Decision
-- ----------------------------------------------  ------------------  --------------------------------
1  Write to STATUS/COUNT (SLVERR)                  Genuine gap         Close now -- directed test
2  Write to unmapped address (DECERR)               Genuine gap         Close now -- directed test
3  ctrl_enable: 1->0 transition                     Genuine gap         Close now -- directed test
4  AW/W arriving on different cycles                Genuine gap         Backlog -- needs new driver/
   (aw_have/w_have, AW_ONLY/W_ONLY)                                     sequence capability, not a
                                                                          quick fix
5  BOTH_LATCHED FSM state                           Unreachable         Excluded permanently -- proven
                                                                          impossible from write_fire
6  RESP reserved value 2'b01                        Unreachable         Excluded permanently -- AXI4-
                                                                          Lite never produces it
7  s_axi_wstrb full toggle range                    Reachable, hollow   Waived -- RTL doesn't read it;
                                                                          revisit only if byte-strobe
                                                                          support is added to the design

Items 1-3 close this regression cycle. Item 4 is real and stays open, on the backlog, with the reason it wasn't a same-day fix written down instead of implied. Items 5 and 6 are permanently excluded, with the proof, so nobody re-derives "is this reachable" from scratch next quarter. Item 7 is waived, not closed and not ignored — with the one condition that would reopen it stated explicitly.

When is this enough?

After closing items 1-3, this DUT's coverage rises — genuinely, not cosmetically, since all three were real gaps and the fixes actually check something. It still won't reach 100%: item 4 stays open on the backlog, items 5 and 6 are permanently excluded from the goal rather than chased, and item 7 sits waived. That's not a regression falling short — chasing item 4 today, before the driver capability to close it exists, would cost real engineering time for a fix that isn't ready to be written yet; chasing items 5 and 6 at all would be chasing something the design guarantees can't happen; chasing item 7 without first deciding whether byte-strobe support belongs in this design at all would produce a green number over an unchecked behavior, exactly the coverage-theater trap ch4 named. "100%" was never the goal on its own — a report where every open item has a written reason is the actual target, and this one, after items 1-3 close, is honestly there.

A verification-feasibility reminder

Same limitation as every chapter in this module: nothing here ran on Icarus Verilog, which implements neither covergroup nor code coverage collection at all (ch1). Every report and every merge in this chapter was built by tracing this site's real test code against the real RTL, the same discipline every chapter in this module has held to. A commercial simulator on EDA Playground is where all of this — the merge, the reports, the waiver log — would come from tooling instead of from reading carefully.

Summary

  • Merging coverage across a regression takes the union of every test's hits — the same "more than one instance" scenario that made get_coverage() differ from get_inst_coverage() (ch2), now applied for real.
  • Merging this project's actual four tests didn't close a single gap ch3-5 found — every test drives the same scenario, just through different infrastructure. Regression size isn't regression quality.
  • An open item is one of three things, not two: a genuine gap (reachable, meaningful, untested), structurally unreachable (provably impossible, exclude it), or reachable-but-hollow (closeable, but the design has no behavior there to check yet).
  • Closing a genuine gap fits the tool to the shape of the gap: a handful of known corners is a directed-test job; a genuinely large space is a constrained-random job; a gap the current test infrastructure has no knob for at all needs new capability before it needs a test.
  • A coverage waiver writes the judgment down — genuine gaps get closed or backlogged, unreachable items get permanently excluded with proof, hollow gaps get waived with the condition that would reopen them — so the next person reading the report doesn't have to re-derive any of it.
  • 100% was never the actual goal; a report where every remaining gap has a written reason is.

Merging coverage across a regression's several tests computes what, exactly?

Merging this site's actual four tests (uvm-advanced ch1, ch2, ch4, ch6) left every gap ch3-5 found exactly where it was, except one read-side item. What does that demonstrate?

s_axi_wstrb's toggle gap is technically closeable -- a test could vary it tomorrow and the number would rise. Why does this chapter still call it 'reachable, but hollow' rather than a genuine gap?

Why can't the AW/W-independence gap be closed just by adjusting an existing constraint on axi_txn, the way the STATUS/COUNT/unmapped-write gap could be?