Skip to content
VLSI Mentor

DDR · Module 27

DDR Scoreboards

A read's correct payload depends on every write ever issued to that address, so the reference model is the array. And with accesses in flight, the expected value stops being a value.

Chapter 27.3 produced a stream of transactions, each carrying what the monitor could determine and a flag saying how well. It compared none of them against anything. A transaction reporting a read of bank 3, row 500, column 16 returning a particular payload is an observation, and nothing in that chapter says whether the payload was right.

The UVM track settles the general argument. uvm/why-scoreboards-exist owns “why observing isn't checking”, the compare itself, and — the section that matters most here — “the independence of the expected”.

What it does not cover is where a DDR expected value comes from, and the answer is uncomfortable: the correct payload for a read is determined by every write ever issued to that address. So the reference model is not a small predictor beside the design. It is a model of the array, and the scoreboard's correctness now rests on it.

Worse, §5 shows that with several accesses in flight the expected value is not a value at all. It is a set — and a scoreboard that compares against one member of that set fails on correct hardware.

1. The Expected Value Is a Memory

State the dependency precisely, because its size is the whole problem.

For a read of address A, the expected payload is the data written by the most recent write to A. Not the most recent write on the bus, not the most recent write to that bank — to that address.

So the reference model must answer “what is at A?” for any A the test touches, which means it must retain every write. CURRICULUM-DERIVED from Module 5's organisation, a worked bound:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   a 16 Gb device, ILLUSTRATIVE
   held densely   :  16 Gb of model state -- 2 GB of simulator memory
   held sparsely  :  one entry per address actually written

A dense model is unaffordable and unnecessary. A test writes a tiny fraction of an array, so the model is an associative array keyed by address — and its size is a property of the test, not of the device.

Two consequences follow from sparseness, and the second is this chapter's first real problem.

The model's cost scales with test length rather than device size. A long random test can still exhaust memory, which makes model size a test-planning concern rather than a device concern.

And a sparse model has a third answer. A dense model of a 16 Gb device has a value at every address, whatever that value is. A sparse model has addresses it has never seen — and §2 is about what to report for a read from one.

2. The Unwritten Address

A read from an address the model has never seen has no expected value, and this is the same problem 27.3 §6 solved for bank state — with the same answer.

Model's knowledge of AExpected payloadA mismatch here means
Written — value recordedthat valuea real failure
Never writtennonenothing — the device may return anything
Written, then invalidatednone, and deliberately§10

Reporting a mismatch for an unwritten address would manufacture failures. An uninitialised DRAM location returns whatever it returns, and a scoreboard expecting zero would fail on hardware behaving correctly.

Reporting a pass would hide them. If the model silently accepts any payload from an unwritten address, a test that reads before writing gets no checking at all — and 27.2 §2's vacuity argument applies exactly: the comparison happened and constrained nothing.

So the third state must be reported, not resolved. §10's model returns a flag and a reason beside the value, and §12's scoreboard counts unchecked reads separately from passing ones. A run where most reads were unchecked is a run that mostly did not check — which is invisible if the two are added together.

3. Ordering Makes the Expected Value a Set

Here is the DDR-specific difficulty that the UVM track's general argument does not reach.

A scoreboard comparing a read against “the last write” assumes it knows which write was last. With one access in flight at a time, it does. With several in flight, it may not — and Module 23 establishes that a controller reorders deliberately, because reordering is where its performance comes from.

The problem, stated with an ILLUSTRATIVE sequence:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   issued by the test        what the device may do
   ------------------        ----------------------
   WR A <- 0x11              either order is legal if the
   WR A <- 0x22              controller's rules permit it
   RD A                      -> expected is 0x11 OR 0x22

If the controller is permitted to reorder those writes, the read's expected payload is a set. A scoreboard comparing against 0x22 fails half the time on correct hardware; one comparing against 0x11 fails the other half.

Three responses, and only the third is generally right.

Constrain the stimulus so ordering is never ambiguous. Serialise accesses to any address, so at most one write to A is ever in flight. This works and it removes the coverage — the reordering behaviour that Module 23 says is the controller's whole value is now never exercised.

Model the controller's ordering rules exactly. Then the expected value is unique again. This makes the scoreboard depend on the design's scheduling policy, which is the independence problem 27.3 §9 warns about arriving in a new component — and worse, a policy change in the design silently invalidates the scoreboard.

Or accept a set and compare against membership. The expected value becomes “one of these”, and a pass means the payload was a legal outcome of a legal ordering. That is weaker checking and it is honest, and §11's resolver produces exactly that.

And there is a fourth case the three responses all quietly assume away: the read itself may be the reordered access. Everything above treats the read's position as fixed and asks which writes preceded it. But a controller permitted to reorder writes is usually permitted to advance a read past a write it does not conflict with — and conflict is decided by address comparison inside the design. So a read that the test issued third may execute first:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   issued           one legal execution        another
   ------           -------------------        -------
   WR A <- 0x11     WR A <- 0x11               RD A      (old contents)
   WR A <- 0x22     RD A   -> 0x11             WR A <- 0x11
   RD A             WR A <- 0x22               WR A <- 0x22

The middle column's read returns 0x11; the right column's returns whatever was at A before the test began — which by §2 may have no expected value at all. So the set can contain not only every in-flight write's value but the previous contents, and it can contain the third state. A scoreboard that treats “unchecked” and “a member of the set” as mutually exclusive is therefore wrong, which is why §12 counts them as separate outcomes over the same read rather than as a partition of reads by kind.

4. Three In-Flight Cases

The set is not always large, and distinguishing the cases is what keeps the checking as strong as it can be.

In flight to address AExpected payloadChecking strength
Nothingthe recorded valueexact — full strength
One write, completed before the read issuedthat valueexact
Two or more writes, order undetermineda setmembership only
A write whose completion is unobservedthe old value or the newtwo-element set

The fourth row is the common case and the most easily got wrong. A write that has been issued but whose completion the monitor has not seen may or may not have landed. So a read overlapping it has two legal answers, and a scoreboard that picks either one is wrong half the time — for reasons that look like a data corruption bug.

And the set only ever grows from ambiguity, never from the device. Every member of it corresponds to a legal ordering the device was permitted to choose. So a large set is a statement about the stimulus, not about the design — and §11's resolver reports set size so that a suite whose sets are routinely large can see that its checking has weakened.

5. Why Distributed Rather Than Central

27.4's registry purpose calls for a distributed scoreboard, and Module 5 supplies the reason.

Banks are independent. Chapter 5.2 owns that guarantee and its one exception, and independence is exactly what makes decomposition valid: a write to bank 3 cannot change what is at an address in bank 7.

So the address space partitions cleanly along banks, and a per-bank scoreboard is not an optimisation but a structural match:

