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_respcross's legalSTATUS×SLVERR,COUNT×SLVERR, andunmapped×DECERR-on-write bins are all unsampled. None of the cross'sillegal_binsever fired — the DUT's decode logic matches the model. - Line/branch (ch3): the write case's
STATUS/COUNTarm anddefaultarm both read 0 hits. - Toggle (ch4):
aw_have/w_havenever toggle;ctrl_enableonly ever rises;s_axi_wstrbis held constant. - FSM (ch5):
AW_ONLY/W_ONLYare never entered;BOTH_LATCHEDis 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:
- A genuine gap. Reachable, meaningful, and simply never exercised. Writing to
STATUS/COUNT/an unmapped address, clearingctrl_enable, and exercising the AW/W-independence path all belong here — every one of them is a real scenarioaxil_regfileis built to handle, and none of them has ever been tried. - Structurally unreachable. Not a gap at all — provably impossible given the design, the way
BOTH_LATCHEDwas proven impossible directly fromwrite_fire's own logic (ch5), and the reserved response code2'b01can 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. - Reachable, but hollow.
s_axi_wstrbsits 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 — butaxil_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 gapFour 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 fromget_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?