DDR · Module 27
DDR Functional Coverage
A naive six-axis cross is 11,520 bins. 288 are legal and 96 test a margin that can fail — so 99.2% of the denominator is a number nobody chose.
Chapters 27.2, 27.3 and 27.4 built instruments. Each reports what it did: a property that may have been vacuous, a monitor that may have diverged, a scoreboard that may have compared nothing. None of them says what the campaign as a whole has exercised, and that is this chapter's subject.
The other tracks own the machinery and the warning. The SystemVerilog track owns covergroup, bins, crosses and the sampling semantics. uvm/coverage-driven-verification owns the methodology and states the limit plainly — “coverage is the map of where verification has been, and a map is not the territory” — and publishes a lab in which 100% coverage signed off a data-corruption bug.
What neither covers is which DDR axes are worth drawing a map of. The answer is not the obvious one. A first attempt writes a cross of command type, bank, bank group, bank state, page state and spacing, which is 11,520 bins — and §4 derives that 288 of them are legal and 96 test a timing margin that could actually fail. The other 99.2% cannot be hit, and a closure argument that does not say so is arithmetic about nothing.
1. Why Command-Type Coverage Is Worthless
Start with what a first coverage model always contains, because understanding why it is useless is what motivates everything after it.
A coverpoint on command type. Module 7 gives roughly eight commands, so eight bins — and any test that runs for a hundred cycles hits all of them.
CURRICULUM-DERIVED reasoning about why: a read requires an activate, which requires a precharge or an idle bank; a refresh happens on a timer whether the test asks or not. So the commands are not independently selectable — issuing one read forces an activate, and running for tREFI forces a refresh.
| Axis | Bins | Cycles to full coverage | Information carried |
|---|---|---|---|
| Command type | ~8 | ~100 | essentially none |
| Bank index | 16 | a few hundred | almost none |
| Bank group | 4 | a few hundred | almost none |
| Read/write direction | 2 | ~10 | none |
An axis reaching 100% in the first hundred cycles of every test is not measuring the campaign. It is measuring whether the testbench was connected — which is worth exactly one assertion, not a coverage model.
And the damage is not merely that the axis is uninformative. A model dominated by such axes reports a high percentage early, which licenses a sign-off argument that nothing supports — and that is the mechanism by which uvm/coverage-driven-verification's lab reached 100% with a data-corruption bug still in the design.
2. What Actually Distinguishes DDR Traffic
The information in a DDR command is not in what it is. It is in when it arrives relative to everything else.
CURRICULUM-DERIVED from Module 14: a read is a read. A read arriving exactly tRCD after the activate to the same bank is a different event from one arriving ten cycles later, because the first tests a margin and the second tests nothing.
So the axes that carry information are relational, and they fall into two families:
Device state at the moment of the command. Is the bank idle or active? Is the addressed row the open one? Is the device in a power-down state, or in a refresh? Module 7 establishes that legality itself depends on this, so state is where the interesting behaviour lives.
Request history. What was the previous command to this bank, to this bank group, to the device? How long ago? This is the axis a first attempt omits entirely, and it is where controller bugs are.
| Family | Example axis | Why it carries information |
|---|---|---|
| Device state | bank state at command | legality depends on it — Module 7 |
| Device state | page hit / miss / conflict | determines whether a precharge was needed |
| Device state | power state | Module 24 — exit latency applies |
| History | previous command to this bank | the spacing rule is between a pair |
| History | spacing relative to the minimum | at-minimum is the only value that tests the rule |
| History | in-flight depth | 27.4 §3 — ordering ambiguity |
| History | activates in the rolling window | 14.8 — the limit is historical |
Row five is the single most important axis in this chapter, and §5 is about it. A spacing of “the minimum” is the only spacing at which a spacing rule can fail, so a campaign that never produces it has not tested any of Module 14's parameters — regardless of what its command-type coverage says.
3. The Cross That Means Something
Crossing device state with request history is where DDR coverage becomes real, and it is also where the arithmetic goes wrong.
The cross a first serious attempt writes, with CURRICULUM-DERIVED cardinalities from 5.3 and Module 7:
axis bins source
---- ---- ------
command 5 ACT, RD, WR, PRE, REF (ILLUSTRATIVE subset)
bank 16 5.3
bank group 4 5.3
bank state 4 IDLE, ACTIVE, ACT_PENDING, PRE_PENDING
page state 3 NA, HIT, MISS
spacing class 3 AT_MIN, ABOVE_MIN, FAR
naive product : 5 x 16 x 4 x 4 x 3 x 3 = 11,520 binsDERIVED, recomputed: the product is 11,520.
A model reporting 41% of 11,520 bins sounds like meaningful progress. §4 shows it is not, because the overwhelming majority of those bins describe situations that cannot occur — and a percentage whose denominator includes them is a percentage of a number nobody chose.
4. How Much of That Cross Can Never Be Hit
Here is the arithmetic, from a model stated in full so it can be checked.
Cross bank state x command x page state alone — DERIVED, recomputed:
naive bins 60 4 states x 5 commands x 3 page states
legal bins 6 10.0%
unreachable 54 90.0%
the six legal bins:
IDLE ACT NA ACTIVE RD HIT
IDLE PRE NA ACTIVE WR HIT
IDLE REF NA ACTIVE PRE NANinety per cent of that sub-cross cannot be hit, and nothing in a coverage tool tells you so. It reports 10% and waits.
And bank and bank group are not independent either. Sixteen banks distributed across four groups gives 16 consistent (bank, group) pairs, not 64 — so the naive product over-counts by a factor of four before legality is considered at all.
Applying both corrections to §3's full cross — DERIVED, recomputed:
| Stage | Bins | Of naive |
|---|---|---|
| Naive product | 11,520 | 100% |
| Minus the bank/group dependency | 2,880 | 25.0% |
| Minus illegal state/command/page combinations | 288 | 2.5% |
Restricted to AT_MIN spacing — §5 | 96 | 0.83% |
So 11,232 of 11,520 bins are unreachable, and of the 288 that remain, 96 test a margin that can fail.
5. Spacing Is the Only Axis That Tests a Rule
§2's row five claimed this; here is the argument and the arithmetic.
A timing parameter is a minimum. Module 14 owns them, and the shape is always “at least N cycles between these two commands”. So a controller can only violate it by issuing the second command too early — and the only stimulus that comes close to doing that is one issuing it exactly at the minimum.
| Spacing of the second command | What it tests |
|---|---|
| Below the minimum | the controller is broken, or the test is illegal |
| Exactly the minimum | the rule itself — the only interesting value |
| Above the minimum | that a legal sequence works |
| Far above | nothing the previous row did not |
So AT_MIN is not one bin among three. It is the bin, and the other two exist to prove the test can produce something other than the interesting case.
And a spacing class is meaningless for most command pairs. DERIVED from an ILLUSTRATIVE table of which ordered pairs have a minimum-spacing rule between them at all:
ordered command pairs 25 5 x 5
pairs WITH a spacing rule 14
pairs with NO rule 11
crossed with 3 spacing classes:
naive bins 75
bins under a rule 42
bins with NO rule 33 spacing class carries nothing
AT_MIN bins 14 18.7% of naive -- the targetThe 33 bins in row four are the quiet problem. A cross of prev_cmd x this_cmd x spacing generates them, a tool counts them, and they measure the spacing between two commands that have no spacing relationship. They are reachable, legal, and meaningless — which is a third category §6 has to name, because illegal_bins will not remove them.
6. Legal, Reachable, Interesting — Three Different Sets
§4 and §5 have produced three distinct exclusions, and conflating them is how coverage models become unauditable.
| Category | Example | How it is handled | Why |
|---|---|---|---|
| Illegal | RD from IDLE | illegal_bins | hitting it is a test bug — it must fail loudly |
| Unreachable | ACT with page HIT | ignore_bins | cannot occur; counting it distorts the denominator |
| Reachable but meaningless | spacing of a pair with no rule | ignore_bins, with a recorded reason | it can happen and it tells you nothing |
| Interesting | RD at AT_MIN after ACT | a real bin | this is the model |
The first two rows are routinely confused and the consequence is severe in one direction. An illegal combination placed in ignore_bins is silently discarded — so a test that issues a read from an idle bank produces no coverage hit and no error, and the illegal stimulus goes unnoticed. An unreachable combination placed in illegal_bins fails the moment your reachability model is wrong, which is noisy but safe.
So the rule is asymmetric: when unsure, use illegal_bins. A false alarm costs an investigation; a silent discard costs the finding.
Row three needs the reason recorded and this is the chapter's own discipline. An ignore_bins with no justification is indistinguishable from an inconvenient bin someone excluded to raise the percentage — and §9's component therefore refuses to count an exclusion that carries no reason, exactly as 27.4 §10's model refuses an invalidation with no reason.
7. Sampling the Instruments, Not Just the Pins
Chapters 27.2 through 27.4 each produced a number that belongs in a coverage model, and omitting them is a gap the pins cannot fill.
| Instrument | The axis it contributes | What its absence hides |
|---|---|---|
| 27.2's properties | antecedent hit count per property | a property that never armed — vacuity |
| 27.3's monitor | the resolution flag per transaction | checking weakened by unresolved state |
| 27.4's resolver | the legal-set size per read | checking weakened to membership |
| 27.4's scoreboard | the outcome — including unchecked | a run that compared nothing |
Row one is the one that closes 27.2's loop. That chapter measured 662 of 843 named properties as implications — 78.53% vacuity-exposed — and its remedy was to publish antecedent counts. A coverage model that samples those counts turns the remedy into a closure criterion: a property whose antecedent bin is empty is a property that proved nothing, and the coverage report now says so.
And rows two through four make the scoreboard's weakening visible in the same report as the coverage. 27.4 §15 published two suites that both showed zero mismatches — one checking exactly and covering nothing, one covering broadly and checking loosely. Neither is distinguishable from a pass count, and both are obvious in a coverage model that samples set size.
8. The Exclusion List Goes Stale
§4's reachability model is what makes every number in this chapter mean something, and it is also the most fragile thing in the environment. Reachability is a function of configuration, and the exclusion list is written once.
CURRICULUM-DERIVED examples of configuration changing which bins can be hit:
| Configuration change | Source | What becomes reachable or unreachable |
|---|---|---|
| 8 banks instead of 16 | 5.3 | eight bank bins become unreachable |
| Bank groups disabled or absent | 5.3 | the group axis collapses; its crosses go dead |
| A longer burst length | Module 12 | some spacing classes become unreachable — the bus is busy longer than the minimum |
| A CAS-latency change | 14.9 | data returns at a different offset; some spacing classes shift |
| A refresh mode change | Module 15 | refresh-adjacent bins change availability |
| Low-power entry enabled | Module 24 | cp_power's exit bins become reachable for the first time |
Row three is the one that catches campaigns, and the mechanism is worth stating exactly. §5 argues that AT_MIN is the only spacing that tests a rule. But a burst occupies the bus for a number of cycles set by the burst length, and if that occupancy exceeds a pair's minimum spacing, the controller cannot issue the second command at the minimum — the data bus is still busy. So AT_MIN for that pair is unreachable in that configuration, and a suite trying to hit it will run forever without succeeding.
The failure direction is the bad one in both cases, and they are different failures.
A bin excluded as unreachable that becomes reachable is invisible twice. It is not in the denominator, so it cannot lower the percentage; and it is in ignore_bins, so hitting it produces no record. The configuration change silently removed a piece of the coverage model and the report looks identical. This is strictly worse than the stale-reference problems elsewhere in this curriculum, because there is no dangling link to find — the exclusion is still syntactically valid and still compiles.
A bin that stays in the model but becomes unreachable is merely expensive. Coverage plateaus below 100%, someone investigates, and the investigation ends in a correct answer. Noisy and safe — the same asymmetry §6 established for illegal_bins.
So the exclusion list needs a configuration key, not just a reason. §11's exclude() takes a reason precisely because an unjustified exclusion is indistinguishable from a number being improved; the same argument says the reason must name the configuration under which the exclusion holds. An exclusion reading “unreachable” is unauditable after any configuration change. One reading “unreachable at BL16, because bus occupancy exceeds tCCD” can be rechecked by anyone, and it fails loudly the moment someone tries to reuse it at BL8.
And this is the one place where a coverage model should be deliberately noisy. An environment that re-derives its exclusion list from §10's model at build time, rather than storing it, converts a silent staleness into an elaboration-time disagreement — which is the same trade 27.1 §11's bindable rule checker makes by refusing to compile against a configuration it was not written for.
9. The Three Quantities at This Level
| Decision | Coverage | Checking | Cost |
|---|---|---|---|
| Command-type axes only — §1 | worse — reports 100% early | unchanged | better |
| Add device state — §2 | better | unchanged | worse |
| Add request history — §2 | much better | unchanged | worse |
| Add spacing class — §5 | much better | unchanged | worse |
| Omit a reachability model — §4 | unmeasurable | unchanged | better |
ignore_bins for illegal cases — §6 | looks better | worse — silent discard | better |
| Sample the instruments — §7 | better | better — vacuity visible | worse |
Row five is the row that makes every other row unreadable, because without a reachability model no percentage in the table means anything. And row six is the only row here that trades checking for appearance — it raises the number and removes an error, which is the combination that makes it dangerous.
10. The Reachability Model
// ---------------------------------------------------------------------
// ddr_reachability_model -- makes §4's stated legality rule executable
// so a coverage denominator is DERIVED rather than assumed.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY THIS EXISTS: §4. The naive cross of six axes is 11,520 bins, of
// which 288 are legal and 96 test a margin that can fail. A coverage
// percentage whose denominator is 11,520 is a percentage of a number
// nobody chose -- and 41% of 11,520 is not merely optimistic, it is
// impossible, which makes it evidence the model is miscounting.
//
// WHY illegal() AND unreachable() ARE SEPARATE (§6): an illegal
// combination must FAIL when hit, because hitting it is a test bug.
// An unreachable one must be excluded from the denominator. Putting
// an illegal case in ignore_bins discards the finding silently, so
// when unsure this model reports illegal -- a false alarm costs an
// investigation, a silent discard costs the bug.
//
// WHAT IT DOES NOT MODEL:
// - the command truth table in full (Module 7 owns it)
// - mode registers, training states, timing parameter VALUES
//
// WHAT IT WOULD MISS:
// - anything its reduced rule calls unreachable that a real device
// permits. That produces a wrong DENOMINATOR, which is why the
// pairing with illegal() above is deliberate.
// ---------------------------------------------------------------------
typedef enum { BS_IDLE, BS_ACTIVE, BS_ACT_PENDING, BS_PRE_PENDING } bank_state_e;
typedef enum { CMD_ACT, CMD_RD, CMD_WR, CMD_PRE, CMD_REF } cov_cmd_e;
typedef enum { PG_NA, PG_HIT, PG_MISS } page_state_e;
typedef enum { SP_AT_MIN, SP_ABOVE_MIN, SP_FAR } spacing_e;
// §6's four categories, kept distinct because conflating the first
// two is how a coverage model becomes unauditable.
typedef enum { BIN_INTERESTING, BIN_ILLEGAL, BIN_UNREACHABLE,
BIN_MEANINGLESS } bin_class_e;
class ddr_reachability_model extends uvm_object;
`uvm_object_utils(ddr_reachability_model)
function new(string name = "ddr_reachability_model");
super.new(name);
endfunction
// §4's stated rule, exactly. A bank in transition accepts nothing.
function bit legal(bank_state_e s, cov_cmd_e c, page_state_e p);
case (c)
CMD_ACT: return (s == BS_IDLE) && (p == PG_NA);
// A read of a row that is not open is not a legal command --
// it is a DIFFERENT command sequence (precharge, activate,
// read), which is why PG_MISS is excluded rather than binned.
CMD_RD,
CMD_WR : return (s == BS_ACTIVE) && (p == PG_HIT);
CMD_PRE: return ((s == BS_IDLE) || (s == BS_ACTIVE)) && (p == PG_NA);
CMD_REF: return (s == BS_IDLE) && (p == PG_NA);
default: return 0;
endcase
endfunction
// Unreachable is NARROWER than "not legal": these are combinations
// no stimulus can construct, as opposed to ones a broken test can.
// §6: a test CAN issue RD from IDLE, so that is illegal, not
// unreachable, and it must fail rather than be ignored.
function bit unreachable(bank_state_e s, cov_cmd_e c, page_state_e p);
// A page state other than NA is meaningless for a command that
// carries no row comparison -- no stimulus produces it.
if ((c == CMD_ACT || c == CMD_PRE || c == CMD_REF) && (p != PG_NA))
return 1;
// A bank in transition is not addressable by any command.
if (s == BS_ACT_PENDING || s == BS_PRE_PENDING)
return 1;
return 0;
endfunction
// §6's asymmetry, encoded: unreachable is checked FIRST, and
// anything left that is not legal is ILLEGAL rather than ignored.
function bin_class_e classify(bank_state_e s, cov_cmd_e c,
page_state_e p);
if (unreachable(s, c, p)) return BIN_UNREACHABLE;
if (!legal(s, c, p)) return BIN_ILLEGAL;
return BIN_INTERESTING;
endfunction
// §5: a spacing class carries information only for a pair that has
// a minimum-spacing rule between it. 14 of 25 ordered pairs do, in
// this ILLUSTRATIVE table, which leaves 33 of 75 spacing bins
// measuring a relationship that does not exist.
function bit pair_has_spacing_rule(cov_cmd_e prev, cov_cmd_e curr);
case (prev)
CMD_ACT: return (curr inside {CMD_RD, CMD_WR, CMD_PRE, CMD_ACT});
CMD_RD : return (curr inside {CMD_PRE, CMD_WR, CMD_RD});
CMD_WR : return (curr inside {CMD_PRE, CMD_RD, CMD_WR});
CMD_PRE: return (curr inside {CMD_ACT, CMD_REF});
CMD_REF: return (curr inside {CMD_ACT, CMD_REF});
default: return 0;
endcase
endfunction
function bin_class_e classify_spacing(cov_cmd_e prev, cov_cmd_e curr,
spacing_e sp);
if (!pair_has_spacing_rule(prev, curr)) return BIN_MEANINGLESS;
// §5: AT_MIN is the only value at which a minimum can fail. The
// other two prove the test can produce something else.
return BIN_INTERESTING;
endfunction
// DERIVED, and recomputed by enumeration rather than asserted --
// §4's table is this function's output.
function void enumerate(output int naive, output int legal_n,
output int unreach_n, output int illegal_n);
naive = 0; legal_n = 0; unreach_n = 0; illegal_n = 0;
for (int si = 0; si < 4; si++)
for (int ci = 0; ci < 5; ci++)
for (int pi = 0; pi < 3; pi++) begin
bank_state_e s = bank_state_e'(si);
cov_cmd_e c = cov_cmd_e'(ci);
page_state_e p = page_state_e'(pi);
naive++;
case (classify(s, c, p))
BIN_INTERESTING: legal_n++;
BIN_UNREACHABLE: unreach_n++;
BIN_ILLEGAL: illegal_n++;
default: ;
endcase
end
endfunction
// The bank/group dependency of §4: 16 banks across 4 groups gives
// 16 consistent pairs, not 64, so a naive product over-counts by 4
// before legality is considered at all.
function int consistent_bank_group_pairs(int banks, int groups);
if (groups <= 0) return 0;
if (banks % groups != 0) begin
`uvm_error("REACH", $sformatf(
"%0d banks do not divide into %0d groups", banks, groups))
return 0;
end
return banks; // each bank belongs to exactly one group
endfunction
endclassunreachable() is deliberately narrower than “not legal”. A test can issue a read from an idle bank — nothing physically prevents driving those pins — so that combination is illegal, not unreachable, and hitting it must fail. A bank in transition accepting no command is unreachable, because no stimulus constructs it. §6's asymmetry is the reason the two are separate functions rather than one.
And enumerate() recomputes §4's table rather than storing it. The numbers in this chapter's tables are that function's output, which is the only form in which a derived figure stays correct when the model changes.
11. The Closure Argument
// ---------------------------------------------------------------------
// ddr_closure_argument -- tracks what a coverage number licenses, and
// refuses to reduce it to one figure.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY IT REFUSES A PERCENTAGE: §4. 288 of 11,520 bins is 100% of what
// is reachable and looks like 2.5%; 41% of 11,520 is impossible. One
// number cannot distinguish "complete" from "miscounting", so this
// component returns the four figures and lets the reader do the
// division -- the same refusal 27.1 §9's residual() makes.
//
// WHY AN EXCLUSION NEEDS A REASON (§6): an ignore_bins with no
// justification is indistinguishable from an inconvenient bin someone
// removed to raise the number. 27.4 §10's model refuses an
// invalidation without a reason for the same reason.
//
// WHAT IT DOES NOT MODEL:
// - the design, the test, or the coverage tool
//
// WHAT IT WOULD MISS:
// - a bin that is HIT but WRONG. Coverage records that a situation
// occurred, never that it was handled correctly -- which is the
// 100%-with-a-bug lab in uvm/coverage-driven-verification.
// ---------------------------------------------------------------------
class ddr_closure_argument extends uvm_object;
`uvm_object_utils(ddr_closure_argument)
protected int m_naive; // the product, before any reasoning
protected int m_unreachable; // excluded: no stimulus constructs it
protected int m_meaningless; // excluded: reachable, carries nothing
protected int m_hit;
protected string m_exclusion_reason[string];
protected bit m_denominator_reviewed;
protected bit m_checking_present;
function new(string name = "ddr_closure_argument");
super.new(name);
endfunction
function void set_naive(int n);
if (n <= 0) begin
`uvm_error("CLOSURE", "naive bin count must be positive")
return;
end
m_naive = n;
endfunction
// §6: an exclusion with no reason is not an exclusion, it is a
// number being improved. Refused rather than accepted quietly.
function void exclude(string bin_name, string reason,
bin_class_e why);
if (reason == "") begin
`uvm_error("CLOSURE", $sformatf(
"exclusion of '%s' refused: no reason given", bin_name))
return;
end
if (!(why inside {BIN_UNREACHABLE, BIN_MEANINGLESS})) begin
// §6's asymmetry: an ILLEGAL bin must fail when hit, never be
// excluded from the denominator.
`uvm_error("CLOSURE", $sformatf(
"exclusion of '%s' refused: illegal bins must fail, not be ignored",
bin_name))
return;
end
if (m_exclusion_reason.exists(bin_name)) return; // idempotent
m_exclusion_reason[bin_name] = reason;
if (why == BIN_UNREACHABLE) m_unreachable++;
else m_meaningless++;
endfunction
function void note_hit(int n = 1);
m_hit += n;
endfunction
// The reviewer asserts that the denominator was examined. Without
// this, a reachable-bin count is a claim nobody checked.
function void mark_denominator_reviewed();
m_denominator_reviewed = 1;
endfunction
// A coverage number licenses nothing without checking present --
// the 100%-with-a-bug case. The environment sets this from whether
// 27.4's scoreboards reported meaningful results.
function void mark_checking_present(bit present);
m_checking_present = present;
endfunction
function int reachable();
int r = m_naive - m_unreachable - m_meaningless;
return (r < 0) ? 0 : r;
endfunction
// Four numbers, no percentage. §4: one figure cannot distinguish
// complete from miscounting.
function void report_numbers(output int naive, output int excluded,
output int reachable_n, output int hit);
naive = m_naive;
excluded = m_unreachable + m_meaningless;
reachable_n = reachable();
hit = m_hit;
endfunction
// Hitting MORE bins than are reachable is not good news. It means
// the reachability model and the coverage tool disagree, and one of
// them is wrong -- §4's "impossible percentage" made detectable.
function bit counts_consistent();
return (m_hit <= reachable());
endfunction
// What the number licenses, stated as a conjunction so no single
// condition can carry the argument alone.
function bit closure_supportable();
if (m_naive == 0) return 0; // nothing enumerated
if (!m_denominator_reviewed) return 0; // §6
if (!m_checking_present) return 0; // the 100%-with-a-bug case
if (!counts_consistent()) return 0; // §4
if (m_hit < reachable()) return 0; // genuinely incomplete
return 1;
endfunction
function int exclusions_recorded(); return m_exclusion_reason.size(); endfunction
function int unreachable_n(); return m_unreachable; endfunction
function int meaningless_n(); return m_meaningless; endfunction
function void report_phase(uvm_phase phase);
`uvm_info("CLOSURE", $sformatf(
"naive=%0d excluded=%0d (unreach=%0d meaningless=%0d) reachable=%0d hit=%0d consistent=%0b supportable=%0b",
m_naive, m_unreachable + m_meaningless, m_unreachable, m_meaningless,
reachable(), m_hit, counts_consistent(), closure_supportable()), UVM_LOW)
endfunction
endclasscounts_consistent() treats over-coverage as a failure, and this is the check that catches §4's impossible percentage. Hitting more bins than the reachability model says exist does not mean the campaign exceeded expectations; it means the model and the tool disagree and one of them is wrong.
And closure_supportable() is a conjunction of five conditions, none of which is the coverage number. A reviewed denominator, checking actually present, consistent counts, and completeness — so a 100% report with no checking returns false, which is the machine-readable form of uvm/coverage-driven-verification's lab result.
12. The Coverage Model Itself
// SIMULATION-ONLY. Three covergroups: device state, request history,
// and the instruments of 27.2 through 27.4. The axes are §2's, the
// exclusions are §6's, and the denominator is §10's model's output.
//
// illegal_bins vs ignore_bins throughout follows §6's asymmetry: when
// unsure, illegal_bins, because a false alarm costs an investigation
// and a silent discard costs the finding.
covergroup ddr_state_cg with function sample(
bank_state_e s, cov_cmd_e c, page_state_e p, int bank, int grp,
int pwr_state);
option.per_instance = 1;
cp_state : coverpoint s;
cp_cmd : coverpoint c;
cp_page : coverpoint p;
// §1: bank index carries almost nothing on its own, so it is binned
// to its edges rather than 16 ways. The full spread lives in the
// cross below, where it is crossed with something informative.
cp_bank_edge : coverpoint bank {
bins first = {0};
bins mid = {[1:14]};
bins last = {15};
}
cp_group : coverpoint grp { bins g[] = {[0:3]}; }
// Module 24: a command arriving as the device exits a low-power
// state pays an exit latency, which is a different event.
cp_power : coverpoint pwr_state {
bins active = {0};
bins pd_exit = {1};
bins sr_exit = {2};
}
// §4's cross, with the 54 unreachable and illegal combinations
// removed by construction. What remains is the six legal bins.
x_state_cmd_page : cross cp_state, cp_cmd, cp_page {
// ILLEGAL -- a test CAN construct these, so hitting one is a
// test bug and must fail. §6: never ignore_bins.
illegal_bins rd_from_idle =
binsof(cp_cmd) intersect {CMD_RD, CMD_WR} &&
binsof(cp_state) intersect {BS_IDLE};
illegal_bins rd_on_miss =
binsof(cp_cmd) intersect {CMD_RD, CMD_WR} &&
binsof(cp_page) intersect {PG_MISS};
illegal_bins act_on_active =
binsof(cp_cmd) intersect {CMD_ACT} &&
binsof(cp_state) intersect {BS_ACTIVE};
// UNREACHABLE -- no stimulus constructs a command to a bank in
// transition, or a page comparison for a command with no row.
ignore_bins in_transition =
binsof(cp_state) intersect {BS_ACT_PENDING, BS_PRE_PENDING};
ignore_bins no_row_to_compare =
binsof(cp_cmd) intersect {CMD_ACT, CMD_PRE, CMD_REF} &&
binsof(cp_page) intersect {PG_HIT, PG_MISS};
}
// The cross that gives bank index its meaning: a command to the
// LAST bank while the device is exiting power-down exercises a path
// an index coverpoint alone never distinguishes.
x_bank_power : cross cp_bank_edge, cp_power;
endgroup
covergroup ddr_history_cg with function sample(
cov_cmd_e prev, cov_cmd_e curr, spacing_e sp, int same_bank,
int same_group, int activates_in_window, int inflight_depth);
option.per_instance = 1;
cp_prev : coverpoint prev;
cp_curr : coverpoint curr;
// §5: the most important coverpoint in this chapter. AT_MIN is the
// only spacing at which a minimum-spacing rule can fail.
cp_spacing : coverpoint sp {
bins at_min = {SP_AT_MIN};
bins above_min = {SP_ABOVE_MIN};
bins far = {SP_FAR};
}
cp_same_bank : coverpoint same_bank {
bins same = {1};
bins other = {0};
}
cp_same_group : coverpoint same_group {
bins same = {1};
bins other = {0};
}
// 14.8: the rolling activate window is a HISTORICAL limit, so the
// interesting bin is the one at the limit.
cp_faw : coverpoint activates_in_window {
bins below = {[0:2]};
bins at_limit = {3};
bins over = {[4:$]}; // the controller should never do this
}
// 27.4 §3: in-flight depth is what makes the expected value a set.
cp_inflight : coverpoint inflight_depth {
bins none = {0};
bins one = {1};
bins some = {[2:4]};
bins deep = {[5:$]};
}
// The chapter's central cross. §5: 14 of 25 ordered pairs have a
// spacing rule, so the 33 bins for pairs without one are excluded
// WITH a recorded reason -- they are reachable and meaningless,
// which is §6's third category.
x_pair_spacing : cross cp_prev, cp_curr, cp_spacing {
ignore_bins no_spacing_rule =
(binsof(cp_prev) intersect {CMD_ACT} &&
binsof(cp_curr) intersect {CMD_REF}) ||
(binsof(cp_prev) intersect {CMD_RD} &&
binsof(cp_curr) intersect {CMD_ACT, CMD_REF}) ||
(binsof(cp_prev) intersect {CMD_WR} &&
binsof(cp_curr) intersect {CMD_ACT, CMD_REF}) ||
(binsof(cp_prev) intersect {CMD_PRE} &&
binsof(cp_curr) intersect {CMD_RD, CMD_WR, CMD_PRE}) ||
(binsof(cp_prev) intersect {CMD_REF} &&
binsof(cp_curr) intersect {CMD_RD, CMD_WR, CMD_PRE});
}
// A spacing rule is between commands to the SAME bank for most
// parameters, so the cross with locality is what makes AT_MIN mean
// what it appears to mean.
x_spacing_locality : cross cp_spacing, cp_same_bank, cp_same_group;
// 14.8 against locality: the activate window counts activates in
// ANY bank, which is exactly why a per-bank view misses it.
x_faw_locality : cross cp_faw, cp_same_bank;
endgroup
covergroup ddr_instrument_cg with function sample(
int antecedent_hits, int resolution_code, int set_size,
int outcome_code, int vacuous);
option.per_instance = 1;
// §7, row one. 27.2 measured 662 of 843 properties as implications
// -- 78.53% vacuity-exposed. A property whose antecedent bin is
// empty proved nothing, and this is the bin that says so.
cp_antecedent : coverpoint antecedent_hits {
bins never_armed = {0};
bins armed_once = {1};
bins armed_few = {[2:99]};
bins armed_many = {[100:$]};
}
// 27.3 §6's three-valued resolution flag.
cp_resolution : coverpoint resolution_code {
bins known = {0};
bins unknown = {1};
bins config = {2};
}
// 27.4 §3's legal-set size.
cp_set_size : coverpoint set_size {
bins unchecked = {0};
bins exact = {1};
bins pair = {2};
bins wide = {[3:$]};
}
// 27.4's five outcomes, kept apart.
cp_outcome : coverpoint outcome_code {
bins match = {0};
bins mismatch = {1};
bins unchecked = {2};
bins skip_row = {3};
bins skip_abandoned = {4};
}
cp_vacuous : coverpoint vacuous {
bins non_vacuous = {0};
bins vacuous = {1};
}
// A property that armed AND a scoreboard that compared: the
// conjunction §11's closure_supportable() requires, made visible.
x_armed_checked : cross cp_antecedent, cp_outcome;
// And vacuity against set size, which is the pair of weakenings
// that 27.2 and 27.4 each reported separately.
x_vacuity_setsize : cross cp_vacuous, cp_set_size;
endgroupTwenty-one coverpoints and seven crosses across three groups, and the structure is the argument: the first group is state with its reachability exclusions built in, the second is history with AT_MIN at its centre, and the third samples the instruments so a weakened campaign is visible in the same report as its coverage.
cp_bank_edge bins 16 banks three ways rather than sixteen. §1 establishes that bank index carries almost no information alone; the full spread only earns its bins inside a cross, which is where x_bank_power puts it.
And cp_faw's over bin exists to fail. A controller should never place four activates in the window, so a hit there is a finding — which is illegal_bins reasoning expressed as a bin the report will show.
Three covergroups rather than one, and the split is deliberate. A single group would merge axes with incompatible sampling events: state is sampled at a command, history needs the previous command to already exist, and the instrument axes are sampled per transaction or per property rather than per cycle. Merging them would force one sampling point and silently under-sample two of the three — and the resulting report would still look complete. Separating them also means the three can be merged or reported independently, so an environment can publish a state score, a history score and an instrument score rather than one figure, which is §11's refusal to reduce a closure argument to a single number applied to the model that feeds it.
13. What the Assertions Prove
Why these checks are immediate rather than concurrent. Both units are classes and a covergroup is not a clocked construct, so there is no sampling edge for assert property to attach to — 27.2 §11's bindable property set is the concurrent counterpart at the pins. The sixteen checks below are immediate assertions over the reachability model's arithmetic and the closure argument's refusals, and eleven of the sixteen assert that something must not happen. What they guard is not timing: it is a denominator nobody reviewed, an exclusion with no reason, and a percentage that licenses a sign-off.
// SIMULATION-ONLY. Immediate assertions over §10's arithmetic and
// §11's refusals.
task automatic check_coverage_model_invariants();
ddr_reachability_model rm = ddr_reachability_model::type_id::create("rm");
ddr_closure_argument ca = ddr_closure_argument::type_id::create("ca");
int naive, lg, unreach, illeg;
rm.enumerate(naive, lg, unreach, illeg);
// P1 -- the enumeration is a partition. Every bin is interesting,
// unreachable or illegal; none is two of those and none is none.
assert (lg + unreach + illeg == naive)
else $error("P1: bin classes do not partition (%0d+%0d+%0d != %0d)",
lg, unreach, illeg, naive);
// P2 -- §4's derived figures, recomputed rather than trusted.
assert (naive == 60 && lg == 6)
else $error("P2: enumeration gives naive=%0d legal=%0d, expected 60 and 6",
naive, lg);
// P3 -- FORBIDDEN. A read from an idle bank is ILLEGAL, never
// unreachable. §6: a test can construct it, so it must fail rather
// than be quietly excluded from the denominator.
assert (rm.classify(BS_IDLE, CMD_RD, PG_NA) == BIN_ILLEGAL)
else $error("P3: RD from IDLE was not classified illegal");
// P4 -- FORBIDDEN. A read on a page miss is likewise illegal, not
// unreachable: it is a different command SEQUENCE, not an
// impossible one.
assert (rm.classify(BS_ACTIVE, CMD_RD, PG_MISS) == BIN_ILLEGAL)
else $error("P4: RD on a page miss was not classified illegal");
// P5 -- a bank in transition is UNREACHABLE, because no stimulus
// constructs a command to it.
assert (rm.classify(BS_ACT_PENDING, CMD_RD, PG_HIT) == BIN_UNREACHABLE)
else $error("P5: a command to a transitioning bank was not unreachable");
// P6 -- FORBIDDEN. A page comparison for a command carrying no row
// is unreachable, not illegal -- nothing can produce it.
assert (rm.classify(BS_IDLE, CMD_ACT, PG_HIT) == BIN_UNREACHABLE)
else $error("P6: ACT with a page state was not unreachable");
// P7 -- the six legal bins are exactly §4's six.
assert (rm.legal(BS_IDLE, CMD_ACT, PG_NA) &&
rm.legal(BS_IDLE, CMD_PRE, PG_NA) &&
rm.legal(BS_IDLE, CMD_REF, PG_NA) &&
rm.legal(BS_ACTIVE, CMD_RD, PG_HIT) &&
rm.legal(BS_ACTIVE, CMD_WR, PG_HIT) &&
rm.legal(BS_ACTIVE, CMD_PRE, PG_NA))
else $error("P7: the six legal combinations are not all legal");
// P8 -- §4's bank/group dependency. 16 banks in 4 groups gives 16
// consistent pairs, not 64.
assert (rm.consistent_bank_group_pairs(16, 4) == 16)
else $error("P8: bank/group pairs miscounted as %0d",
rm.consistent_bank_group_pairs(16, 4));
// P9 -- FORBIDDEN. A bank count that does not divide the group
// count is reported, not silently rounded.
assert (rm.consistent_bank_group_pairs(15, 4) == 0)
else $error("P9: an inconsistent bank/group split was accepted");
// P10 -- FORBIDDEN. A spacing class is meaningless for a pair with
// no spacing rule. §5: 33 of 75 bins measure a relationship that
// does not exist.
assert (rm.classify_spacing(CMD_PRE, CMD_RD, SP_AT_MIN) == BIN_MEANINGLESS)
else $error("P10: a pair with no spacing rule was called interesting");
// P11 -- and AT_MIN for a pair that HAS a rule is the interesting
// case. §5: it is the only value at which a minimum can fail.
assert (rm.classify_spacing(CMD_ACT, CMD_RD, SP_AT_MIN) == BIN_INTERESTING)
else $error("P11: ACT->RD at minimum spacing was not interesting");
// P12 -- FORBIDDEN. An exclusion with no reason is refused. §6: it
// is indistinguishable from a number being improved.
ca.set_naive(11520);
ca.exclude("in_transition", "", BIN_UNREACHABLE);
assert (ca.exclusions_recorded() == 0)
else $error("P12: an exclusion with no reason was accepted");
// P13 -- FORBIDDEN. An ILLEGAL bin can never be excluded from the
// denominator, whatever reason is offered.
ca.exclude("rd_from_idle", "inconvenient", BIN_ILLEGAL);
assert (ca.exclusions_recorded() == 0)
else $error("P13: an illegal bin was excluded from the denominator");
// P14 -- a justified exclusion is recorded and reduces the
// reachable count.
ca.exclude("in_transition", "no stimulus addresses a transitioning bank",
BIN_UNREACHABLE);
assert (ca.exclusions_recorded() == 1 && ca.reachable() == 11519)
else $error("P14: a justified exclusion was not applied (reachable=%0d)",
ca.reachable());
// P15 -- FORBIDDEN. Hitting more bins than are reachable is an
// inconsistency, not good news. §4's impossible percentage, made
// detectable.
ca.note_hit(20000);
assert (!ca.counts_consistent())
else $error("P15: hitting more bins than reachable was called consistent");
// P16 -- FORBIDDEN. Closure is never supportable without checking
// present, however complete the coverage. This is the
// 100%-with-a-bug case in one line.
begin
ddr_closure_argument c2 = ddr_closure_argument::type_id::create("c2");
c2.set_naive(288);
c2.mark_denominator_reviewed();
c2.mark_checking_present(0);
c2.note_hit(288); // every reachable bin hit
assert (!c2.closure_supportable())
else $error("P16: closure claimed at full coverage with no checking");
end
endtask14. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
RD from an idle bank | Illegal — fails when hit | §6 — a test can construct it |
RD on a page miss | Illegal | it is a different sequence, not an impossible one |
ACT with page state HIT | Unreachable — ignored | nothing produces a row comparison there |
| Command to a transitioning bank | Unreachable | no stimulus addresses it |
ignore_bins with no reason | Refused | P12 — indistinguishable from raising the number |
| An illegal bin offered for exclusion | Refused whatever the reason | P13 — §6's asymmetry |
| 15 banks across 4 groups | Reported as an error, count 0 | P9 — never silently rounded |
| Hits exceeding the reachable count | Inconsistent — not success | P15 — the model and tool disagree |
| 288 of 11,520 reported as 2.5% | Correct, and 100% of reachable | §4 — the denominator is the problem |
| 41% of 11,520 reported | Impossible — evidence of miscounting | §4 — only 2.5% can be hit |
| Full coverage, no checking | Closure not supportable | P16 — the 100%-with-a-bug case |
| Every bin hit, denominator unreviewed | Not supportable | §11 — a claim nobody checked |
A pair with no spacing rule at AT_MIN | Meaningless — excluded with a reason | §5 — 33 of 75 bins |
cp_faw over bin hit | A finding | the controller should never do it |
cp_antecedent never_armed hit | A property that proved nothing | §7 — 27.2's vacuity, as coverage |
| Command-type coverage at 100% | Says the testbench is connected | §1 — ~100 cycles, no information |
Rows nine and ten are the pair to hold together. The same denominator makes a complete campaign look 2.5% done and makes a broken model look 41% done — and neither number can be read without §10's reachability model beside it.
15. DV — Testing the Coverage Model
// SIMULATION-ONLY. Independent reference. Enumerates the same cross
// with an explicit legality TABLE rather than §10's case statements --
// a different representation, so agreement is evidence.
class reachability_reference;
// One row per legal combination, written out rather than computed.
typedef struct { int s; int c; int p; } combo_t;
combo_t legal_table[$];
function new();
legal_table.push_back('{0, 0, 0}); // IDLE ACT NA
legal_table.push_back('{0, 3, 0}); // IDLE PRE NA
legal_table.push_back('{0, 4, 0}); // IDLE REF NA
legal_table.push_back('{1, 1, 1}); // ACTIVE RD HIT
legal_table.push_back('{1, 2, 1}); // ACTIVE WR HIT
legal_table.push_back('{1, 3, 0}); // ACTIVE PRE NA
endfunction
function bit is_legal(int s, int c, int p);
foreach (legal_table[i])
if (legal_table[i].s == s && legal_table[i].c == c
&& legal_table[i].p == p) return 1;
return 0;
endfunction
function int count_legal(); return legal_table.size(); endfunction
// The naive product, computed from the axis cardinalities alone so
// it cannot inherit an error from the legality model.
function int naive_product(int cmds, int banks, int groups,
int states, int pages, int spacings);
return cmds * banks * groups * states * pages * spacings;
endfunction
endclass| Check | What it establishes |
|---|---|
| Enumerate all 60 combinations against the table | §10 and the table agree on all 60 |
count_legal() against enumerate()'s legal count | both give 6 |
naive_product(5,16,4,4,3,3) | 11,520 — §3's figure independently |
| Divide by the bank/group dependency | 2,880 — §4's second row |
| Apply the 10% legal fraction | 288 — §4's third row |
| Divide by the three spacing classes | 96 — §4's fourth row |
| Offer every illegal combination for exclusion | all 24 refused — P13 |
| Offer every unreachable combination with a reason | all accepted and recorded |
| Offer each one again | idempotent; count unchanged |
Run a 100-cycle test, sample ddr_state_cg | cp_cmd full; x_state_cmd_page far from full |
| Run a directed at-minimum suite | cp_spacing.at_min hit for all 14 ruled pairs |
| Run a random suite with no timing pressure | at_min empty — no parameter tested |
| Hit every reachable bin with checking disabled | closure_supportable() false — P16 |
| Hit every reachable bin with checking present | supportable — the only configuration that is |
| Inject a hit count above reachable | counts_consistent() false — P15 |
Two suites are worth publishing, because they are the two shapes of a passing coverage report that proves nothing:
TWO PASSING COVERAGE REPORTS, NEITHER SUPPORTING A SIGN-OFF
(A) the report that looks finished and tested no timing parameter
stimulus : 2,000,000 random accesses, no timing pressure
cp_cmd : 5 of 5 bins 100%
cp_state : 4 of 4 bins 100%
cp_page : 3 of 3 bins 100%
cp_bank_edge : 3 of 3 bins 100%
cp_group : 4 of 4 bins 100%
---------------------------------------------
headline coverpoint score 100%
cp_spacing.at_min 0 hits
x_pair_spacing AT_MIN bins 0 of 14
every coverpoint is full. §5's argument says the campaign has
not tested ONE of Module 14's timing parameters, because a
minimum can only fail at the minimum -- and the headline
number is 100%.
(B) the report that is complete, consistent, and still licenses
nothing
stimulus : directed at-minimum suite, checking DISABLED
reachable bins 288
hit 288 100% of reachable
counts_consistent() TRUE
denominator_reviewed TRUE
checking_present FALSE
---------------------------------------------
closure_supportable() FALSE
every reachable bin was visited, the denominator was reviewed
and the arithmetic is consistent. Nothing checked whether the
design behaved correctly in any of them, so the report records
288 situations that OCCURRED and zero that were verified.
diagnosis : (A) is §1 -- axes that reach 100% carry no
information, and a headline built from them licenses an
argument nothing supports. (B) is uvm/coverage-driven-
verification's lab in this chapter's arithmetic: 100% coverage
with a data-corruption bug, because coverage records visits.
the fix : (A) needs §5's AT_MIN bins in the headline, not the
appendix. (B) needs §11's conjunction, which is why
closure_supportable() requires checking_present and not merely
a complete count.16. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Coverage stuck near 2.5% | The denominator includes unreachable bins — §4 | Compare hits against reachable(), not naive |
| Coverage above what is reachable | Model and tool disagree — §4 | counts_consistent() false |
| 100% early in every test | Command-type axes only — §1 | The full cross's score against the coverpoints' |
| A timing parameter never tested | at_min never produced — §5 | cp_spacing.at_min hit count |
| An illegal stimulus never reported | An illegal case placed in ignore_bins — §6 | Grep the cross for ignore_bins on illegal rows |
| Bins excluded with no audit trail | Unjustified exclusions | exclusions_recorded() against the excluded count |
| A property contributing nothing | Its antecedent never armed — §7 | cp_antecedent.never_armed |
| High coverage, scoreboard silent | Checking absent or weakened | cp_outcome.unchecked; x_armed_checked |
| Closure refused at full coverage | checking_present false — P16 | closure_supportable()'s five conditions |
The over bin on cp_faw hit | The controller exceeded 14.8's limit | It is a finding, not coverage |
| Spacing bins full but meaningless | Pairs with no rule counted — §5 | x_pair_spacing's ignore list against the 14 ruled pairs |
Row five is the one with no instrument in this chapter, and it is §6's asymmetry in its failing direction. An illegal combination sitting in ignore_bins produces no hit and no error, so neither the coverage report nor the assertion log mentions it. The only way to find it is to read the cross.
17. Misconceptions
“Command-type coverage is the place to start.” §1. Every test hits all eight bins in roughly a hundred cycles. It measures whether the testbench was connected.
“More axes means better coverage.” §4. Six axes give 11,520 bins of which 288 are reachable. Adding axes multiplies the unreachable fraction faster than the useful one.
“A coverage percentage measures progress.” §4. A percentage of 11,520 makes a complete campaign look 2.5% done. Without a reachability model there is no denominator worth dividing by.
“41% coverage is better than 2.5%.” §4. Only 2.5% is reachable, so 41% is impossible and is evidence the model is miscounting — most often missing illegal_bins.
“ignore_bins and illegal_bins are interchangeable.” §6. An illegal case in ignore_bins is discarded silently, so the test bug that produced it goes unreported. The asymmetry means: when unsure, illegal_bins.
“Any spacing above the minimum tests the parameter.” §5. A minimum can only fail at the minimum. AT_MIN is the bin; the others prove the test can produce something else.
“A cross of previous and current command covers spacing.” §5. Eleven of twenty-five ordered pairs have no spacing rule at all, so 33 of 75 bins measure a relationship that does not exist.
“Bank index deserves sixteen bins.” §1, §12. Alone it carries almost nothing; it earns its spread inside a cross with something informative.
“Coverage and checking are separate concerns.” §7, P16. A coverage number licenses nothing without checking present — and both of §15's reports pass without licensing a sign-off.
“100% coverage means the design was verified.” §11. Coverage records that a situation occurred. uvm/coverage-driven-verification publishes the lab where 100% coexisted with data corruption.
“An excluded bin needs no justification.” §6, P12. Then it is indistinguishable from an inconvenient bin someone removed to raise the number.
18. Interview Reasoning
Why is command-type coverage nearly worthless for DDR? The commands are not independently selectable — a read forces an activate, and running for tREFI forces a refresh — so any test hits every bin in about a hundred cycles.
What axes actually carry information? Device state at the command and request history: bank state, page hit or miss, power state, the previous command to the same bank, and the spacing relative to the minimum.
Why is spacing the most important axis? Because a timing parameter is a minimum, and a minimum can only be violated by issuing the second command too early. Only stimulus at exactly the minimum tests the rule.
You cross six axes and get 11,520 bins. What do you do next? Derive how many are reachable. The bank and bank-group axes are dependent, which removes three quarters; the legality rule removes 90% of what is left; 288 remain, and 96 of those test a margin.
Your report says 41% of 11,520. What is your first thought? That the model is wrong, not that the campaign is 41% done. Only about 2.5% of that product is reachable, so 41% is impossible — usually a missing illegal_bins letting auto-generated bins count.
When do you use illegal_bins rather than ignore_bins? Whenever a test could construct the combination. Hitting it is then a test bug that must fail. ignore_bins is for combinations no stimulus can produce — and when unsure, choose illegal_bins, because a false alarm costs an investigation and a silent discard costs the finding.
What is a bin that is reachable and meaningless? A spacing class for a command pair with no spacing rule between them. It can happen, it will be counted, and it measures a relationship that does not exist — so it is excluded with a recorded reason.
What does 100% functional coverage license? That every enumerated situation occurred. Nothing about whether the design handled any of them, which is why a closure argument has to require that checking was present and meaningful.
A campaign reports 100% on every coverpoint and no at-minimum bins. What has it tested? Not one timing parameter. Every coverpoint being full is consistent with a suite that applied no timing pressure at all.
What do you put on a dashboard instead of one percentage? Four numbers — naive, excluded, reachable, hit — plus whether the denominator was reviewed and whether checking was present. One figure cannot distinguish complete from miscounting.
19. Exercises
-
§4 derives 288 legal bins from 11,520. Recompute it after adding a seventh axis of four bins, and state how many of the new bins are reachable under the same legality rule.
-
§5 finds 11 of 25 ordered pairs with no spacing rule. Construct the ordered pair you believe is most often wrongly assumed to have one, and say what its spacing bins would appear to measure.
-
§6 argues for
illegal_binswhen unsure. Construct the case where that choice costs more than the alternative, then say why the asymmetry still holds. -
§11's
closure_supportable()has five conditions. Remove each in turn and describe the false sign-off the removal permits. -
§15's report (A) is 100% on every coverpoint with zero at-minimum bins. Design the single headline figure you would publish instead, then state what your figure hides.
-
§7 samples 27.2's antecedent counts as coverage. Derive how many bins a per-property antecedent coverpoint would add for the 843 properties that chapter measured, and say whether that is affordable.
-
A colleague proposes auto-generating bins for every axis and excluding nothing. Show from §4 what their percentage converges to as axes are added, and identify which of §9's quantities they have traded.
-
§10's legality model is ILLUSTRATIVE. Identify the one simplification in it you would fix first, and state which figure in §4 moves as a result.
20. Where This Goes
DDR functional coverage is now arithmetic with a stated denominator. Command type carries no information because the commands are not independently selectable; the axes that carry information are device state and request history; spacing relative to the minimum is the only axis that tests a timing parameter; a naive six-axis cross is 11,520 bins of which 288 are reachable and 96 test a margin; and illegal, unreachable and meaningless are three different exclusions, one of which must fail rather than be ignored.
Four results carry forward. The denominator is the whole argument — the same 11,520 makes a complete campaign look 2.5% done and a broken model look 41% done. AT_MIN is the bin, and §15's report (A) is a 100% coverpoint score with no timing parameter tested. When unsure, illegal_bins, because the asymmetry is between a costly false alarm and a lost finding. And coverage licenses nothing without checking, which §11 encodes as a conjunction rather than a threshold.
Two things stay open. The legality model is ILLUSTRATIVE, so every derived figure here is exact under a stated rule and approximate under a real device's truth table — Module 7 owns the commands, and P2's recomputation protects the arithmetic rather than the premise. And an illegal case hidden in ignore_bins has no instrument in this chapter, exactly as 27.4 §16's ordering row had none: the only way to find it is to read the cross.
What Modules 27.1 through 27.5 have now produced is a verification environment's parts — a compliance obligation, a property set, a monitor, a distributed scoreboard, and a coverage model with a defensible denominator. Every one of them was built here, and almost nobody builds them.
Chapter 27.6 takes that up. A DDR verification IP arrives with its own monitor, its own reference model, its own coverage model and its own assertions — and the integration question is not how to connect it. It is what you are now unable to see, because a component you did not write reports what its author chose to report, and every judgement this module has made about what to report — the three-valued resolution flag, the set size, the unchecked total, the reviewed denominator — is a judgement somebody else has already made on your behalf, and usually has not published.
Continue learning
Related tutorials
- Related topic
UCIe Functional Coverage
Proving a UCIe regression exercised the meaningful architectural space rather than merely running many tests — a five-level plan hierarchy, coverage sampled on the event it describes rather than every clock, crosses justified by a named bug, illegal bins that require specification evidence rather than an implementation limit, recovery-context coverage as the centerpiece, and closure that classifies every uncovered bin instead of waiving it.
- Related topic
Registers
Registers are storage moved inside the execution path and named by the instruction encoding rather than addressed. The architectural, microarchitectural and RTL views of that, a synthesizable 2R1W register file with its clocked behaviour and reset argument, and the port and scaling limits that make a denser tier unavoidable.
- Related topic
JEDEC Compliance
A compliance claim has a structure: an enumerated rule list, a result per rule, and a residual. This chapter builds the structure — and refuses to invent the rule list, because JEDEC would not serve it.
- Related topic
DDR Assertions
662 of this curriculum's 843 named properties are implications, so 78.53% can pass by never arming. Built on the measured census, not on advice.
Standards & specifications
- Governing standard
- JEDEC JESD79 (DDR SDRAM)(opens JEDEC Solid State Technology Association in a new tab)
Defines the DDR SDRAM device itself — signals, command encoding, mode registers, timing parameters and the initialisation sequence — one document per generation. Memory-controller microarchitecture, address-mapping policy, PHY training algorithms and board-level design are not specified by it.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the DDR curriculum.