Central scoreboardPer-bank scoreboards
Reference stateone map, all addressesone map per bank
Ordering ambiguityacross every access in flightonly within a bank
A bank's traffic affectsthe whole modelthat bank's model
Cross-bank rulesnaturally visiblebelong to nobody — §7

The second row is the real win and it is not about performance. §3's ambiguity set is bounded by the accesses in flight to the same address — and addresses in different banks cannot collide. Partitioning by bank therefore shrinks every ambiguity set, which makes the checking stronger rather than merely faster.

The fourth row is the cost, and §7 is about it.

6. The Obligations Distribution Leaves Unowned

Decomposing by bank makes per-bank checking clean and makes an entire class of rule invisible.

Cross-bank obligations have no natural home, and the list is not short:

ObligationOwnerWhy no per-bank scoreboard sees it
RefreshModule 15the device, all banksa refresh is device-wide
Activate windows14.8a rolling limit across banksit counts activates in any bank
Bank-group spacing5.3pairs of banksthe relation is between two models
Power state6.2device-wideper-bank state says nothing about it
Ordering across banksthe controllereach bank sees only its own accesses

So a set of per-bank scoreboards can all pass while the device-level behaviour is wrong, and nothing in the decomposition surfaces it.

The resolution is not to centralise. §5 establishes that centralising costs checking strength by enlarging every ambiguity set. The resolution is a separate device-level component that consumes the same transaction stream and owns only the cross-bank obligations — which is 27.7's subject, because it is an environment-structure decision rather than a scoreboard one.

What this chapter can do is name the gap and make it measurable. §12's scoreboard publishes what it does not cover, so an environment assembling sixteen of them can be asked what nothing is checking.

A worked instance, ILLUSTRATIVE, showing how complete the silence is. Suppose a controller under test issues activates correctly within every bank but violates 14.8's rolling four-activate window across banks — five activates inside the window, spread one per bank. Each per-bank scoreboard sees one activate, which is legal in isolation and which it is not counting anyway, since its job is data. Every read still returns the right payload, because the device honoured the commands it was given. So sixteen scoreboards report sixteen clean results and the device has been driven outside its specification. The failure is not subtle in the waveform and it is invisible in the scoreboard, and the reason is structural rather than a bug: a rolling window across banks has no per-bank projection, so no amount of care inside a per-bank component recovers it. This is the precise sense in which the gap is unowned — not under-tested, but outside the decomposition entirely, which is why it has to be closed by adding a component rather than by improving these.

7. The Independence Problem, Again

Chapter 27.3 §9 established the cannot-fail monitor: one that reads the design's own state agrees with it by construction. A scoreboard has two versions of the same hazard and they are not equally obvious.

The obvious one: taking the expected value from the design. A reference model that reads the DUT's memory array would pass always. Nobody does this.

The subtle one: taking the ordering from the design. §3's second response — model the controller's ordering rules exactly — is the tempting path, because it restores a unique expected value and makes failures go away. And it makes the scoreboard agree with whatever the controller decided, which is precisely the thing under test.

Source of orderingIndependent?Result
The test's issue order plus the protocol's rulesyesa set, honestly derived — §3
The controller's observed decisionsnothe scoreboard ratifies the design
The controller's documented policypartlyindependent until the policy changes silently

The second row is a scoreboard that cannot fail on ordering, and it will still catch data corruption — which makes it far more plausible than the cannot-fail monitor and therefore easier to ship. It fails only to catch the class of bug where the controller reordered in a way it was not permitted to, and that is exactly what Module 23's machinery makes possible.

8. The Three Quantities at This Level

DecisionCoverageCheckingCost
Sparse reference model — §1unchangedunchangedbetter
Report unwritten separately — §2unchangedbetter — honest totalsslightly worse
Serialise to remove ambiguity — §3worse — no reordering exercisedbetter — exact comparebetter
Accept a set — §3betterweaker but honestworse
Take ordering from the design — §7unchangedcatastrophically worsebetter
Per-bank decomposition — §5unchangedbetter — smaller setsbetter, minus §6's gap

Rows three and four are a genuine choice with no right answer, and this chapter's position is that accepting a set is usually correct because the coverage it buys is the controller's whole value. Row five is not a choice, and it sits in the same place as 27.3 §10's fourth row: cheapest, and it destroys the thing being paid for.

The trade in rows three and four is the one thing here that a campaign can measure rather than argue about, and that is why §13's coverage model samples set size and exactness alongside the outcome. A suite sitting permanently at row three shows cp_set_size hitting only its exact bin; one sitting permanently at row four shows cp_exactness in mostly_set. Neither reading is a failure and a campaign showing only one of them is, because the two runs catch different bugs — and §15 publishes both so the shape of the argument is visible rather than asserted.

9. The Scoreboard Architecture

A distributed DDR scoreboard's structure, in four rows. The top row holds the three inputs: the transaction stream from chapter 27.3's monitor, the test's issue order which is the only independent source of ordering information, and the protocol's ordering rules. The second row holds the four per-bank components: the sparse reference memory holding one entry per written address, the in-flight tracker that knows which writes have not completed, the order resolver that turns issue order and protocol rules into either a unique expected value or a set, and the comparison itself. The third row holds the three outcomes the scoreboard must count separately: matches, mismatches, and reads that could not be checked because the address was never written. The bottom row is the device-level gap — refresh, activate windows, bank-group spacing and power state — which no per-bank scoreboard sees and which chapter 27.7 must own. Edges run from the transaction stream into the reference memory, the in-flight tracker and the comparison, from issue order and protocol rules into the order resolver, from the reference memory and the resolver into the comparison, from the comparison into all three outcomes, and from the outcomes into the device-level gap to show what they do not reach.Txn streamfrom 27.3's monitorTest issue orderthe independent source —§7Protocol ruleswhat reordering ispermittedBank partitionbanks are independent —5.2Sparse ref memoryone entry per writtenaddr — §1In-flight trackerwhich writes have notlanded — §4Order resolvera value, or a set — §3The comparisonmembership, not equality— §3Matcheschecked and correctMismatcheschecked and wrongUnchecked readsno expected value — §2Device-level gaprefresh, tFAW, power — §612

Two things the structure makes visible.

Ordering enters from the test and the protocol, never from the design. §7's table makes that a correctness requirement and the diagram makes it structural — there is no edge from a controller-decision source, and adding one would produce the scoreboard that ratifies its own subject.

And the bottom row is drawn as a destination the outcomes cannot reach. The three muted edges into Device-level gap are the point: matches, mismatches and unchecked reads all fail to say anything about refresh, activate windows or power state. §6's gap is drawn rather than described, because a diagram showing sixteen clean per-bank scoreboards and nothing else is the mistake this chapter is warning about.

