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:
a 16 Gb device, ILLUSTRATIVE
held densely : 16 Gb of model state -- 2 GB of simulator memory
held sparsely : one entry per address actually writtenA 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 A | Expected payload | A mismatch here means |
|---|---|---|
| Written — value recorded | that value | a real failure |
| Never written | none | nothing — the device may return anything |
| Written, then invalidated | none, 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:
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 0x22If 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:
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 <- 0x22The 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 A | Expected payload | Checking strength |
|---|---|---|
| Nothing | the recorded value | exact — full strength |
| One write, completed before the read issued | that value | exact |
| Two or more writes, order undetermined | a set | membership only |
| A write whose completion is unobserved | the old value or the new | two-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 scoreboard | Per-bank scoreboards | |
|---|---|---|
| Reference state | one map, all addresses | one map per bank |
| Ordering ambiguity | across every access in flight | only within a bank |
| A bank's traffic affects | the whole model | that bank's model |
| Cross-bank rules | naturally visible | belong 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:
| Obligation | Owner | Why no per-bank scoreboard sees it |
|---|---|---|
| Refresh — Module 15 | the device, all banks | a refresh is device-wide |
| Activate windows — 14.8 | a rolling limit across banks | it counts activates in any bank |
| Bank-group spacing — 5.3 | pairs of banks | the relation is between two models |
| Power state — 6.2 | device-wide | per-bank state says nothing about it |
| Ordering across banks | the controller | each 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 ordering | Independent? | Result |
|---|---|---|
| The test's issue order plus the protocol's rules | yes | a set, honestly derived — §3 |
| The controller's observed decisions | no | the scoreboard ratifies the design |
| The controller's documented policy | partly | independent 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
| Decision | Coverage | Checking | Cost |
|---|---|---|---|
| Sparse reference model — §1 | unchanged | unchanged | better |
| Report unwritten separately — §2 | unchanged | better — honest totals | slightly worse |
| Serialise to remove ambiguity — §3 | worse — no reordering exercised | better — exact compare | better |
| Accept a set — §3 | better | weaker but honest | worse |
| Take ordering from the design — §7 | unchanged | catastrophically worse | better |
| Per-bank decomposition — §5 | unchanged | better — smaller sets | better, 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
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
// ---------------------------------------------------------------------
// 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
endclassexpected() 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
// ---------------------------------------------------------------------
// 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
endclassThe 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
// ---------------------------------------------------------------------
// 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
endclassFive 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.
// 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
endtaskCoverage is sampled over comparisons rather than signals, and the axes are the ones this chapter introduced:
// 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;
endgroup14. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
| Read from a never-written address | Unchecked, counted separately | §2 — guessing manufactures failures |
| Read from an invalidated address | Unchecked, with the reason preserved | §10 — a different finding from never-written |
| Write to an invalidated address, then read | Checked against the new value | P4 — a write restores determinacy |
invalidate() with no reason | Refused | P5 — the two third-states must stay distinct |
| One recorded value, nothing in flight | Set size 1 — exact checking | §4's first row |
| One recorded value, one write in flight | Set size 2 | §4's fourth row |
| Two in-flight writes of the same value | Set size stays 2 | P10 — one legal outcome, not two |
| Nothing recorded, nothing in flight | Set size 0 — unchecked | P7 |
| Completion with no matching issued write | Reported; in-flight unchanged | P11 |
Transaction with row_res != RES_KNOWN | Skipped, counted | 27.3 §6 — no trustworthy address |
| Abandoned transaction | Skipped, counted separately | 27.3 §12 — its burst never completed |
| Zero comparisons, zero mismatches | Result not meaningful | P15 — the vacuity argument again |
| Writes never observed completing | Error at report time | §11 — they stay in the set forever otherwise |
| Sixteen scoreboards, all clean | Says nothing about §6's five obligations | P18 — the gap is enumerable |
| A transaction for another bank | Ignored silently | Each scoreboard owns one bank |
| Ordering taken from the controller | Passes 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
// 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| Check | What it establishes |
|---|---|
| Replay 5,000 random writes and reads; compare every expectation | Two representations of one model |
Confirm distinct_addresses() matches footprint_entries() | §1's footprint accounting |
| Read every address before writing it | All unchecked; zero mismatches; result not meaningful |
| Write then read the same address, nothing in flight | Set size 1; exact match |
| Issue two writes to one address, read before either completes | Set size 2; membership passes for both values |
| Issue two writes of the same value | Set size stays 2 — P10 |
| Complete one write, then read | Set shrinks; retire_settled returns only the settled one |
Feed a transaction with RES_UNKNOWN row | Skipped; skipped_unresolved rises; not compared |
| Feed an abandoned transaction | Skipped; skipped_abandoned rises |
| Invalidate, read, write, read | Unchecked then checked; P3 and P4 |
| End a test with a write never completing | Error — §11's report phase |
| Assemble 16 scoreboards, run a clean test, enumerate gaps | 5 uncovered obligations per bank — P18 |
| Sample the covergroup over a serialised suite | cp_set_size shows only exact; reordering never exercised |
| Sample it over a heavily overlapped suite | wide 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:
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
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Mismatches on a design believed correct | Ordering ambiguity compared as equality — §3 | max_set_size(); a size above 1 with equality compare |
| Mismatches only at high in-flight depth | Same cause | cp_inflight against cp_outcome |
| Zero mismatches, zero confidence | Nothing compared — §2 | result_meaningful(); the unchecked total |
| Most reads unchecked | Reading before writing | The unchecked total against total_reads_seen() |
| Many reads skipped, never compared | Unresolved rows upstream — 27.3 §6 | skipped_unresolved(); the monitor attached late |
| Payload compared for a burst that never ran | An abandoned transaction reached the compare | skipped_abandoned() should have caught it |
| Model memory grows without bound | Sparse footprint tracking test length — §1 | footprint_entries() over time |
| Mismatch reason unclear | Never-written versus invalidated confused | The why_not string; P3 preserves it |
| Ordering failures never caught | Ordering taken from the controller — §7 | No property catches this; review the resolver's inputs |
| All banks pass, device-level bug ships | §6's gap | uncovered_obligations(); 27.7 owns the fix |
| Writes accumulate in flight forever | Completions 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 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 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.
-
Using §4's table, derive the maximum legal-set size for
Wwrites in flight to one address withDdistinct values. Then state which ofWandDa test controls directly. -
§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.
-
Extend §12's scoreboard to own one of §6's cross-bank obligations. Show why it cannot, and identify precisely what information it lacks.
-
§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.
-
§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.
-
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 times — 7.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
Related tutorials
- Related topic
DDR Monitors
A DDR monitor sees pins and must derive transactions. When it cannot resolve a bank's open row, reporting a confident wrong answer is worse than reporting none.
- Related topic
UVM Architecture for DDR
Three interfaces that are not variations of each other. They share no clock, no transaction identity, and no notion of what a failure is — and the cross-bank obligations finally need a component.
- Related topic
Senior Verification Strategy
Use UVM, add assertions, add coverage names three tools and zero obligations. Start from what must be true, then ask what evidence each kind of truth admits — and hold the eight reasons a green assertion proves nothing.
- Related topic
UCIe Scoreboards
Building a distributed UCIe scoreboard that follows obligations rather than expected packets — four independent models instead of one class, associative storage keyed by identity and generation, correlation across semantic operations, transport objects and physical attempts, epoch tracking, recovery-safe state, and a first-divergence report that names the layer instead of the symptom.
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.
