PCIe · Module 24
Coverage — Evidence About What You Did Not Test
Baseline random traffic reached 12 of 16 bins, and every one of the four it missed was an ownership-boundary cross — which is exactly where every measured defect in this curriculum lives.
Chapter 24.2 kept raising a question it could only answer one property at a time: was the antecedent ever reached? It measured a property passing with 60,205 antecedent hits under one stimulus and 0 under another — both reported as passes.
Coverage answers that question for the whole campaign. And its most useful output is not a percentage; it is a list of things that never happened.
1. Sources, Scope, and What the Language Tracks Own
2. Coverage Measures Reach
The distinction is owned by the UVM track and restated here in one line because everything else depends on it:
Coverage tells you what the campaign exercised. It never tells you whether the DUT was right.
The PCIe-specific consequence is worth stating precisely. A campaign can reach 100% of a well-designed coverage model and still miss a defect — because the model only asks about states someone thought of. And a campaign at 75% with the right four holes has told you more than one at 99% whose holes are in behaviour the DUT does not implement (§12).
So this chapter measures coverage's value by a different question: does a hole in this model correspond to a defect class that actually occurred in this curriculum? §14 shows all four baseline holes doing exactly that.
3. The Plan Comes From Risk, Not From Syntax
Write the risks first, in prose, before any covergroup. For a PCIe requester the list is short and it is derived from where defects were measured:
| Risk | Why it matters | Measured at |
|---|---|---|
| small transfers dominate | packet overhead swamps payload | 22.5 §4 — 20% efficient at 4 B |
| deep outstanding | identity and reuse bugs appear only above depth 1 | 23.3 §14 — 62.6% |
| split Completions | retirement-on-first-fragment | 23.5 §12 — 80.8% |
| credit exhaustion | the last-credit race | 22.3 §11 — 65.6% |
| backpressure at the final beat | stability and progress bugs | 23.4 §12 — 79.1% |
| reset with work outstanding | stale context, epoch handling | 24.3 §12 — 4,692 stale |
| same-cycle free and allocate | bitmap next-state | 23.3 §14 — 6.3% |
| link leaves the operational state | everything above becomes unprovable | 24.1 §3 |
Every one of those becomes a coverpoint or a cross in §5–§10. And the ordering matters: the last four are coincidences, not values, which is why §10 argues that the crosses carry more information than the coverpoints.
4. Sample on the Transfer
5. Coverpoint — TLP Class
// UVM / VERIFICATION-ONLY. Bins for the classes this DUT implements.
// SPEC-DEFINED classes (12.1, 12.2, 10.2); which ones are relevant is a
// configuration question (§12).
covergroup cg_tlp_class with function sample(pcie_txn_e k);
option.per_instance = 1;
cp_class: coverpoint k {
bins mem_rd = {TLP_MEM_RD}; // Non-Posted
bins mem_wr = {TLP_MEM_WR}; // Posted
bins cpl_d = {TLP_CPL_D};
bins cpl = {TLP_CPL}; // status-only completion
illegal_bins never = {TLP_UNKNOWN}; // §11: illegal only where truly illegal
}
endgroupTwo decisions embedded here.
No bin for classes this DUT does not implement. A requester that never emits Configuration requests should not carry a permanently-empty cfg_wr bin — that is a manufactured hole that will be waived, and waiving trains people to waive (§12).
And illegal_bins is used only for a value that cannot legally occur in this abstraction — an unmapped enum. It is not used for "we do not expect this"; §11 explains why that belongs in an assertion.
6. Coverpoint — Size, in Buckets
// ILLUSTRATIVE bucket boundaries -- chosen from the risks in §3, and
// parameterized so a different MPS/MRRS configuration re-bins itself.
covergroup cg_size with function sample(int unsigned bytes);
option.per_instance = 1;
cp_size: coverpoint bytes {
bins tiny = {[1:15]}; // header dominates (22.5 §4)
bins small = {[16:63]};
bins medium = {[64:255]};
bins large = {[256:1023]};
bins max_pay = {[1024:MAX_PAYLOAD]};
bins boundary = {MAX_PAYLOAD}; // exactly at the limit
bins unaligned_tail = {[1:MAX_PAYLOAD]} with (item % 4 != 0);
}
endgroupOne bin per byte value would produce thousands of bins and no information. The buckets exist because each corresponds to a different failure mode: tiny exercises the overhead regime, boundary exercises the exact-limit arithmetic, and unaligned_tail exercises the partial final beat that Chapter 23.4 §12 measured injecting 15.5 phantom bytes per packet when the byte-valid mask is wrong.
unaligned_tail is the bin most often missing from a real model, because round-numbered stimulus never produces it.
7. Coverpoint — Outstanding Depth
// Bins tied to the PARAMETER, never hardcoded -- a DUT with 4 tags and one
// with 64 need the same shape and different boundaries.
covergroup cg_depth with function sample(int unsigned depth);
option.per_instance = 1;
cp_depth: coverpoint depth {
bins zero = {0};
bins one = {1};
bins low = {[2:(MAX_TAGS/4)]};
bins mid = {[(MAX_TAGS/4)+1:(MAX_TAGS*3)/4]};
bins near_full = {[((MAX_TAGS*3)/4)+1:MAX_TAGS-1]};
bins full = {MAX_TAGS}; // exhaustion (22.3 §5)
}
endgroupone gets its own bin deliberately. Depth 1 is where a FIFO scoreboard is accidentally correct (24.3 §4), where a single "current transaction" register works (23.5 §3), and where a shared counter is right (23.3 §4). A campaign that never leaves depth 1 verifies none of those, and separating the bin makes that visible in the report rather than hidden inside low.
full matters for the opposite reason. Tag exhaustion is the condition under which Chapter 23.3 §14's leak wedges the engine — and a campaign that never exhausts Tags cannot observe a leak at all.
8. Coverpoint — Credit Pressure
// Per class, because starvation is per class (22.3 §3). Combining the six
// pools into one "credit" coverpoint is mutation 10.
covergroup cg_credit with function sample(fc_class_e c, int unsigned avail);
option.per_instance = 1;
cp_class: coverpoint c { bins posted = {FC_P}; bins nonposted = {FC_NP};
bins completion = {FC_CPL}; }
cp_level: coverpoint avail {
bins healthy = {[8:$]};
bins low = {[2:7]};
bins one_left = {1}; // the last-credit race (22.3 §11)
bins zero = {0}; // blocked
}
x_class_level: cross cp_class, cp_level;
endgroupone_left is the bin that earns the covergroup. Chapter 22.3 §11 measured producers collectively over-wanting in 65.6% of loaded cycles — but only under load. A campaign whose credit coverpoint sits in healthy for the whole run has not tested the reservation manager at all, and every credit property in 24.2 §10 is vacuous.
The cross is small and deliberate: 3 × 4 = 12 bins, all meaningful. §10 explains why crossing this with three more coverpoints would not be.
9. Transition Coverage — Link State
// SPEC-DEFINED that L0 is operational (18.6). The TRANSITIONS covered here
// are limited to ones this chapter can state safely -- entering and leaving
// the operational state. Module 18 owns the state machine; a fabricated
// transition bin is worse than none (24.2 §12).
covergroup cg_link with function sample(link_state_e s);
option.per_instance = 1;
cp_state: coverpoint s {
bins operational = {LS_L0};
bins recovering = {LS_RECOVERY};
bins down = {LS_DETECT, LS_POLLING, LS_CONFIG};
// Only transitions through the operational boundary.
bins enter_l0 = (LS_RECOVERY, LS_CONFIG => LS_L0);
bins leave_l0 = (LS_L0 => LS_RECOVERY);
}
endgroupTwo restraints, both deliberate.
No auto-bins. coverpoint s with automatic transition bins over a full LTSSM enumeration produces a combinatorial set including transitions the specification does not permit — and the resulting holes are unclosable and meaningless.
And leave_l0 is the high-value bin. Chapter 24.1 §3 established that a Link leaving the operational state makes the layers above unprovable; a campaign that never leaves L0 has never tested the recovery path of anything.
10. The Crosses That Carry the Information
11. Illegal Bins Are Not Assertions
illegal_bins and assert property answer different questions, and using one for the other weakens both.
| Use | For |
|---|---|
assert property | this must never happen — a DUT obligation with a temporal context |
illegal_bins | this value cannot occur in this abstraction — a modelling statement |
ignore_bins | this combination is out of scope for this configuration (§12) |
The PCIe example. "A Completion must never arrive for an unknown identity" is a property (24.2 §9, P23) — it has an antecedent, a temporal relationship, and a failure trace. Encoding it as an illegal bin gives you a coverage report entry with no trace and no cycle number, and its behaviour on hit is tool-dependent.
illegal_bins earns its place for enum hygiene: an unmapped pcie_txn_e value means the monitor decoded something it does not model. That is a modelling error, and a coverage report is the right place for it.
And ignore_bins requires a written justification (§12). Using it to remove a hole that is merely hard to reach is how a model becomes green and useless — mutation 5, and the most common form of coverage dishonesty.
12. Coverage Must Know the Configuration
13. The Coverage Path
Four things to read out of the figure.
Coverage and the scoreboard share monitors and nothing else. There is no edge between them in either direction — a coverage subscriber that influenced expected results would make both wrong together, and one that read the scoreboard's verdict would stop being a measure of stimulus.
Four collectors, not one. A hole in ownership crosses and a hole in transaction coverage mean completely different things, and merging them into one percentage destroys that.
The configuration feeds resource coverage. Depth bins are derived from the DUT's Tag count (§12), so a smaller device does not manufacture holes.
And nothing points back at the DUT. Coverage observes; §15's P16 asserts it.
14. Measured Behaviour
15. Infrastructure Checks
// P1: coverage samples once per accepted transaction. §14: sampling on
// `valid` inflated the count 1.67x on identical traffic.
property p_sample_on_transfer;
@(posedge clk) disable iff (!rst_n)
(cvg_sample_count != $past(cvg_sample_count)) |-> $past(tx_valid && tx_ready);
endproperty
// P2: a stalled transaction is sampled once, not once per stall cycle.
property p_no_sample_during_stall;
@(posedge clk) disable iff (!rst_n)
(tx_valid && !tx_ready) |=> $stable(cvg_sample_count);
endproperty
// P3: the sampled class enum is always a modelled value; anything else is
// a monitor decode error (§11).
property p_class_enum_valid;
@(posedge clk) disable iff (!rst_n)
cvg_sample |-> (sampled_class inside {TLP_MEM_RD, TLP_MEM_WR, TLP_CPL_D, TLP_CPL});
endproperty
// P4: sampled depth never exceeds the configured Tag count (§12).
property p_depth_within_config;
@(posedge clk) disable iff (!rst_n)
cvg_sample |-> (sampled_depth <= cfg_max_tags);
endproperty
// P5: sampled size never exceeds the configured maximum payload.
property p_size_within_config;
@(posedge clk) disable iff (!rst_n)
cvg_sample |-> (sampled_bytes <= cfg_max_payload);
endproperty
// P6: the credit covergroup exists only when credits are observable.
property p_credit_cvg_gated;
@(posedge clk) disable iff (!rst_n)
(!cfg_has_credit_visibility) |-> (credit_sample_count == 0);
endproperty
// P7: a transition is sampled once per state CHANGE, not per cycle in state.
property p_transition_sampled_once;
@(posedge clk) disable iff (!rst_n)
(link_state == $past(link_state)) |=> $stable(link_transition_count);
endproperty
// P8: the link state is sampled from a registered value, never combinationally.
property p_state_sampled_registered;
@(posedge clk) disable iff (!rst_n)
cvg_sample |-> (sampled_state == link_state_q);
endproperty
// P9: no transaction is sampled twice by two collectors from one observation.
property p_no_duplicate_sampling;
@(posedge clk) disable iff (!rst_n)
obs_published |=> (total_samples == $past(total_samples) + n_collectors);
endproperty
// P10: the observation object is not mutated after publication (24.3 §10).
property p_observation_immutable;
@(posedge clk) disable iff (!rst_n)
obs_published |=> $stable(obs_contents);
endproperty
// P11: a cross bin cannot be hit unless every constituent condition held.
property p_cross_requires_all_conditions;
@(posedge clk) disable iff (!rst_n)
x_deep_split_hit |-> (sampled_depth > 1) && sampled_was_split;
endproperty
// P12: the final-beat cross requires an actual stall on the final beat.
property p_final_stall_cross_real;
@(posedge clk) disable iff (!rst_n)
x_final_stalled_hit |-> (sampled_final_beat && sampled_stalled);
endproperty
// P13: the reset cross requires outstanding work at the reset.
property p_reset_cross_real;
@(posedge clk) disable iff (!rst_n)
x_reset_deep_hit |-> (sampled_in_reset && (sampled_depth > 0));
endproperty
// P14: coverage state is reset-generation aware, so post-reset samples are
// not attributed to the pre-reset epoch.
property p_coverage_epoch_valid;
@(posedge clk) disable iff (!rst_n)
cvg_sample |-> (sampled_epoch == current_epoch);
endproperty
// P15: samples are flushed before the test's final report.
property p_flush_before_report;
@(posedge clk) disable iff (!rst_n)
report_phase_active |-> (pending_samples == 0);
endproperty
// P16: the coverage subscriber never drives the DUT or the scoreboard.
property p_coverage_non_functional;
@(posedge clk) disable iff (!rst_n)
$stable({tx_valid, tx_ready, sb_expected_count}) or !$stable(cvg_sample_count);
endproperty
// P17: an ignore_bins exclusion is accompanied by a recorded justification
// (checked structurally by the closure script, asserted here as intent).
property p_ignore_bins_justified;
@(posedge clk) disable iff (!rst_n)
ignore_applied |-> justification_present;
endproperty
// P18: an illegal bin hit is reported, never silently counted.
property p_illegal_reported;
@(posedge clk) disable iff (!rst_n)
illegal_bin_hit |=> illegal_reported;
endpropertyEighteen checks. P1, P2 and P9 are the sampling contract — the 1.67× result made concrete. P11–P13 are unusual and worth stealing: they assert that a cross bin cannot be credited unless every condition genuinely held, which catches a collector wired to the wrong signal and reporting closure it did not achieve.
16. Closure — and the Missing 5%
17. Verification — Mutations
| # | Mutation | Symptom | Caught by |
|---|---|---|---|
| 1 | Sample on valid rather than the transfer | 1.67× inflation, skewed toward stalls (§14) | P1, P2 |
| 2 | Sample once per beat of a multi-beat packet | large packets over-weighted | P1 |
| 3 | One bin per address value | thousands of bins, no information | review |
| 4 | Cross every coverpoint with every other | ~10,080 bins, unclosable (§10) | review |
| 5 | ignore_bins used to remove a hard-to-reach hole | the model goes green and stops being evidence (§11) | P17 |
| 6 | illegal_bins used where an assertion belongs | a hit with no trace and no cycle number (§11) | P18 |
| 7 | Bins for features this DUT cannot implement | manufactured holes; waiving becomes a habit (§12) | P4, P5 |
| 8 | Depth bins hardcoded to 16 for an 8-Tag DUT | half the bins permanently empty | P4 |
| 9 | Credit coverage instantiated with no credit visibility | permanent hole where there is no signal | P6 |
| 10 | Six credit pools merged into one coverpoint | class-specific starvation invisible (§8) | review |
| 11 | Link state sampled combinationally | mid-transition values counted | P8 |
| 12 | Automatic transition bins over the full LTSSM | unreachable transitions become permanent holes (§9) | review |
| 13 | Transition sampled every cycle in a state | one transition counted hundreds of times | P7 |
| 14 | Coverage sampled before the transaction completed | partial transactions classified as complete | P1 |
| 15 | The observation object mutated after publication | bins credited to the wrong values | P10 |
| 16 | Two collectors sample from one observation | double counting | P9 |
| 17 | Depth taken from a request counter rather than outstanding | the bin measures issue rate, not concurrency | P11 |
| 18 | Cross bin credited without all conditions holding | closure reported that was not achieved | P11–P13 |
| 19 | No unaligned_tail bin | the partial-final-beat defect is never reached (§6) | review |
| 20 | No one_left credit bin | the last-credit race untested (§8) | review |
| 21 | No depth-1 bin, folded into low | the depth-1-only-correct designs look tested (§7) | review |
| 22 | No split-Completion coverage | the 80.8% orphan cascade unreached | review |
| 23 | No reset-with-outstanding cross | stale-epoch handling untested (§10) | P13 |
| 24 | No same-cycle free/allocate coverage | the 6.3% bitmap divergence unreached | review |
| 25 | Coverage collected from driver intent, not monitor observation | measures what was asked for, not what happened | P16 |
| 26 | Coverage subscriber writes to the scoreboard | both wrong together (§13) | P16 |
| 27 | Test terminates before samples are flushed | the last transactions never counted | P15 |
| 28 | Coverage databases from different configurations merged | bins mean different things in each | review |
| 29 | Post-reset samples attributed to the pre-reset epoch | closure credited to a run that did not achieve it | P14 |
| 30 | Coverage percentage reported as a correctness metric | a green campaign with an untested defect class (§2) | review |
| 31 | A hole waived with no recorded reason | question 5 of §16 never asked | P17 |
| 32 | Randomizing more cycles to close an ownership cross | four coincidences at ~1e-6; never converges (§14) | review |
Two counterexamples worth stating explicitly.
Mutation 32 is the one that wastes weeks. A cross requiring four independent conditions is reached about once in a million cycles under random stimulus (§14). Running ten times as many random cycles moves it from "essentially never" to "essentially never", and the regression cost is real. The targeted sequence that raises the per-sample probability to ~2% closes it in one run — which is why §16's question 2 comes before any decision to randomize harder.
Mutation 5 is the dishonest one and it is usually well-intentioned. A hole that is hard to reach gets an ignore_bins with a comment like "not applicable in this configuration" — and if that is true it belongs there (§12). If it is merely hard, the model has been edited to match the campaign rather than the risk, and the next engineer inherits a green report over an untested behaviour. §16's six questions exist to force the distinction, and P17 asserts that the justification was recorded.
18. Debugging Coverage
Symptom — 99% coverage and a defect escaped to silicon. Read the 1% (§16). If the remaining bins are ownership crosses, the campaign never tested the class of defect most likely to exist. Coverage percentage is not a risk measure; the list of holes is.
Symptom — one cross never hits no matter how long the regression runs. Work §16's questions in order. Most often it is question 2 — a cross of four coincidences is unreachable by random stimulus (§14, mutation 32). But check question 5 too: the DUT may be preventing the state, which makes the hole a bug report rather than a stimulus task.
Symptom — total bins explode after adding one cross. A cross multiplies. Adding a 12-bin coverpoint to an existing 4 × 7 cross produces 336 bins, most unreachable (§10). The fix is a targeted cross of two or three conditions, chosen from §3's risk table, not a product of everything available.
Symptom — a random test runs for hours without reaching deep outstanding.
The stimulus is not generating enough concurrent work, or the DUT is not accepting it. Check the depth coverpoint's distribution: if it never leaves one and low, the sequence is issuing serially, and every depth-sensitive defect (23.3, 23.5, 24.3 §4) is untested.
Symptom — hit counts look implausibly high and the distribution is skewed toward large transfers.
Sampling on valid (§4, mutation 1). The inflation is proportional to stall duration, so the bins associated with congestion dominate. §14 measured 1.67× on identical traffic — check the sampling event before believing any distribution.
Symptom — an Endpoint-only test shows Root-Port holes. The model is not configuration-aware (§12, mutation 7). Those bins can never fill and will be waived every cycle forever. Gate the covergroup on the configuration rather than waiving the holes.
Symptom — link transition bins disagree with the waveform. Sampling a combinational state (mutation 11) or sampling every cycle rather than on change (mutation 13). A state sampled combinationally can catch a mid-transition value that never existed at a clock edge. P7 and P8 are the properties, and the waveform is the arbiter.
19. Misconceptions
"Coverage tells you the DUT is correct." It tells you what was exercised (UVM — coverage philosophy, §2).
"100% coverage means done." It means the model was closed; the model only asks about states someone thought of (§2).
"75% is worse than 95%." Not if the missing 25% is irrelevant and the missing 5% is where the bugs are (§16).
"Sample whenever the transaction is valid." Once per transfer — 1.67× inflation otherwise (§4).
"More random cycles will close it." Not a four-condition cross at ~1e-6 (§14, mutation 32).
"Cross everything; more data is better." ~10,080 bins and a model nobody closes (§10).
"illegal_bins replaces an assertion." No trace, no cycle number, tool-dependent behaviour (§11).
"ignore_bins is how you handle hard bins." It is how you handle impossible bins, with a justification (§11, §12).
"A hole is a stimulus task." It may be a DUT bug — §16's question 5.
"Bins should cover the whole protocol." They should cover what this DUT implements (§12).
"Auto transition bins save effort." They manufacture unreachable holes over a full LTSSM (§9).
"Coverage can help the scoreboard predict." Then both are wrong together (§13).
"Merge the coverage databases from all runs." Only if the configurations match; bins mean different things otherwise (mutation 28).
20. Understanding Check
Q1. Your campaign reports 95% coverage. Why is that number nearly useless on its own? Because it does not say which 5% (§16). §14's baseline hit 75% with four holes, all of them ownership crosses corresponding to defects measured at 80.8%, 79.1%, 4,692 stale observations and 6.3%. A 95% run whose remaining bins are those same four is in an identical risk position — the percentage improved and the untested behaviour did not change.
Q2. A cross of four conditions has not hit in a 200,000-cycle random regression. What do you do, and what do you not do? Do not run more random cycles — four independent ~3% conditions coincide about once per million cycles, so ten times the regression buys essentially nothing (§14, mutation 32). Write a directed sequence that forces the coincidence, which raises the per-sample probability to a few percent and closes it in one run. And first check §16's question 5: if the DUT cannot enter the state, the hole is a bug.
Q3. Why does the depth coverpoint give value 1 its own bin?
Because depth 1 is where several wrong designs are accidentally correct (§7): a FIFO scoreboard (24.3 §4), a single "current transaction" register (23.5 §3), a shared outstanding counter (23.3 §4). Folding it into a low bin hides whether the campaign ever left it, and a campaign that did not has verified none of those mechanisms.
Q4. Your coverage report shows an Endpoint DUT missing Configuration-request bins. Is that a hole? No — it is a modelling error (§12). An Endpoint does not originate Configuration requests, so the bin can never fill and will be waived at every closure review. Gate the covergroup on the environment configuration so the model describes what this DUT can reach; a manufactured hole trains people to waive, and the next real hole gets waived too.
Q5. Sampling on valid gave 1.67× the samples. Beyond the inflated count, what else is wrong?
The distribution is skewed (§4). The inflation is proportional to how long each transaction stalled, so bins associated with congestion — large payloads, low credits, deep outstanding — are over-represented relative to transactions that passed straight through. The report then characterizes the stalls rather than the traffic, which is the opposite of what a coverage model is for.
Q6. What is the single most valuable cross in this chapter's model, and why? Final Completion fragment while the result path is stalled (§10, §16). Chapter 23.5 §12 measured 75.9% of stalled results having their context reused before the consumer read them — a data-corruption defect that cannot occur unless the consumer stalls at the final fragment. No amount of coverage elsewhere substitutes, and the window does not exist in a testbench whose result consumer is always ready.
21. What's Next
Coverage says what the campaign reached. It says nothing about where the observations came from.
Chapter 24.5 VIP addresses that: when the protocol side of the environment is a commercial agent, its monitor becomes a primary observation source — and its configuration becomes part of your coverage model's correctness, because a misconfigured VIP produces bins that describe a different device.
24.6 UVM Architecture then wires the subscribers of §12 alongside 24.3's scoreboard and 24.2's bound checkers — including the reset epoch that P14 assumed someone was coordinating.
And 24.7 Error Injection builds the stimulus this model cannot reach on its own. The credit-zero bin, the terminal-status paths, the replay conditions — §16's question 2 answers "write a directed sequence" for most holes, and for the error bins that directed sequence is an injection campaign, which is the next chapter's subject.