10. The Reference Memory

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ddr_ref_memory -- a sparse model of what should be at each address,
// with an explicit third answer for addresses never written.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY SPARSE: §1. A dense model of a 16 Gb device is 2 GB of
// simulator state and unnecessary, because a test writes a tiny
// fraction of an array. The cost then scales with TEST LENGTH rather
// than device size, which makes model size a test-planning concern.
//
// WHY A THIRD ANSWER: §2, and this is the third time this curriculum
// has needed it -- 7.4 for bank state, 27.3 §6 for a monitor's
// reconstruction, here for expected data. A two-valued model
// manufactures failures if it guesses and hides them if it defaults.
//
// WHAT IT DOES NOT MODEL:
//   - the array, retention, refresh effectiveness, on-die correction
//   - any physical property whatsoever
//
// WHAT IT WOULD MISS:
//   - a device silently repairing data (25.4 §4): it matches exactly
//   - data lost to a missed refresh: it mismatches, and the model
//     cannot say why
// ---------------------------------------------------------------------
class ddr_ref_memory extends uvm_object;
  `uvm_object_utils(ddr_ref_memory)

  // Sparse: one entry per address actually written.
  protected logic [63:0] m_data[longint];
  // §10: an address can be deliberately invalidated, which is not the
  // same as never written -- the reason is kept for reporting.
  protected string       m_invalid[longint];

  protected int m_writes;
  protected int m_overwrites;
  protected int m_invalidations;

  function new(string name = "ddr_ref_memory");
    super.new(name);
  endfunction

  function void write(longint addr, logic [63:0] value);
    if (m_data.exists(addr)) m_overwrites++;
    m_data[addr] = value;
    // A write restores validity: whatever made the address unknown,
    // this write determines it again.
    if (m_invalid.exists(addr)) m_invalid.delete(addr);
    m_writes++;
  endfunction

  // §10: used when an event makes the content indeterminate without
  // writing it -- a reset, or an ordering the resolver cannot settle.
  function void invalidate(longint addr, string reason);
    if (reason == "") begin
      `uvm_error("REFMEM", "invalidate() requires a reason")
      return;
    end
    m_invalid[addr] = reason;
    m_invalidations++;
  endfunction

  // Returns 1 and sets value when an expectation exists. Returns 0
  // for never-written AND for invalidated addresses -- §2's third
  // state, which the caller must count separately rather than treat
  // as a pass.
  function bit expected(longint addr, output logic [63:0] value,
                        output string why_not);
    if (m_invalid.exists(addr)) begin
      why_not = m_invalid[addr];
      return 0;
    end
    if (!m_data.exists(addr)) begin
      why_not = "never written";
      return 0;
    end
    value   = m_data[addr];
    why_not = "";
    return 1;
  endfunction

  function bit is_known(longint addr);
    return m_data.exists(addr) && !m_invalid.exists(addr);
  endfunction

  function int entries();        return m_data.size();     endfunction
  function int writes();         return m_writes;          endfunction
  function int overwrites();     return m_overwrites;      endfunction
  function int invalidations();  return m_invalidations;   endfunction
  function int invalid_now();    return m_invalid.size();  endfunction

  // §1: model size is a test property. Publishing it lets a long
  // random test see its own growth before it exhausts memory.
  function int footprint_entries(); return m_data.size() + m_invalid.size(); endfunction
endclass

expected() returns a flag and a reason rather than a sentinel value. There is no payload value that could mean “unknown” — every 64-bit pattern is a legal thing for a device to return — so the third state has to live outside the value. And the reason is returned too, because §10's invalidation and §2's never-written are different findings that a debug session must distinguish.

A write clears an invalidation. Whatever made an address indeterminate, writing it determines it again — which is the one event that can reduce the model's uncertainty, and the analogue of 27.3 §7's resynchronisation.

11. The Order Resolver

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ddr_order_resolver -- turns issue order plus in-flight state into
// either a unique expected value or a set of legal ones.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY IT NEVER READS THE CONTROLLER (§7): taking ordering from the
// design's observed decisions makes the scoreboard ratify the thing
// under test. It would still catch data corruption, which is what
// makes it plausible enough to ship -- and it would stop catching an
// illegal reorder, which is exactly what Module 23's machinery makes
// possible.
//
// WHAT IT DOES NOT MODEL:
//   - the controller, its arbitration, queues or scheduling policy
//
// WHAT IT WOULD MISS:
//   - an ILLEGAL reorder whose payload lands inside the legal set.
//     Membership bounds the damage of ambiguity; it does not detect
//     every ordering fault.
// ---------------------------------------------------------------------
class pending_write extends uvm_object;
  `uvm_object_utils(pending_write)
  longint      addr;
  logic [63:0] value;
  int unsigned issue_seq;     // the TEST's order, not the device's
  bit          completion_observed;
  function new(string name = "pending_write");
    super.new(name);
    completion_observed = 0;
  endfunction
endclass


class ddr_order_resolver extends uvm_component;
  `uvm_component_utils(ddr_order_resolver)

  protected pending_write m_inflight[$];
  protected int m_max_set_size;
  protected int m_ambiguous_reads;
  protected int m_exact_reads;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void note_write_issued(longint addr, logic [63:0] value,
                                  int unsigned seq);
    pending_write w = pending_write::type_id::create("w");
    w.addr = addr; w.value = value; w.issue_seq = seq;
    m_inflight.push_back(w);
  endfunction

  // §4's fourth row: a write whose completion is observed is settled;
  // one whose completion is not remains a member of the set.
  function void note_write_completed(longint addr, int unsigned seq);
    foreach (m_inflight[i])
      if (m_inflight[i].addr == addr && m_inflight[i].issue_seq == seq) begin
        m_inflight[i].completion_observed = 1;
        return;
      end
    `uvm_error("ORDER", $sformatf(
      "completion for addr %0h seq %0d with no matching issued write", addr, seq))
  endfunction

  // Settled writes can be retired into the reference memory in issue
  // order. Returns them oldest-first so a caller applies them in the
  // only order the test guaranteed.
  function void retire_settled(longint addr, ref pending_write out[$]);
    int i = 0;
    out.delete();
    // Sort-free: issue_seq is monotonic per test, so scanning in
    // insertion order yields issue order.
    while (i < m_inflight.size()) begin
      if (m_inflight[i].addr == addr && m_inflight[i].completion_observed) begin
        out.push_back(m_inflight[i]);
        m_inflight.delete(i);
      end else i++;
    end
  endfunction

  // The heart of the chapter. Given the recorded value (if any) and
  // the writes still in flight to this address, produce the SET of
  // legal expected payloads.
  //
  // has_recorded=0 means §2's never-written, and then the set is only
  // the in-flight candidates -- which may also be empty, in which
  // case nothing can be checked.
  function void legal_set(longint addr, bit has_recorded,
                          logic [63:0] recorded,
                          ref logic [63:0] out_set[$]);
    out_set.delete();
    if (has_recorded) out_set.push_back(recorded);
    foreach (m_inflight[i])
      if (m_inflight[i].addr == addr) begin
        bit dup = 0;
        // De-duplicate: two in-flight writes of the same value give
        // one legal outcome, not two. An inflated set size would
        // misreport how weak the checking is.
        foreach (out_set[j]) if (out_set[j] === m_inflight[i].value) dup = 1;
        if (!dup) out_set.push_back(m_inflight[i].value);
      end
    if (out_set.size() > m_max_set_size) m_max_set_size = out_set.size();
    if (out_set.size() > 1) m_ambiguous_reads++;
    else if (out_set.size() == 1) m_exact_reads++;
  endfunction

  function int inflight_for(longint addr);
    int n = 0;
    foreach (m_inflight[i]) if (m_inflight[i].addr == addr) n++;
    return n;
  endfunction

  function int inflight_total();   return m_inflight.size();   endfunction
  function int max_set_size();     return m_max_set_size;      endfunction
  function int ambiguous_reads();  return m_ambiguous_reads;   endfunction
  function int exact_reads();      return m_exact_reads;       endfunction

  // §4: a large set is a statement about the STIMULUS. A suite whose
  // sets are routinely large has weakened its own checking, and this
  // is the number that says so.
  function bit checking_mostly_exact();
    int total = m_exact_reads + m_ambiguous_reads;
    if (total == 0) return 0;          // nothing compared -- not a pass
    return (m_exact_reads * 2) > total;
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("ORDER", $sformatf(
      "exact=%0d ambiguous=%0d max_set=%0d still_inflight=%0d mostly_exact=%0b",
      m_exact_reads, m_ambiguous_reads, m_max_set_size,
      m_inflight.size(), checking_mostly_exact()), UVM_LOW)
    if (m_inflight.size() != 0)
      `uvm_error("ORDER", $sformatf(
        "%0d writes never observed completing", m_inflight.size()))
  endfunction
