Skip to content
VLSI Mentor

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.

AxisBinsCycles to full coverageInformation carried
Command type~8~100essentially none
Bank index16a few hundredalmost none
Bank group4a few hundredalmost none
Read/write direction2~10none

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.

FamilyExample axisWhy it carries information
Device statebank state at commandlegality depends on it — Module 7
Device statepage hit / miss / conflictdetermines whether a precharge was needed
Device statepower stateModule 24 — exit latency applies
Historyprevious command to this bankthe spacing rule is between a pair
Historyspacing relative to the minimumat-minimum is the only value that tests the rule
Historyin-flight depth27.4 §3 — ordering ambiguity
Historyactivates in the rolling window14.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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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 bins

DERIVED, 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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  NA

Ninety 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:

StageBinsOf naive
Naive product11,520100%
Minus the bank/group dependency2,88025.0%
Minus illegal state/command/page combinations2882.5%
Restricted to AT_MIN spacing — §5960.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 commandWhat it tests
Below the minimumthe controller is broken, or the test is illegal
Exactly the minimumthe rule itself — the only interesting value
Above the minimumthat a legal sequence works
Far abovenothing 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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 target

The 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.

§4 and §5 have produced three distinct exclusions, and conflating them is how coverage models become unauditable.

CategoryExampleHow it is handledWhy
IllegalRD from IDLEillegal_binshitting it is a test bug — it must fail loudly
UnreachableACT with page HITignore_binscannot occur; counting it distorts the denominator
Reachable but meaninglessspacing of a pair with no ruleignore_bins, with a recorded reasonit can happen and it tells you nothing
InterestingRD at AT_MIN after ACTa real binthis 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.

InstrumentThe axis it contributesWhat its absence hides
27.2's propertiesantecedent hit count per propertya property that never armed — vacuity
27.3's monitorthe resolution flag per transactionchecking weakened by unresolved state
27.4's resolverthe legal-set size per readchecking weakened to membership
27.4's scoreboardthe outcome — including uncheckeda 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 changeSourceWhat becomes reachable or unreachable
8 banks instead of 165.3eight bank bins become unreachable
Bank groups disabled or absent5.3the group axis collapses; its crosses go dead
A longer burst lengthModule 12some spacing classes become unreachable — the bus is busy longer than the minimum
A CAS-latency change14.9data returns at a different offset; some spacing classes shift
A refresh mode changeModule 15refresh-adjacent bins change availability
Low-power entry enabledModule 24cp_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

DecisionCoverageCheckingCost
Command-type axes only — §1worse — reports 100% earlyunchangedbetter
Add device state — §2betterunchangedworse
Add request history — §2much betterunchangedworse
Add spacing class — §5much betterunchangedworse
Omit a reachability model — §4unmeasurableunchangedbetter
ignore_bins for illegal cases — §6looks betterworse — silent discardbetter
Sample the instruments — §7betterbetter — vacuity visibleworse

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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
endclass

unreachable() 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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
endclass

counts_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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
endgroup

Twenty-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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endtask

14. Corner Cases

CaseBehaviourWhy
RD from an idle bankIllegal — fails when hit§6 — a test can construct it
RD on a page missIllegalit is a different sequence, not an impossible one
ACT with page state HITUnreachable — ignorednothing produces a row comparison there
Command to a transitioning bankUnreachableno stimulus addresses it
ignore_bins with no reasonRefusedP12 — indistinguishable from raising the number
An illegal bin offered for exclusionRefused whatever the reasonP13 — §6's asymmetry
15 banks across 4 groupsReported as an error, count 0P9 — never silently rounded
Hits exceeding the reachable countInconsistent — not successP15 — 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 reportedImpossible — evidence of miscounting§4 — only 2.5% can be hit
Full coverage, no checkingClosure not supportableP16 — the 100%-with-a-bug case
Every bin hit, denominator unreviewedNot supportable§11 — a claim nobody checked
A pair with no spacing rule at AT_MINMeaningless — excluded with a reason§5 — 33 of 75 bins
cp_faw over bin hitA findingthe controller should never do it
cp_antecedent never_armed hitA 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
CheckWhat it establishes
Enumerate all 60 combinations against the table§10 and the table agree on all 60
count_legal() against enumerate()'s legal countboth give 6
naive_product(5,16,4,4,3,3)11,520 — §3's figure independently
Divide by the bank/group dependency2,880 — §4's second row
Apply the 10% legal fraction288 — §4's third row
Divide by the three spacing classes96 — §4's fourth row
Offer every illegal combination for exclusionall 24 refused — P13
Offer every unreachable combination with a reasonall accepted and recorded
Offer each one againidempotent; count unchanged
Run a 100-cycle test, sample ddr_state_cgcp_cmd full; x_state_cmd_page far from full
Run a directed at-minimum suitecp_spacing.at_min hit for all 14 ruled pairs
Run a random suite with no timing pressureat_min empty — no parameter tested
Hit every reachable bin with checking disabledclosure_supportable() false — P16
Hit every reachable bin with checking presentsupportable — the only configuration that is
Inject a hit count above reachablecounts_consistent() false — P15

Two suites are worth publishing, because they are the two shapes of a passing coverage report that proves nothing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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

SymptomLikely causeHow to confirm
Coverage stuck near 2.5%The denominator includes unreachable bins — §4Compare hits against reachable(), not naive
Coverage above what is reachableModel and tool disagree — §4counts_consistent() false
100% early in every testCommand-type axes only — §1The full cross's score against the coverpoints'
A timing parameter never testedat_min never produced — §5cp_spacing.at_min hit count
An illegal stimulus never reportedAn illegal case placed in ignore_bins — §6Grep the cross for ignore_bins on illegal rows
Bins excluded with no audit trailUnjustified exclusionsexclusions_recorded() against the excluded count
A property contributing nothingIts antecedent never armed — §7cp_antecedent.never_armed
High coverage, scoreboard silentChecking absent or weakenedcp_outcome.unchecked; x_armed_checked
Closure refused at full coveragechecking_present false — P16closure_supportable()'s five conditions
The over bin on cp_faw hitThe controller exceeded 14.8's limitIt is a finding, not coverage
Spacing bins full but meaninglessPairs with no rule counted — §5x_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

  1. §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.

  2. §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.

  3. §6 argues for illegal_bins when unsure. Construct the case where that choice costs more than the alternative, then say why the asymmetry still holds.

  4. §11's closure_supportable() has five conditions. Remove each in turn and describe the false sign-off the removal permits.

  5. §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.

  6. §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.

  7. 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.

  8. §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

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.