endclass

The set is de-duplicated, and that is not cosmetic. Two in-flight writes carrying the same value give one legal outcome. An inflated set size would misreport how weak the checking is — and §13's coverage model samples set size precisely so a suite can see its own checking strength, so the number has to mean something.

And checking_mostly_exact() returns false when nothing was compared. Zero exact and zero ambiguous reads is not a suite with perfect checking; it is a suite that checked nothing. This is 27.2 §2's vacuity argument arriving in a third component — after the monitor's consistency check in 27.3 §14, and with the same shape.

12. The Distributed Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ddr_bank_scoreboard -- one bank's reference memory, comparison, and
// three-way accounting.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY PER BANK (§5): 5.2 establishes bank independence, so the
// address space partitions cleanly -- and §3's ambiguity set is
// bounded by accesses in flight to the SAME address, which cannot
// span banks. Partitioning makes the checking STRONGER, not faster.
//
// WHY THREE TOTALS (§2): match, mismatch and unchecked are three
// different outcomes. Folding unchecked into either one makes a run
// that mostly did not check look like a run that mostly passed.
//
// WHAT IT DOES NOT MODEL:
//   - the device, the controller, or any cross-bank obligation
//
// WHAT IT WOULD MISS:
//   - everything in §6's table. Sixteen of these passing says
//     nothing about refresh, tFAW, bank-group spacing or power
//     state, and uncovered_obligations() exists to say so out loud.
// ---------------------------------------------------------------------
class ddr_bank_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(ddr_bank_scoreboard)

  uvm_analysis_imp #(ddr_txn, ddr_bank_scoreboard) txn_imp;

  protected int                m_bank;
  protected ddr_ref_memory     m_mem;
  protected ddr_order_resolver m_order;

  // §2's three totals, never folded together.
  protected int m_match;
  protected int m_mismatch;
  protected int m_unchecked;
  // And two more that a naive scoreboard silently discards.
  protected int m_skipped_unresolved_row;
  protected int m_skipped_abandoned;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    txn_imp = new("txn_imp", this);
    m_bank  = -1;
  endfunction

  function void configure(int bank, ddr_ref_memory mem,
                          ddr_order_resolver order);
    if (bank < 0)   `uvm_fatal("SB", "bank index must be non-negative")
    if (mem == null) `uvm_fatal("SB", "null reference memory")
    if (order == null) `uvm_fatal("SB", "null order resolver")
    m_bank  = bank;
    m_mem   = mem;
    m_order = order;
  endfunction

  protected function longint addr_of(ddr_txn t);
    // ILLUSTRATIVE flattening. Module 8 owns real address mapping;
    // all this needs is a stable per-bank key.
    return (longint'(t.row) << 16) | longint'(t.col);
  endfunction

  function void write(ddr_txn t);
    longint       a;
    logic [63:0]  exp;
    logic [63:0]  set[$];
    string        why;
    bit           has;
    bit           hit;

    if (t == null) begin
      `uvm_error("SB", "null transaction")
      return;
    end
    if (t.bank != m_bank) return;        // not ours

    // 27.3 §12: an abandoned transaction is a finding, not data.
    // Comparing its payload would compare a burst that never
    // completed.
    if (t.outcome == TXN_ABANDONED) begin
      m_skipped_abandoned++;
      return;
    end

    // 27.3 §6: a transaction whose row the monitor could not resolve
    // has no trustworthy address, so it cannot be scoreboarded. This
    // is SKIPPED and counted -- not passed.
    if (t.row_res != RES_KNOWN) begin
      m_skipped_unresolved_row++;
      return;
    end

    a = addr_of(t);

    if (t.dir == TXN_WRITE) begin
      // A write's own payload is the new expectation. Applying it
      // here -- on completion -- is what makes the model track
      // observed reality rather than intent.
      m_mem.write(a, t.data.size() > 0 ? t.data[0] : 64'h0);
      return;
    end

    // A read. §3: the expectation may be a value or a set.
    has = m_mem.expected(a, exp, why);
    m_order.legal_set(a, has, exp, set);

    if (set.size() == 0) begin
      // §2: no expected value at all. Counted separately, because a
      // run of these is a run that did not check.
      m_unchecked++;
      `uvm_info("SB", $sformatf(
        "bank %0d addr %0h unchecked: %s", m_bank, a,
        (why != "") ? why : "no recorded value and nothing in flight"), UVM_HIGH)
      return;
    end

    hit = 0;
    foreach (set[i]) if (set[i] === (t.data.size() > 0 ? t.data[0] : 64'h0)) hit = 1;

    if (hit) m_match++;
    else begin
      m_mismatch++;
      `uvm_error("SB", $sformatf(
        "bank %0d addr %0h: observed %0h not in the %0d legal value(s)",
        m_bank, a, (t.data.size() > 0 ? t.data[0] : 64'h0), set.size()))
    end
  endfunction

  function int matches();    return m_match;     endfunction
  function int mismatches(); return m_mismatch;  endfunction
  function int unchecked();  return m_unchecked; endfunction
  function int skipped_unresolved(); return m_skipped_unresolved_row; endfunction
  function int skipped_abandoned();  return m_skipped_abandoned;      endfunction

  function int total_reads_seen();
    return m_match + m_mismatch + m_unchecked;
  endfunction

  // §2: a result is only meaningful when something was checked. A
  // scoreboard reporting zero mismatches over zero comparisons is
  // the vacuity problem in a third component.
  function bit result_meaningful();
    return (m_match + m_mismatch) > 0;
  endfunction

  // §6, published rather than assumed. An environment assembling
  // sixteen of these can ask what nothing is checking.
  function void uncovered_obligations(ref string out[$]);
    out.delete();
    out.push_back("refresh obligations (Module 15)");
    out.push_back("activate windows across banks (14.8)");
    out.push_back("bank-group spacing (5.3)");
    out.push_back("device power state (6.2 CKE qualification)");
    out.push_back("ordering across banks");
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("SB", $sformatf(
      "bank %0d: match=%0d mismatch=%0d unchecked=%0d skipped_row=%0d skipped_aband=%0d meaningful=%0b",
      m_bank, m_match, m_mismatch, m_unchecked,
      m_skipped_unresolved_row, m_skipped_abandoned, result_meaningful()), UVM_LOW)
  endfunction
endclass

Five totals, not two. Match and mismatch are the obvious pair; unchecked, skipped-for-unresolved-row and skipped-abandoned are the three a naive scoreboard discards silently — and each corresponds to a real upstream condition. A run with a large skipped_unresolved total is telling you 27.3 §6's late-attach problem is affecting your checking, which no pass/fail number would reveal.

And uncovered_obligations() is a method rather than documentation. §6 establishes that sixteen clean per-bank scoreboards say nothing about device-level rules; an environment can now enumerate the gap programmatically, which is the only form in which it survives a handover.

13. What the Assertions Prove

Why this chapter's checks are immediate rather than concurrent. All five units here are classes, and a class has no clock — so there is no sampling edge for assert property to attach to, and 27.2 §11's bindable property set is the concurrent counterpart that lives at the pins instead. The nineteen checks below are immediate assertions over the scoreboard's own accounting, which is the equivalent instrument for testbench data structures, and they carry the same forbidden-condition discipline 27.2 established for properties: eleven of the nineteen assert that something must not happen. The distinction matters because the failures this chapter is guarding against are not timing failures. They are a model that answers when it should not, a set that claims members it cannot justify, and an accounting that turns “nothing was checked” into “nothing was wrong” — and none of those has a clock edge.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. All three components are classes with no clock, so
// their invariants are immediate assertions -- 27.3 §15 explains why
// this is the right instrument for testbench data structures, and
// 27.2 §11's bindable property set is the concurrent counterpart.
// Twelve of the nineteen below assert that something must NOT happen.
task automatic check_scoreboard_invariants(ddr_ref_memory mem,
                                           ddr_order_resolver ord,
                                           ddr_bank_scoreboard sb);
  logic [63:0] v; string why; bit has;
  logic [63:0] set[$];

  // P1 -- FORBIDDEN. A never-written address never yields an
  // expectation. §2: guessing manufactures failures on hardware that
  // is behaving correctly.
  has = mem.expected(64'hDEAD_BEEF_0000_0001, v, why);
  assert (has == 0 && why == "never written")
    else $error("P1: an unwritten address produced an expectation");

  // P2 -- a written address yields exactly its value.
  mem.write(64'h1000, 64'hA5A5);
  has = mem.expected(64'h1000, v, why);
  assert (has && v == 64'hA5A5)
    else $error("P2: a written address did not yield its value");

  // P3 -- FORBIDDEN. An invalidated address never yields an
  // expectation, and its reason is preserved for debug.
  mem.invalidate(64'h1000, "reset");
  has = mem.expected(64'h1000, v, why);
  assert (has == 0 && why == "reset")
    else $error("P3: an invalidated address produced an expectation");

  // P4 -- a write clears an invalidation. §10: whatever made the
  // address indeterminate, writing it determines it again.
  mem.write(64'h1000, 64'h5A5A);
  has = mem.expected(64'h1000, v, why);
  assert (has && v == 64'h5A5A)
    else $error("P4: a write did not clear the invalidation");

  // P5 -- FORBIDDEN. invalidate() without a reason is refused. §10:
  // never-written and invalidated are different findings.
  begin
    int before = mem.invalidations();
    mem.invalidate(64'h2000, "");
    assert (mem.invalidations() == before)
      else $error("P5: invalidate accepted an empty reason");
  end

  // P6 -- the footprint is the sum of known and invalidated entries.
  // §1: model size is a test property and must be reportable.
  assert (mem.footprint_entries() >= mem.entries())
    else $error("P6: footprint below the known-entry count");

  // P7 -- FORBIDDEN. The legal set is empty when nothing is recorded
  // and nothing is in flight. §2: an empty set means unchecked, and
  // it must not silently become a pass.
  ord.legal_set(64'hFFFF_0000, 0, 64'h0, set);
  assert (set.size() == 0)
    else $error("P7: a legal set was produced with no evidence");

  // P8 -- a recorded value alone gives a set of exactly one. §4's
  // first row: full-strength checking.
  ord.legal_set(64'h3000, 1, 64'h1234, set);
  assert (set.size() == 1 && set[0] == 64'h1234)
    else $error("P8: a recorded value did not give a singleton set");

  // P9 -- an in-flight write enlarges the set. §4's fourth row: a
  // write whose completion is unobserved gives two legal answers.
  ord.note_write_issued(64'h3000, 64'h9999, 1);
  ord.legal_set(64'h3000, 1, 64'h1234, set);
  assert (set.size() == 2)
    else $error("P9: an in-flight write did not enlarge the set (got %0d)", set.size());

  // P10 -- FORBIDDEN. The set never contains duplicates. §11: two
  // in-flight writes of one value give ONE legal outcome, and an
  // inflated size would misreport the checking strength.
  ord.note_write_issued(64'h3000, 64'h9999, 2);
  ord.legal_set(64'h3000, 1, 64'h1234, set);
  assert (set.size() == 2)
    else $error("P10: duplicate values inflated the set to %0d", set.size());

  // P11 -- FORBIDDEN. A completion with no matching issued write is
  // reported, never silently accepted.
  begin
    int before = ord.inflight_total();
    ord.note_write_completed(64'hBAD0, 99);
    assert (ord.inflight_total() == before)
      else $error("P11: an unmatched completion changed in-flight state");
  end

  // P12 -- FORBIDDEN. Checking is never reported mostly-exact when
  // nothing was compared. §11: zero and zero is not perfection.
  assert (!(ord.checking_mostly_exact()
            && (ord.exact_reads() + ord.ambiguous_reads() == 0)))
    else $error("P12: mostly-exact claimed with zero comparisons");

  // P13 -- max set size is at least any observed set size, so the
  // reported weakness cannot be understated.
  assert (ord.max_set_size() >= set.size())
    else $error("P13: max_set_size below an observed set size");

  // P14 -- PARTITION. The scoreboard's three read outcomes sum to the
  // reads it saw. §2: unchecked is a third outcome, not a pass. The two
  // skip counters are deliberately OUTSIDE this partition -- a skipped
  // transaction was never a read the scoreboard could act on, which is
  // what P16 and P17 below demonstrate by driving one of each.
  assert (sb.matches() + sb.mismatches() + sb.unchecked()
          == sb.total_reads_seen())
    else $error("P14: read outcomes do not partition");

  // P15 -- FORBIDDEN. A result is never meaningful with zero
  // comparisons. The vacuity argument in a third component.
  assert (!(sb.result_meaningful() && ((sb.matches() + sb.mismatches()) == 0)))
    else $error("P15: result reported meaningful with nothing compared");

  // P16 -- FORBIDDEN. A transaction with an unresolved row is never
  // compared. 27.3 §6: it has no trustworthy address, so the compare
  // counts must NOT move and the skip count must.
  begin
    ddr_txn u = ddr_txn::type_id::create("u");
    int cmp_before  = sb.matches() + sb.mismatches() + sb.unchecked();
    int skip_before = sb.skipped_unresolved();
    u.bank = 0; u.dir = TXN_READ; u.outcome = TXN_COMPLETE;
    u.row_res = RES_UNKNOWN;            // the condition under test
    sb.write(u);
    assert (sb.skipped_unresolved() == skip_before + 1
            && (sb.matches() + sb.mismatches() + sb.unchecked()) == cmp_before)
      else $error("P16: an unresolved-row transaction reached the compare");
  end

  // P17 -- FORBIDDEN. An abandoned transaction is never compared.
  // 27.3 §12: its burst never completed, so its payload is not data.
  // Checked the same way -- the skip must move, the compare must not.
  begin
    ddr_txn a = ddr_txn::type_id::create("a");
    int cmp_before  = sb.matches() + sb.mismatches() + sb.unchecked();
    int skip_before = sb.skipped_abandoned();
    a.bank = 0; a.dir = TXN_READ; a.outcome = TXN_ABANDONED;
    a.row_res = RES_KNOWN;              // resolved, but abandoned
    sb.write(a);
    assert (sb.skipped_abandoned() == skip_before + 1
            && (sb.matches() + sb.mismatches() + sb.unchecked()) == cmp_before)
      else $error("P17: an abandoned transaction reached the compare");
  end

  // P18 -- §6's gap is enumerable and non-empty. A scoreboard
  // claiming to cover everything would return an empty list, which
  // is the failure this method exists to prevent.
  begin
    string gaps[$];
    sb.uncovered_obligations(gaps);
    assert (gaps.size() >= 5)
      else $error("P18: uncovered obligations under-reported (%0d)", gaps.size());
  end

  // P19 -- FORBIDDEN. Retiring settled writes never returns an
  // unsettled one, or the model would apply a write that may not
  // have landed.
  begin
    pending_write out[$];
    ord.retire_settled(64'h3000, out);
    foreach (out[i])
      assert (out[i].completion_observed)
        else $error("P19: retired an unsettled write");
  end
endtask

Coverage is sampled over comparisons rather than signals, and the axes are the ones this chapter introduced:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. Coverage over the scoreboard's own decisions --
// which is where §3's set size and §2's third outcome exist. 27.5
// owns the DDR functional-coverage model; this covers the
// scoreboard's behaviour, which is a different question.
covergroup scoreboard_behaviour_cg with function sample(
    int set_size, int outcome_code, txn_dir_e dir,
    resolution_e row_res, int inflight_same_addr, int bank,
    int why_code, int footprint_entries, int exactness_code);
  option.per_instance = 1;

  // §3: the whole point. A suite whose sets are always 1 has never
  // exercised reordering; one whose sets are always large has
  // weakened its checking to membership everywhere.
  cp_set_size : coverpoint set_size {
    bins unchecked = {0};
    bins exact     = {1};
    bins pair      = {2};
    bins wide      = {[3:$]};
  }

  // §2's three outcomes plus the two skips a naive scoreboard drops.
  cp_outcome : coverpoint outcome_code {
    bins match          = {0};
    bins mismatch       = {1};
    bins unchecked      = {2};
    bins skip_row       = {3};
    bins skip_abandoned = {4};
  }

  cp_dir : coverpoint dir;

  // 27.3 §12's resolution flag, sampled here so the two chapters'
  // instruments meet: a suite full of unresolved rows is checking
  // less than its pass count suggests.
  cp_row_res : coverpoint row_res;

  cp_inflight : coverpoint inflight_same_addr {
    bins none = {0};
    bins one  = {1};
    bins many = {[2:$]};
  }

  cp_bank_edge : coverpoint bank {
    bins first = {0};
    bins mid   = {[1:14]};
    bins last  = {15};
  }

  // The cross that matters most: set size against outcome. A mismatch
  // at set size 1 is a hard failure; a mismatch at size 3 may be an
  // ordering model problem, and they need different investigations.
  x_size_outcome : cross cp_set_size, cp_outcome;

  // And direction against in-flight depth, so a suite cannot claim
  // reordering coverage from reads alone.
  x_dir_inflight : cross cp_dir, cp_inflight;

  // §2 and §10: the two third-states are different findings, and a
  // suite that has only ever seen one of them has not exercised the
  // reference model's uncertainty handling. Sampled as a code so a
  // never-written read and an invalidated read cannot merge.
  cp_why_unchecked : coverpoint why_code {
    bins not_unchecked = {0};
    bins never_written = {1};
    bins invalidated   = {2};
    bins no_evidence   = {3};
  }

  // §1: model size is a test property, so a campaign should see it
  // at more than one order of magnitude. A suite whose footprint is
  // always tiny has never tested the model's own scaling.
  cp_footprint : coverpoint footprint_entries {
    bins empty  = {0};
    bins small  = {[1:1000]};
    bins medium = {[1001:100000]};
    bins large  = {[100001:$]};
  }

  // §11's exactness verdict, sampled directly. The `false_at_zero`
  // bin exists because a run with nothing compared must be visibly
  // distinct from a run whose checking genuinely weakened -- both
  // report not-mostly-exact, for opposite reasons.
  cp_exactness : coverpoint exactness_code {
    bins mostly_exact  = {0};
    bins mostly_set    = {1};
    bins false_at_zero = {2};
  }

  // 27.3's resolution flag against the outcome. A suite where every
  // RES_UNKNOWN transaction was skipped and none was ever resolved
  // has a monitor problem wearing a scoreboard's pass count.
  x_res_outcome : cross cp_row_res, cp_outcome;
endgroup

14. Corner Cases

CaseBehaviourWhy
Read from a never-written addressUnchecked, counted separately§2 — guessing manufactures failures
Read from an invalidated addressUnchecked, with the reason preserved§10 — a different finding from never-written
Write to an invalidated address, then readChecked against the new valueP4 — a write restores determinacy
invalidate() with no reasonRefusedP5 — the two third-states must stay distinct
One recorded value, nothing in flightSet size 1 — exact checking§4's first row
One recorded value, one write in flightSet size 2§4's fourth row
Two in-flight writes of the same valueSet size stays 2P10 — one legal outcome, not two
Nothing recorded, nothing in flightSet size 0 — uncheckedP7
Completion with no matching issued writeReported; in-flight unchangedP11
Transaction with row_res != RES_KNOWNSkipped, counted27.3 §6 — no trustworthy address
Abandoned transactionSkipped, counted separately27.3 §12 — its burst never completed
Zero comparisons, zero mismatchesResult not meaningfulP15 — the vacuity argument again
Writes never observed completingError at report time§11 — they stay in the set forever otherwise
Sixteen scoreboards, all cleanSays nothing about §6's five obligationsP18 — the gap is enumerable
A transaction for another bankIgnored silentlyEach scoreboard owns one bank
Ordering taken from the controllerPasses always on ordering§7 — no property catches it

Rows twelve and fourteen are the pair to hold together. A scoreboard reporting zero mismatches may have compared nothing, and sixteen reporting zero mismatches may have compared nothing device-wide — two different emptinesses, both of which look like success.

15. DV — Testing the Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. Independent reference. Replays writes and reads
// against a QUEUE of (addr, value) pairs searched newest-first,
// rather than the DUT's associative array -- a different
// representation, so agreement is evidence rather than a restatement.
class scoreboard_reference;
  typedef struct { longint a; logic [63:0] v; bit valid; } cell_t;
  cell_t log[$];

  function void write(longint a, logic [63:0] v);
    cell_t c; c.a = a; c.v = v; c.valid = 1;
    log.push_back(c);
  endfunction

  function void invalidate(longint a);
    cell_t c; c.a = a; c.v = 0; c.valid = 0;
    log.push_back(c);
  endfunction

  // Newest-first scan: the last entry for an address wins.
  function bit expected(longint a, output logic [63:0] v);
    for (int i = log.size() - 1; i >= 0; i--)
      if (log[i].a == a) begin
        if (!log[i].valid) return 0;
        v = log[i].v;
        return 1;
      end
    return 0;      // never written
  endfunction

  function int distinct_addresses();
    longint seen[longint];
    foreach (log[i]) seen[log[i].a] = 1;
    return seen.size();
  endfunction
endclass
CheckWhat it establishes
Replay 5,000 random writes and reads; compare every expectationTwo representations of one model
Confirm distinct_addresses() matches footprint_entries()§1's footprint accounting
Read every address before writing itAll unchecked; zero mismatches; result not meaningful
Write then read the same address, nothing in flightSet size 1; exact match
Issue two writes to one address, read before either completesSet size 2; membership passes for both values
Issue two writes of the same valueSet size stays 2 — P10
Complete one write, then readSet shrinks; retire_settled returns only the settled one
Feed a transaction with RES_UNKNOWN rowSkipped; skipped_unresolved rises; not compared
Feed an abandoned transactionSkipped; skipped_abandoned rises
Invalidate, read, write, readUnchecked then checked; P3 and P4
End a test with a write never completingError — §11's report phase
Assemble 16 scoreboards, run a clean test, enumerate gaps5 uncovered obligations per bank — P18
Sample the covergroup over a serialised suitecp_set_size shows only exact; reordering never exercised
Sample it over a heavily overlapped suitewide bin hit; checking_mostly_exact() false

The thirteenth and fourteenth checks together produce the report worth publishing, because they are the two ways a scoreboard's result can be misleading in opposite directions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  TWO SUITES, TWO KINDS OF FALSE CONFIDENCE

  (A) the serialised suite -- exact checking, no coverage
        stimulus : one access to any address in flight at a time
        exact=41,208  ambiguous=0  max_set=1
        match=41,208  mismatch=0  unchecked=0
        checking_mostly_exact()  TRUE
        cp_set_size : exact HIT, pair MISS, wide MISS

        every property PASSES. Checking is at full strength.
        And the controller's reordering -- which Module 23 says is
        where its entire performance comes from -- was never
        exercised once. The scoreboard is perfect and the suite is
        narrow.

  (B) the overlapped suite -- broad coverage, weak checking
        stimulus : up to 8 writes per address in flight
        exact=2,104  ambiguous=38,902  max_set=7
        match=41,006  mismatch=0  unchecked=0
        checking_mostly_exact()  FALSE
        cp_set_size : exact HIT, pair HIT, wide HIT

        every property PASSES. Reordering is thoroughly exercised.
        And 94.8% of reads were checked only for MEMBERSHIP in a set
        of up to seven values -- so a controller returning the wrong
        one of seven legal payloads passes.

    both runs report zero mismatches. (A) checked hard and looked
    nowhere; (B) looked everywhere and checked loosely.

    diagnosis : §8's rows three and four are a real trade and a
      single pass/fail number hides which side of it you are on.
      Neither run is wrong; a suite that only ever does one of them
      is.

    the fix : publish exact against ambiguous (§11) and cover set
      size (§13). A campaign needs both shapes of run, and the two
      numbers are what show whether it has them.

16. Debugging

SymptomLikely causeHow to confirm
Mismatches on a design believed correctOrdering ambiguity compared as equality — §3max_set_size(); a size above 1 with equality compare
Mismatches only at high in-flight depthSame causecp_inflight against cp_outcome
Zero mismatches, zero confidenceNothing compared — §2result_meaningful(); the unchecked total
Most reads uncheckedReading before writingThe unchecked total against total_reads_seen()
Many reads skipped, never comparedUnresolved rows upstream — 27.3 §6skipped_unresolved(); the monitor attached late
Payload compared for a burst that never ranAn abandoned transaction reached the compareskipped_abandoned() should have caught it
Model memory grows without boundSparse footprint tracking test length — §1footprint_entries() over time
Mismatch reason unclearNever-written versus invalidated confusedThe why_not string; P3 preserves it
Ordering failures never caughtOrdering taken from the controller — §7No property catches this; review the resolver's inputs
All banks pass, device-level bug ships§6's gapuncovered_obligations(); 27.7 owns the fix
Writes accumulate in flight foreverCompletions never observed§11's report phase errors

Row nine has no instrument, exactly as 27.3 §18's equivalent row did. A scoreboard taking ordering from the design still catches data corruption, which is what makes it survivable and shippable — and it stops catching the illegal reorder, which is the bug the controller's own complexity creates.

17. Misconceptions

“A scoreboard predicts the expected value.” §1. For DDR it retains it. The expected payload of a read is determined by every write ever issued to that address, so the model is the array.

“The reference model must be as big as the device.” §1. It is sparse — one entry per address actually written — so its size is a property of the test, not the device.

“An unwritten address should read zero.” §2. It has no expected value. Expecting zero fails on correct hardware; accepting anything checks nothing.

“Unchecked reads can be counted as passes.” §2. Then a run that mostly did not check looks like a run that mostly passed — and P15 forbids reporting such a result as meaningful.

“The expected value is always a value.” §3. With writes in flight and reordering permitted, it is a set, and comparing against one member fails on correct hardware.

“Model the controller's ordering and the problem goes away.” §7. It does, along with the ability to catch an illegal reorder. The scoreboard then ratifies the scheduling decisions under test.

“Two in-flight writes mean two legal answers.” §11, P10. Not if they carry the same value. An inflated set misreports how weak the checking is.

“Serialising the stimulus is the safe choice.” §3, §15. It restores exact checking and removes the reordering coverage that is the controller's whole value. Report (A) is what that looks like.

“A central scoreboard is simpler.” §5. It also enlarges every ambiguity set, because ambiguity is bounded by accesses in flight to one address and addresses in different banks cannot collide.

“Sixteen passing per-bank scoreboards mean the device is right.” §6, P18. They say nothing about refresh, activate windows, bank-group spacing, power state or cross-bank ordering.

“Zero mismatches is the number that matters.” §15. Both reports in that section show zero mismatches, one from narrow checking and one from loose checking, and the single number cannot tell them apart.

18. Interview Reasoning

Where does a DDR scoreboard's expected value come from? From every write ever issued to that address — so the reference model is a model of the array, held sparsely as one entry per written address.

What do you expect from an address never written? Nothing. It has no expected value, and reporting either a specific value or an automatic pass is wrong in opposite directions — so it is a third outcome counted separately.

Why can the expected value be a set? Because a controller is permitted to reorder. If two writes to one address are in flight when a read issues, either may have landed, so there are two legal payloads.

What are your options with an ambiguous expectation? Serialise the stimulus and lose the reordering coverage; model the controller's ordering and lose independence; or accept a set and check membership. The third is usually right and is weaker.

Why is modelling the controller's ordering dangerous? The scoreboard then agrees with the scheduling decisions under test. It still catches data corruption, which is what makes it plausible enough to ship, and it stops catching an illegal reorder.

Why distribute a DDR scoreboard by bank? Because banks are independent, so the address space partitions cleanly — and ambiguity is bounded by accesses to the same address, which cannot span banks. Partitioning makes the checking stronger, not just faster.

What does that decomposition cost? Every cross-bank obligation loses its owner: refresh, activate windows, bank-group spacing, power state and cross-bank ordering. Sixteen clean scoreboards say nothing about any of them.

How many totals should a DDR scoreboard report? Five. Match, mismatch, unchecked, skipped-for-unresolved-address, and skipped-abandoned — because the last three are real upstream conditions that a pass/fail number hides.

Your scoreboard reports zero mismatches. What do you ask next? How many comparisons happened, and how large the legal sets were. Zero mismatches is consistent with checking nothing and with checking only membership in a set of seven.

What can a DDR reference model never detect? Anything the array does that the interface does not report — and specifically a device silently repairing an error, since on-die ECC makes a corrected fault look identical to no fault.

19. Exercises

  1. §1 bounds a dense model at 2 GB for a 16 Gb device. Derive the number of distinct addresses a test may touch before a sparse model exceeds that, stating your per-entry overhead assumption.

  2. §2 requires a third state. Show that no reserved payload value can encode it, and say what that implies about any interface returning a fixed-width value.

  3. Using §4's table, derive the maximum legal-set size for W writes in flight to one address with D distinct values. Then state which of W and D a test controls directly.

  4. §7's third row calls a documented policy “partly independent”. Construct the failure in which a silent policy change invalidates a scoreboard, and propose the check that would detect it.

  5. Extend §12's scoreboard to own one of §6's cross-bank obligations. Show why it cannot, and identify precisely what information it lacks.

  6. §11 de-duplicates the legal set. Construct the stimulus that maximises the gap between set size with and without de-duplication, and say what the inflated figure would have implied.

  7. §15's two reports both show zero mismatches. Design the single metric you would put on a dashboard to distinguish them, then state what it hides.

  8. A colleague proposes comparing every read against the set of all values ever written to that address. Show that this never produces a mismatch on a correct design, and identify which of §8's quantities it destroys.

20. Where This Goes

A DDR scoreboard is now a memory with an ordering problem. The expected payload of a read is determined by every write to that address, so the reference model is a sparse model of the array; an unwritten address has no expectation and must be counted as a third outcome; with writes in flight the expectation is a set and the comparison becomes membership; and the decomposition by bank that makes the sets small leaves every cross-bank obligation unowned.

Four results carry forward. The three-valued discipline has now appeared three times7.4 for bank state, 27.3 §6 for a monitor's reconstruction, and §2 here for expected data — and in every case the two-valued version is wrong in both directions. Ordering is the DDR-specific difficulty, and the tempting fix destroys independence in a way that still catches data corruption and therefore survives review. Five totals, not two, because unchecked and skipped are upstream conditions a pass/fail number hides. And §15's two reports both show zero mismatches from opposite failures — narrow exact checking, and broad loose checking.

Two things are left open. The illegal reorder that lands inside a legal set is undetectable here — §11's header states it, and it is the direct analogue of 27.1 §13's correlated vendor-model error and 27.3 §14's shared divergence. And §6's device-level gap is named rather than closed, because closing it is an environment-structure decision rather than a scoreboard one.

What all three checking chapters have now produced is instruments: properties that can be vacuous, monitors that can diverge, scoreboards that can compare nothing. Each one reports what it did, and none of them says what the campaign as a whole has exercised.

Chapter 27.5 takes that up. The SystemVerilog track owns cover groups, bins and crosses; uvm/coverage-driven-verification owns the methodology and puts the warning plainly — “coverage is the map of where verification has been, and a map is not the territory” — and it even 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, and the answer is not the obvious one: the interesting coverage of a DDR controller is almost never a command type, it is a crossing of device state with request history, and most of the crosses a first attempt writes are either unreachable or hit on cycle one.

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.