DDR · Module 28
Data Corruption
One wrong byte is consistent with every failure family in this module. What narrows it is the distribution across lane, beat, address, pattern, frequency and time.
Every chapter before this one ended by handing something here. Chapter 28.1 committed a tap from a bitmap that was not the eye. Chapter 28.2 reconstructed a violated contract and noted that required arrives as an input. Chapter 28.3 reported a clean obligation while data was wrong. Chapter 28.4 reached valid and stopped, because the digital evidence was exhausted.
So a corrupted byte is consistent with all of them, plus the channel, plus the device. The list of causes is this entire module and then some.
Which means the central question is not "what caused it". You cannot ask that yet. The question is:
WHAT STRUCTURE EXISTS IN THE ERROR?
A single wrong byte carries almost no information. A thousand wrong bytes carry a great deal, because their distribution across lane, beat, address, bank, pattern, frequency, temperature and time is a fingerprint — and each fingerprint is consistent with a few mechanisms rather than all of them.
1. A Mismatch Is Not a Finding
Module 27 built the scoreboard, and 27.4 §2 was careful about what a mismatch means: the observed payload was not in the set of legal expected values. That is the start of this chapter, not the end of anything.
What a single mismatch gives you:
| Field | Information |
|---|---|
| Address | one point in a space of billions |
| Expected vs observed | the difference, which is the useful part |
| Direction | read or write — §3 |
| Time | one instant |
The difference is where the structure lives. Not the value — the XOR.
expected 0x33 0011 0011
observed 0xB3 1011 0011
XOR 0x80 1000 0000 one bit, position 7
the VALUE tells you almost nothing. The XOR says: exactly one bit
flipped, and it was bit 7. Accumulate that over a thousand errors
and you have a distribution over bit positions -- which is a
different and far more useful object than a thousand values.So the first instrument is an XOR accumulator, not a log of values. §13's block accumulates the OR of all error XORs — a bitmap of every bit position that ever failed — and the AND of them, which is every position that failed every time. Those two numbers separate a single stuck bit from scattered single-bit errors, and no list of expected/observed pairs makes that visible at a glance.
2. The Axes
Sixteen ways to classify a corruption. The discipline is to fill in as many as the data supports before proposing a mechanism, because each axis eliminates mechanisms rather than confirming one.
| Axis | Values | What it discriminates |
|---|---|---|
| Direction | read / write | which datapath, and which side's settings — 28.4 §8 |
| Persistence | persistent / intermittent | a stuck fault from a marginal one — §3 |
| Bit position | a bitmap | a stuck bit from scattered flips — §1 |
| Byte lane | a bitmap | a per-lane path from a shared one |
| Burst beat | a histogram | a burst-position effect from a uniform one |
| Address bits | correlated / uncorrelated | the address path and the map |
| Bank | a bitmap | a per-bank resource |
| Row | boundary-correlated or not | page crossing, activate timing |
| Rank | a bitmap | loading, termination, rank selection |
| Pattern | data-dependent or not | data-dependent channel behaviour |
| Frequency | sensitive or not | timing and channel |
| Temperature | sensitive or not | retention, drift, channel |
| Refresh phase | correlated or not | 28.3 §9's window histogram |
| Training state | before / after a retrain | 28.1 |
| Power transition | correlated or not | state restored incorrectly after a transition |
| Boot type | cold / warm | 19.5's cold-versus-warm hazard |
No single axis is decisive. Two or three usually are. A failure that is write-only, lane-local, pattern-independent and frequency-independent is a very short list; a failure that is read-only, uniform across lanes, pattern-dependent and frequency-sensitive is a different short list.
And filling in an axis with "unknown" is a legitimate and useful answer, exactly as 27.3 §6 established for a monitor's reconstruction. An axis nobody measured must not be recorded as "uncorrelated", because that is the value that eliminates mechanisms.
3. Persistence — The First Fork
Cheap, and it splits the space roughly in half.
| Persistent | Intermittent | |
|---|---|---|
| Definition | the same access fails every time | the same access fails sometimes |
| Mechanism class | a stuck fault: a broken connection, a wrong stored value, a decode error | a marginal one: timing, channel, thermal, a race |
| Reproducer | trivial — one access | needs a rate and a duration |
| Frequency sensitivity | usually none | usually present |
| Cheapest next step | read the same address repeatedly | establish an error rate before changing anything |
The test is one loop: read the same address a thousand times and count failures. A thousand failures is persistent; three is intermittent; zero means the failure is not where you think it is and the reproducer is wrong.
Establishing a rate before changing anything is the discipline intermittent failures demand, and it is the one most often skipped. Without a baseline rate, no subsequent experiment is interpretable — a change that takes the rate from 10⁻⁹ to 10⁻¹⁰ looks like a fix and is indistinguishable from noise unless you know the original and ran long enough.
the interpretability requirement, stated plainly
baseline : 40 errors in 8 hours
after a change : 0 errors in 20 minutes
this proves NOTHING. 20 minutes at the baseline rate expects
40 * (20/480) = 1.67 errors, and observing zero of an expected 1.67
is unremarkable.
to claim a 10x improvement you must run long enough that the
baseline would have produced enough errors for zero to be
surprising -- which for 40-per-8-hours is many hours, not minutes.
this is why "establish the rate first" is not pedantry. It sets
the duration every later experiment must run for.4. Direction — Read, Write, or Both
The second cheap fork, and it eliminates whole families.
Run a read-only test and a write-only test separately, each with the other direction's traffic minimised.
| Result | Supports | Eliminates |
|---|---|---|
| Reads only | read capture, read timing, the read reference level | write-side settings and the write datapath |
| Writes only | write timing, write levelling, the write reference level, the data mask | read-side settings |
| Both, similarly | a shared resource: the array, the address path, the clock, a shared setting | direction-specific settings |
| Both, one worse | a shared cause with asymmetric margin | nothing — the weakest result |
Row one and row two are strong because few mechanisms are direction-asymmetric. Chapter 28.4 §8 establishes the reference levels belong to specific directions and that their ranges do not overlap where it matters, so a direction-only failure points hard at that direction's own configuration.
Row three is the one people misread as "everything is broken". It is a narrowing result: it eliminates every direction-specific setting, which is most of the settings a bring-up touches.
A practical warning about write-only testing. Verifying a write requires reading it back, so a "write-only" test still reads. The way to keep the fork clean is to write with the suspect configuration and read back with a known-good one — for instance at a lower frequency, or after a full retrain — and to say explicitly in the report which configuration each direction used.
5. Bit, Lane, and Beat Structure
Three axes that share an instrument and separate very different mechanisms.
Bit position. §1's XOR accumulation gives an OR bitmap and an AND bitmap.
| OR / AND | Means |
|---|---|
| OR has one bit, AND has one bit | always the same bit — a stuck path |
| OR has one bit, AND is zero | that bit, not every time — marginal on one path |
| OR is wide, AND is zero | scattered — not a per-bit fault |
| OR is wide, AND is wide | many bits fail together every time — a bus-wide or word-wide fault |
Byte lane. A lane is a group of bits with its own delay setting, its own strobe relationship and its own physical path. CURRICULUM-DERIVED from 6.9 and 19.1: a lane-local failure narrows to that lane's delay setting, its capture logic, its package and board trace, and its device pin.
Burst beat. Chapter 12.1 owns what a burst length counts and 12.2 the ordering. A histogram of failing beat positions is one of the most diagnostic objects in this chapter:
| Beat distribution | Supports |
|---|---|
| Uniform across beats | not a burst-position effect |
| First beat only | burst start: strobe gating, turnaround, the preamble region |
| Last beat only | burst end: postamble, gating closing early |
| First and last | the edges of the strobe window — 20.5 |
| One middle beat, always the same | a counter or mux selecting wrongly at that position |
| Growing towards the end | something drifting across the burst |
Rows two through four are the shape that redirects an investigation towards capture, and they are invisible unless the beat index is recorded with every error. A corruption log without a beat index has discarded the axis most likely to localise a capture problem, which is why §13's accumulator keeps a histogram rather than a count.
6. Address Structure
The axis that requires the most arithmetic and repays it.
The question is not which addresses failed — it is which address bits correlate with failure. CURRICULUM-DERIVED from 8.6, which owns the map and its inversion: an address bit corresponds to a field of the device geometry, and knowing which bit is implicated tells you which field.
The technique is the same as §1's, one level up:
accumulate over failing addresses:
OR of all failing addresses
AND of all failing addresses
a bit set in AND -> that bit was 1 in EVERY failure
a bit clear in OR -> that bit was 0 in EVERY failure
either way the bit is CORRELATED with failure.
bits that are 1 in OR and 0 in AND varied across failures, so
they are uncorrelated.
ILLUSTRATIVE, over 812 failing addresses:
OR = 0x3FFF_FE00
AND = 0x0000_0200
bit 9 : set in AND -> always 1 -> CORRELATED
bits 0-8 : clear in OR -> always 0 -> CORRELATED
bits 10+ : varied -> uncorrelated
reading that through 8.6's map: the low nine bits being always
zero means every failure was at a 512-byte-aligned address, and
bit 9 always set narrows it further. That is a statement about a
FIELD, and the map says which field.Then the inversion matters. Knowing bit 9 is implicated is only useful if you know what bit 9 selects, and 8.6 owns that map — including that it changes with the mapping policy 18.1 chooses. So the same bit implicates different hardware under different policies, and a report naming a bit without naming the policy is incomplete.
Row-boundary correlation is a special case worth checking explicitly. Failures clustered at row boundaries implicate the activate/precharge sequence around a page crossing — Module 9's subject — rather than the data path. The test is to compare the failure rate for accesses that cross a row boundary against those that do not, which requires only that the generator knows the map.
7. Pattern, Frequency, Temperature
The three axes that most often get over-read.
Pattern dependence. If failures depend on the data being written, the mechanism is data-dependent. That is a real narrowing and it points at the channel — but it is not proof of one, because a pattern also changes the number of transitions, the mask behaviour if masks are in use, and which ECC syndromes arise.
| Pattern result | Supports | Does not establish |
|---|---|---|
| Only with high-transition patterns | data-dependent channel behaviour | any specific electrical mechanism |
| Only with one specific value | a stuck or aliased value, or a decode case | a channel effect at all |
| Independent of pattern | a mechanism that does not care about data | that data was not involved |
Row two is the giveaway that it is not a channel problem. A failure that occurs only when writing one specific byte value is almost certainly a logic or decode issue — a channel does not single out a value, it responds to transitions.
Frequency sensitivity. The most over-interpreted result in DDR debugging, and 28.2 §8 already gave the argument for timing violations. For corruption the candidate list is longer:
| Hypothesis when lowering frequency helps | Cheaper than a channel measurement? |
|---|---|
| A timing parameter conversion error — 28.2 §11 | yes |
| The wrong frequency bin's settings | yes |
A max(nCK, ns) dual-term crossover — 28.2 §12 | yes |
| A committed calibration valid only at the trained frequency — 28.4 §5 | yes |
| Reduced traffic rate at lower frequency, so less exposure | yes |
| Genuine channel margin | no — 28.6 |
Five digital hypotheses before the physical one, and the fifth is the confound 28.3 §9 also had to control for: lower frequency usually means fewer accesses per second, so the error rate per second falls even when the rate per access does not. Report errors per access.
Temperature sensitivity. Real and diagnostic, and it implicates several layers at once: retention (15.4), timing drift, channel behaviour, and any calibration whose provenance tag omitted temperature (28.4 §5).
The discriminating structure is whether a retrain at the new temperature fixes it. If it does, the mechanism is a setting that drifted out of validity; if it does not, the mechanism is not a setting.
8. Correlated With Refresh, Training, or a Power Transition
Three time-correlation axes that route directly to other chapters — and the routing is the value.
| Correlation | Route to | What to read first |
|---|---|---|
| Position in the refresh window | 28.3 §9 | that chapter's bucket histogram — start-clustered versus end-clustered |
| Immediately after a retrain | 28.1 | the committed tap and its window width |
| Immediately after a power-state exit | 28.4 §5 | whether settings were re-established or restored |
| Only on the first boot of the day | temperature, and 19.5's cold path | the cold-versus-warm sequence difference |
The power-transition row deserves emphasis because its mechanism is specific. A power-state exit can invalidate settings that were valid before it. CURRICULUM-DERIVED from 28.4 §5: if the exit path restores a stored value rather than re-establishing it, and the conditions changed during the low-power interval, the restored value is stale by exactly the mechanism §5 of that chapter describes — and the tag comparison would have caught it.
The "first boot of the day" signature is worth naming because it sounds like folklore and is not. It is a temperature signature: the first cold boot calibrates at a temperature the system never returns to. The test is to force a cold boot after a soak, which separates cold boot from cold device.
9. Signature → Hypotheses → Experiment
The chapter's working table. Each row is a signature, its plausible mechanisms, the evidence that would contradict them, and the next experiment.
| Signature | Plausible mechanisms | Contradicted by | Next discriminating experiment |
|---|---|---|---|
| Same bit, every time | stuck path, broken connection, a wrong bit in a register field | the bit moves when lanes are remapped | swap the lane mapping; the failing bit should move with the lane |
| One lane, scattered bits | that lane's delay setting, capture, or physical path | the lane changes with the device | swap the device; if the lane stays, it is board or package |
| First beat only | strobe gating at burst start, turnaround, preamble | changing burst length moves nothing | change burst length — 12.1; the beat index should scale |
| Last beat only | postamble, gating closing early | the failure survives a longer burst unchanged | as above |
| One middle beat, fixed | a counter or mux wrong at that position | the index changes with the starting column | vary the starting column — 12.2 |
| Address bit correlated | the address path, the map, aliasing | the bit is uncorrelated under a different mapping policy | change the mapping policy — 18.1 |
| Row-boundary clustered | activate/precharge around a page crossing | boundary-crossing and non-crossing rates are equal | generate crossing and non-crossing streams separately |
| Pattern-dependent | data-dependent channel behaviour | only one specific value fails | classify: transitions or a single value — §7 |
| Frequency-sensitive | five digital causes, then channel — §7 | errors per access are flat | re-measure per access at both frequencies |
| Temperature-sensitive | retention, drift, stale calibration, channel | a retrain at temperature fixes it | retrain at the new temperature |
| Refresh-window correlated | 28.3 §9's four | errors are uniform in the window | bucket by window position |
Row one's experiment is the strongest in the table and the least used. If remapping lanes moves the failing bit, the fault is in the lane path; if the bit stays put, it is downstream of the remap. One reconfiguration splits the space cleanly, and it needs no new instrumentation.
Rows three through five share one experiment: change the burst parameters and watch whether the beat index follows. A beat-position fault that scales with burst length is a burst-relative mechanism; one that stays at absolute beat 5 regardless is a fixed-position fault in the datapath.
10. A Beat-Specific Error, Cycle by Cycle
One burst, one wrong beat, and the fields that make it a signature rather than an incident. ILLUSTRATIVE data.
One bit, one lane, one beat — and the three fields that say so
10 cyclesThe err_xor lane is the one that matters, and it is why §1 insists on the difference rather than the value. 33 versus B3 is two numbers; 80 is one bit in position 7, which is a fact about hardware.
Three fields make this incident into a data point: beat index 5, XOR 0x80, and the address. Accumulate those three over a thousand errors and §5's and §6's tables become answerable. A log recording only "mismatch at address X" has thrown away the beat and the bit, and both are recoverable only by re-running.
And captured is sticky from the moment of the first error. One burst produces one error here; a real failure produces thousands, and the thousandth is a consequence of whatever state the first one left. §13's block preserves the first and accumulates structure from the rest — which are two different jobs and need two different mechanisms.
11. The Localization Funnel
Two blocks, two different jobs, and the split is the design decision. First-error capture latches and refuses to update; the structure accumulator never latches and updates on everything. A single block trying to do both either loses the first error or loses the distribution.
And the ECC query sits between the structure and the conclusion, deliberately. §12 establishes that correction changes what you observe, so a structure read without knowing whether correction is active is a structure of unknown provenance.
12. The ECC Visibility Problem
Chapter 25.4 owns on-die ECC as a bounded-fault mechanism with transparency and scrub, and it has a section titled The Visibility Problem. That problem is this chapter's hardest complication, and it has three distinct parts.
First: correction hides faults. A fault within the correction bound is repaired and the interface returns correct data. So a device with on-die ECC can be accumulating exactly the faults you are hunting while presenting a clean interface — and the absence of observed corruption is not the absence of faults.
Second: the bound is a bound. Correction covers a limited number of faults per protected word. Beyond it, behaviour is not "no correction" — it can be mis-correction, where the mechanism computes a repair for a fault pattern it cannot actually distinguish and applies it.
Third, and this is the part that breaks §5's and §6's tables: the observed error structure is not the underlying fault structure.
why mis-correction breaks a bit-position histogram
underlying fault : bits 3 and 11 are wrong
correction sees : a syndrome consistent with a single fault
it "repairs" : bit 22
what the interface returns : bits 3, 11 AND 22 are wrong
the observed XOR now has THREE bits, one of which was never
faulty, and a bit-position histogram accumulated across many such
events reports positions the hardware never failed at.
so the honest statement is not "bit 22 fails" but "the observed
pattern is a function of the fault pattern AND the correction
applied to it, and the mapping is not invertible from the
interface".The practical instruction is to establish which correction layers are active before interpreting any structure, and to record that in the report alongside the structure. A bit histogram from a device with correction enabled and one with it disabled are different measurements — and if the mechanism can be disabled for a debug run, doing so is one of the highest-value experiments in this chapter.
13. First-Error Capture and Structure Accumulation
// ---------------------------------------------------------------------
// corruption_evidence -- CLASSIFICATION: synthesisable, BINDABLE.
//
// TWO mechanisms, deliberately separate:
// FIRST-ERROR CAPTURE latches and refuses to update. The thousandth
// error is a consequence of the state the first one left, so the
// first is the evidence (Module 28's second law).
// STRUCTURE ACCUMULATION never latches. A single error carries
// almost nothing; the DISTRIBUTION over many carries §5's and
// §6's answers.
// A single block doing both loses one of them.
//
// WHY THE XOR AND NOT THE VALUE (§1): "33 vs B3" is two numbers; the
// XOR 0x80 is one bit in position 7, which is a fact about hardware.
// OR over XORs = every position that ever failed. AND over XORs =
// every position that failed EVERY time. Those two separate a stuck
// bit from scattered flips, which no list of values does.
//
// WHY ADDRESS OR/AND (§6): a bit set in AND was 1 in every failure; a
// bit clear in OR was 0 in every failure. Either way it is CORRELATED,
// and 8.6's map says which field it selects.
//
// WHAT IT CANNOT TELL YOU: the FAULT structure when correction is
// active. §12 -- mis-correction can make bits appear wrong that never
// failed, so a bit-position conclusion is a statement about fault
// composed with correction.
//
// SYNTHESIS: capture registers, a small histogram, four accumulators.
// ---------------------------------------------------------------------
module corruption_evidence #(
parameter int DATA_W = 64,
parameter int ADDR_W = 34,
parameter int LANES = 8,
parameter int BEATS = 16, // max burst beats -- 12.1 owns the contract
parameter int BANKS = 16
)(
input logic clk,
input logic rst_n,
// ---- mismatch report (from Module 27's scoreboard)
input logic err_valid,
input logic [ADDR_W-1:0] err_addr,
input logic [DATA_W-1:0] err_expected,
input logic [DATA_W-1:0] err_observed,
input logic [$clog2(BEATS)-1:0] err_beat,
input logic [$clog2(BANKS)-1:0] err_bank,
input logic err_is_read,
input logic ecc_correction_active, // §12
input logic clear,
// ---- FIRST-ERROR CAPTURE (latched, first wins)
output logic first_valid,
output logic [ADDR_W-1:0] first_addr,
output logic [DATA_W-1:0] first_xor,
output logic [$clog2(BEATS)-1:0] first_beat,
output logic [$clog2(BANKS)-1:0] first_bank,
output logic [LANES-1:0] first_lane_bm,
output logic first_is_read,
output logic first_ecc_active,
// ---- STRUCTURE ACCUMULATION (never latched)
output logic [DATA_W-1:0] xor_or,
output logic [DATA_W-1:0] xor_and,
output logic [LANES-1:0] lane_or,
output logic [LANES-1:0] lane_and,
output logic [ADDR_W-1:0] addr_or,
output logic [ADDR_W-1:0] addr_and,
output logic [BANKS-1:0] bank_bm,
output logic [15:0] beat_hist [BEATS],
output logic [31:0] err_count,
output logic [31:0] err_reads,
output logic [31:0] err_writes,
output logic [31:0] err_with_ecc_active,
output logic [$clog2(DATA_W+1)-1:0] first_popcount
);
// $clog2(1) is 0, making every index vector illegal as [-1:0]. A
// power-of-two test alone accepts 1, so the bounds are explicit.
initial begin
if (LANES < 2) $fatal(1, "corruption_evidence: LANES must be >= 2 (got %0d)", LANES);
if (BEATS < 2) $fatal(1, "corruption_evidence: BEATS must be >= 2");
if (BANKS < 2) $fatal(1, "corruption_evidence: BANKS must be >= 2");
if (DATA_W < LANES || (DATA_W % LANES) != 0)
$fatal(1, "corruption_evidence: DATA_W %0d must be a multiple of LANES %0d", DATA_W, LANES);
if (ADDR_W < 2) $fatal(1, "corruption_evidence: ADDR_W must be >= 2");
end
localparam int LANE_W = DATA_W / LANES;
logic [DATA_W-1:0] xr;
logic [LANES-1:0] lane_bm;
// Which lanes does this error touch? A lane is LANE_W contiguous
// bits -- 6.9 owns the bus organisation.
always_comb begin
xr = err_expected ^ err_observed;
for (int l = 0; l < LANES; l++)
lane_bm[l] = |xr[l*LANE_W +: LANE_W];
end
always_ff @(posedge clk) begin
if (!rst_n || clear) begin
first_valid <= 1'b0;
first_addr <= '0;
first_xor <= '0;
first_beat <= '0;
first_bank <= '0;
first_lane_bm <= '0;
first_is_read <= 1'b0;
first_ecc_active <= 1'b0;
first_popcount <= '0;
// OR accumulators start at zero; AND accumulators must start at
// ALL ONES or the first AND would clear everything.
xor_or <= '0;
xor_and <= {DATA_W{1'b1}};
lane_or <= '0;
lane_and <= {LANES{1'b1}};
addr_or <= '0;
addr_and <= {ADDR_W{1'b1}};
bank_bm <= '0;
err_count <= '0;
err_reads <= '0;
err_writes <= '0;
err_with_ecc_active <= '0;
for (int b = 0; b < BEATS; b++) beat_hist[b] <= '0;
end else if (err_valid) begin
// ---- FIRST WINS. Nothing but `clear` replaces this.
if (!first_valid) begin
first_valid <= 1'b1;
first_addr <= err_addr;
first_xor <= xr;
first_beat <= err_beat;
first_bank <= err_bank;
first_lane_bm <= lane_bm;
first_is_read <= err_is_read;
// §12: whether correction was active is part of the evidence,
// because it determines whether a bit-position conclusion is
// available at all.
first_ecc_active <= ecc_correction_active;
first_popcount <= $countones(xr);
end
// ---- ACCUMULATE, always. §1: the distribution is the finding.
xor_or <= xor_or | xr;
xor_and <= xor_and & xr;
lane_or <= lane_or | lane_bm;
lane_and <= lane_and & lane_bm;
addr_or <= addr_or | err_addr;
addr_and <= addr_and & err_addr;
bank_bm <= bank_bm | (1'b1 << err_bank);
// §5: the beat histogram. Saturating -- a wrapped bin reading 3
// after 65,539 errors would invert the distribution's shape,
// which is the one thing this array exists to show.
if (beat_hist[err_beat] != 16'hFFFF)
beat_hist[err_beat] <= beat_hist[err_beat] + 1'b1;
if (err_count != 32'hFFFF_FFFF) err_count <= err_count + 1'b1;
if (err_is_read) begin
if (err_reads != 32'hFFFF_FFFF) err_reads <= err_reads + 1'b1;
end else begin
if (err_writes != 32'hFFFF_FFFF) err_writes <= err_writes + 1'b1;
end
if (ecc_correction_active && err_with_ecc_active != 32'hFFFF_FFFF)
err_with_ecc_active <= err_with_ecc_active + 1'b1;
end
end
endmoduleThe AND accumulators initialise to all ones, and that is not a detail. An AND accumulator starting at zero stays zero forever and reports no bit failed every time regardless of the data. It would be a register that cannot produce a finding — the hardware form of 27.2 §2's check that cannot fail.
beat_hist saturates per bin rather than wrapping. A wrapped bin reading 3 after 65,539 errors would invert the shape of the distribution, and the shape is the entire output of that array. Saturation loses magnitude and preserves shape, which is the correct trade for a histogram used to compare bins against each other.
And first_ecc_active is captured with the first error. §12 establishes that a bit-position conclusion is only available when correction is not in play, so whether it was active is part of the evidence rather than context someone remembers later.
14. Ranking the Structure
// ---------------------------------------------------------------------
// corruption_classifier -- SIMULATION-ONLY.
//
// Maps §13's accumulated structure to §9's ranked hypotheses and the
// next discriminating experiment. Returns a LIST, never a cause.
//
// WHY IT WITHHOLDS BIT CONCLUSIONS UNDER CORRECTION (§12): 25.4's
// visibility problem means mis-correction can make bits appear wrong
// that never failed. A bit-position verdict is then a statement about
// fault composed with correction, and the composition is not
// invertible from the interface.
//
// WHAT IT WOULD MISS: two mechanisms with one observed structure --
// the normal case per §1, not an edge case.
// ---------------------------------------------------------------------
typedef enum {
SIG_NONE, SIG_STUCK_BIT, SIG_LANE_LOCAL, SIG_FIRST_BEAT, SIG_LAST_BEAT,
SIG_MID_BEAT, SIG_ADDR_CORRELATED, SIG_BANK_LOCAL, SIG_SCATTERED
} corr_sig_e;
class corruption_snapshot;
int data_w, lanes, beats, addr_w;
longint xor_or, xor_and;
int lane_or, lane_and;
longint addr_or, addr_and;
int bank_bm;
int beat_hist[];
int err_count, err_reads, err_writes, err_with_ecc_active;
bit ecc_was_active;
endclass
class corruption_classifier;
function int popcount(longint v, int width);
int n = 0;
for (int i = 0; i < width; i++) if (v[i]) n++;
return n;
endfunction
function int peak_beat(corruption_snapshot s);
int best = -1, bestv = 0;
foreach (s.beat_hist[i])
if (s.beat_hist[i] > bestv) begin bestv = s.beat_hist[i]; best = i; end
return best;
endfunction
// §6: a bit is correlated when it was 1 in every failure (set in
// AND) or 0 in every failure (clear in OR). Returns a mask.
function longint correlated_addr_bits(corruption_snapshot s);
longint m = 0;
for (int i = 0; i < s.addr_w; i++)
if (s.addr_and[i] || !s.addr_or[i]) m[i] = 1'b1;
return m;
endfunction
// §12: available only when correction was NOT active.
function bit bit_conclusion_available(corruption_snapshot s);
return !s.ecc_was_active;
endfunction
function corr_sig_e signature(corruption_snapshot s);
int pk;
if (s.err_count == 0) return SIG_NONE;
// A stuck bit is the strongest and cheapest conclusion -- and it
// is WITHHELD under correction, because §12 says the observed bit
// positions may include bits that never failed.
if (bit_conclusion_available(s)
&& popcount(s.xor_and, s.data_w) == 1
&& popcount(s.xor_or, s.data_w) == 1) return SIG_STUCK_BIT;
// Lane structure survives correction: a lane correlates with paths
// OUTSIDE the protected word.
if (popcount(s.lane_and, s.lanes) >= 1
&& popcount(s.lane_or, s.lanes) == popcount(s.lane_and, s.lanes))
return SIG_LANE_LOCAL;
pk = peak_beat(s);
if (pk == 0) return SIG_FIRST_BEAT;
if (pk == s.beats - 1) return SIG_LAST_BEAT;
if (pk > 0) return SIG_MID_BEAT;
if (popcount(correlated_addr_bits(s), s.addr_w) > 0)
return SIG_ADDR_CORRELATED;
if (popcount(s.bank_bm, 32) == 1) return SIG_BANK_LOCAL;
return SIG_SCATTERED;
endfunction
function void rank(corruption_snapshot s, ref corr_sig_e out[$]);
out.delete();
if (s.err_count == 0) return;
out.push_back(signature(s));
// Direction is an independent axis (§4) and always worth reporting
// alongside, because it eliminates whole families rather than
// naming one.
if (s.err_reads > 0 && s.err_writes == 0) out.push_back(SIG_LANE_LOCAL);
endfunction
function string next_experiment(corruption_snapshot s);
case (signature(s))
SIG_STUCK_BIT:
return "swap the lane mapping; the failing bit should move with the lane if the fault is in the lane path, and stay put if it is downstream of the remap";
SIG_LANE_LOCAL:
return "swap the device; if the failing lane stays the same, the cause is board or package rather than device";
SIG_FIRST_BEAT, SIG_LAST_BEAT:
return "change the burst length (12.1); a burst-relative mechanism moves the failing beat index, a fixed-position datapath fault does not";
SIG_MID_BEAT:
return "vary the starting column (12.2); if the failing beat index follows the starting column the mechanism is burst-relative";
SIG_ADDR_CORRELATED:
return "change the mapping policy (18.1); a bit that stops correlating under a different policy implicates the field that bit selected, not the bit";
SIG_BANK_LOCAL:
return "redistribute traffic across banks; if the failures follow the bank index the cause is per-bank, if they follow the address the map is implicated";
SIG_SCATTERED:
return "establish the error rate per ACCESS at two frequencies before changing anything (§7); scattered errors need a rate, not a sample";
default:
return "no errors accumulated: the reproducer is not exercising the failure";
endcase
endfunction
// §12's boundary, enforced rather than commented.
function string bit_position_verdict(corruption_snapshot s);
if (!bit_conclusion_available(s))
return "unavailable: on-die correction was active, so the observed bit positions are a function of the fault pattern AND the correction applied to it (25.4's visibility problem, §12)";
if (popcount(s.xor_and, s.data_w) == 1)
return "one bit failed in every observed error -- a stuck-path hypothesis, testable by remapping lanes";
return "no single bit failed in every error: the structure is not per-bit";
endfunction
// §3's interpretability requirement, as a computation nobody can
// skip. Returns the hours a follow-up run must last for ZERO errors
// to be surprising at the baseline rate.
function real hours_needed_for_zero_to_mean_something(
real baseline_errors, real baseline_hours, real want_expected);
if (baseline_errors <= 0.0 || baseline_hours <= 0.0) return -1.0;
return want_expected * baseline_hours / baseline_errors;
endfunction
endclassbit_position_verdict() returns a refusal when correction was active, and that refusal is §12 made unskippable. A classifier that reported a stuck bit from a corrected device would be confidently wrong about which bit.
hours_needed_for_zero_to_mean_something() exists because §3's arithmetic is the step most often skipped. Given a baseline of 40 errors in 8 hours, asking for an expected 5 errors requires 5 × 8 / 40 = 1 hour — so a 20-minute clean run proves nothing and the function says so numerically rather than leaving it to judgement.
And signature() checks lane structure before beat structure, and both before bit structure under correction. That ordering is §12's qualification in priority form: the conclusions that survive correction are offered first.
15. Minimal Reproducer Reduction
Structure tells you what to test. A minimal reproducer is what makes testing cheap enough to iterate, and reduction is a discipline rather than luck.
Reduce one dimension at a time, and after each reduction confirm the failure still reproduces at a measurable rate.
| Dimension | Reduce toward | Stop when |
|---|---|---|
| Address range | one page, then one address | the rate drops materially |
| Pattern | one value, then one bit | the failure stops |
| Burst length | the shortest that fails | shorter no longer fails |
| Banks | one bank | the rate drops |
| Traffic rate | the lowest that still fails | the rate becomes unmeasurable |
| Duration | the shortest run with enough errors | §3's arithmetic says stop |
The rule that makes this work is to change one dimension per step and keep the rate measurable. Reducing two at once and losing the failure leaves you unable to say which reduction killed it.
And a reduction that kills the failure is itself evidence. If narrowing to one bank stops the failure, the mechanism involves more than one bank — which is a finding, not a failed step. Record it and back off by one dimension, because a reduction sequence is a search and its dead ends carry information exactly as 28.1's failing taps do.
One warning specific to intermittent failures. Reducing traffic rate reduces exposure, so the error rate falls for reasons unrelated to the mechanism. Normalise per access at every step, or reduction will appear to fix the problem repeatedly.
16. What the Assertions Prove
// Bound to §13's evidence block. Every property carries `disable iff`,
// and every antecedent is covered below -- 27.2 measured this
// curriculum at 78.53% implications, so a silent pass is otherwise
// indistinguishable from an unbound block.
module corruption_evidence_sva #(
parameter int DATA_W = 64, parameter int LANES = 8,
parameter int BEATS = 16, parameter int ADDR_W = 34
)(
input logic clk, rst_n, clear,
input logic err_valid, err_is_read, ecc_correction_active,
input logic [DATA_W-1:0] err_expected, err_observed, first_xor, xor_or, xor_and,
input logic [ADDR_W-1:0] addr_or, addr_and,
input logic [LANES-1:0] lane_or, lane_and, first_lane_bm,
input logic first_valid, first_ecc_active,
input logic [31:0] err_count, err_reads, err_writes, err_with_ecc_active
);
// ---- P1. FIRST WINS. The captured evidence never changes without
// an explicit clear. Module 28's second law: the thousandth error is
// a consequence of the state the first one left.
property p_first_error_sticky;
@(posedge clk) disable iff (!rst_n)
(first_valid && !clear) |=> (first_xor == $past(first_xor)
&& first_lane_bm == $past(first_lane_bm));
endproperty
assert property (p_first_error_sticky)
else $error("evidence: a later error overwrote the first");
// ---- P2. FORBIDDEN. A later error must not clear first_valid.
property p_first_valid_sticky;
@(posedge clk) disable iff (!rst_n)
(first_valid && !clear) |=> first_valid;
endproperty
assert property (p_first_valid_sticky)
else $error("evidence: first_valid dropped without a clear");
// ---- P3. The capture is the FIRST error's XOR, not a later one's.
property p_capture_is_first_xor;
@(posedge clk) disable iff (!rst_n)
(err_valid && !first_valid)
|=> (first_xor == $past(err_expected ^ err_observed));
endproperty
assert property (p_capture_is_first_xor)
else $error("evidence: captured XOR is not the first error's");
// ---- P4. FORBIDDEN. A mismatch is never reported with equal
// expected and observed. That combination means the scoreboard and
// this block disagree about what an error is.
property p_error_has_a_difference;
@(posedge clk) disable iff (!rst_n)
err_valid |-> (err_expected != err_observed);
endproperty
assert property (p_error_has_a_difference)
else $error("evidence: an error was reported with no difference");
// ---- P5. INVARIANT. The OR accumulator is monotone: a bit position
// once seen failing stays recorded. §1 depends on accumulation.
property p_xor_or_monotone;
@(posedge clk) disable iff (!rst_n)
(!clear) |=> ((xor_or & $past(xor_or)) == $past(xor_or));
endproperty
assert property (p_xor_or_monotone)
else $error("evidence: xor_or lost a bit");
// ---- P6. INVARIANT. The AND accumulator is anti-monotone: it only
// ever loses bits. Together with P5 this makes the pair meaningful.
property p_xor_and_antimonotone;
@(posedge clk) disable iff (!rst_n)
(!clear) |=> ((xor_and & $past(xor_and)) == xor_and);
endproperty
assert property (p_xor_and_antimonotone)
else $error("evidence: xor_and gained a bit");
// ---- P7. FORBIDDEN. AND is always a subset of OR. A bit that
// failed every time must be a bit that failed at least once, and a
// violation here means the accumulators were initialised wrongly --
// the specific failure §13 warns about for AND starting at zero.
property p_and_subset_of_or;
@(posedge clk) disable iff (!rst_n)
(err_count != 32'd0) |-> ((xor_and & xor_or) == xor_and);
endproperty
assert property (p_and_subset_of_or)
else $error("evidence: xor_and is not a subset of xor_or");
// ---- P8. Same relationship for lanes and addresses.
property p_lane_and_subset;
@(posedge clk) disable iff (!rst_n)
(err_count != 32'd0) |-> ((lane_and & lane_or) == lane_and);
endproperty
assert property (p_lane_and_subset)
else $error("evidence: lane_and is not a subset of lane_or");
property p_addr_and_subset;
@(posedge clk) disable iff (!rst_n)
(err_count != 32'd0) |-> ((addr_and & addr_or) == addr_and);
endproperty
assert property (p_addr_and_subset)
else $error("evidence: addr_and is not a subset of addr_or");
// ---- P9. PARTITION. Reads plus writes account for every error. §4
// is an axis, and an error belonging to neither direction means the
// axis was never populated.
property p_direction_partition;
@(posedge clk) disable iff (!rst_n)
(err_reads + err_writes == err_count);
endproperty
assert property (p_direction_partition)
else $error("evidence: %0d reads + %0d writes != %0d errors",
err_reads, err_writes, err_count);
// ---- P10. FORBIDDEN. The ECC-active error count never exceeds the
// total. §12 depends on that count being trustworthy.
property p_ecc_count_bounded;
@(posedge clk) disable iff (!rst_n)
(err_with_ecc_active <= err_count);
endproperty
assert property (p_ecc_count_bounded)
else $error("evidence: ECC-active errors exceed total errors");
// ---- P11. The first error's ECC flag matches the condition at
// capture. §12: whether a bit conclusion is available at all depends
// on this, so it must be captured WITH the error, not read later.
property p_first_ecc_captured;
@(posedge clk) disable iff (!rst_n)
(err_valid && !first_valid)
|=> (first_ecc_active == $past(ecc_correction_active));
endproperty
assert property (p_first_ecc_captured)
else $error("evidence: the first error's ECC context was not captured");
// ---- P12. FORBIDDEN. err_count advances only on an error.
property p_count_only_on_error;
@(posedge clk) disable iff (!rst_n)
(!err_valid) |=> (err_count == $past(err_count));
endproperty
assert property (p_count_only_on_error)
else $error("evidence: err_count advanced with no error");
// ---- 27.2 §7: publish every antecedent.
cover property (@(posedge clk) disable iff (!rst_n) err_valid);
cover property (@(posedge clk) disable iff (!rst_n) err_valid && err_is_read);
cover property (@(posedge clk) disable iff (!rst_n) err_valid && !err_is_read);
cover property (@(posedge clk) disable iff (!rst_n) err_valid && !first_valid);
cover property (@(posedge clk) disable iff (!rst_n) err_valid && first_valid);
cover property (@(posedge clk) disable iff (!rst_n) err_valid && ecc_correction_active);
cover property (@(posedge clk) disable iff (!rst_n) clear);
cover property (@(posedge clk) disable iff (!rst_n) first_valid);
cover property (@(posedge clk) disable iff (!rst_n) $countones(xor_and) == 1);
cover property (@(posedge clk) disable iff (!rst_n) $countones(xor_or) == 1);
cover property (@(posedge clk) disable iff (!rst_n) $countones(xor_or) > 4);
cover property (@(posedge clk) disable iff (!rst_n) $countones(lane_and) == 1);
cover property (@(posedge clk) disable iff (!rst_n) err_reads != 32'd0 && err_writes == 32'd0);
cover property (@(posedge clk) disable iff (!rst_n) err_count > 32'd1000);
endmodule17. DV — Testing the Evidence
// SIMULATION-ONLY. Independent reference: it stores every error in a
// QUEUE and computes the structure by rescanning, rather than
// accumulating incrementally. A different algorithm, so agreement is
// evidence -- 27.4 §15's argument.
class evidence_reference;
typedef struct { longint addr; longint xr; int beat; int bank; bit rd; } err_t;
err_t log[$];
function void record(longint addr, longint xr, int beat, int bank, bit rd);
err_t e; e.addr = addr; e.xr = xr; e.beat = beat; e.bank = bank; e.rd = rd;
log.push_back(e);
endfunction
function longint xor_or();
longint v = 0;
foreach (log[i]) v |= log[i].xr;
return v;
endfunction
// Rescanning AND: start from the FIRST entry rather than all-ones,
// which is the natural formulation when the data is already held --
// and it is the check that catches §13's all-ones initialisation
// being wrong.
function longint xor_and();
longint v;
if (log.size() == 0) return 0;
v = log[0].xr;
foreach (log[i]) v &= log[i].xr;
return v;
endfunction
function longint addr_or();
longint v = 0;
foreach (log[i]) v |= log[i].addr;
return v;
endfunction
function longint addr_and();
longint v;
if (log.size() == 0) return 0;
v = log[0].addr;
foreach (log[i]) v &= log[i].addr;
return v;
endfunction
function int beat_count(int b);
int n = 0;
foreach (log[i]) if (log[i].beat == b) n++;
return n;
endfunction
function int reads();
int n = 0;
foreach (log[i]) if (log[i].rd) n++;
return n;
endfunction
// §1's first error, by construction rather than by latching.
function longint first_xor();
if (log.size() == 0) return 0;
return log[0].xr;
endfunction
function int errors(); return log.size(); endfunction
endclass| Check | What it establishes |
|---|---|
| Replay §10's single error; compare every field | Latched capture equals the reference's first entry |
| 50,000 random errors through both | Incremental and rescanning structure agree on all six accumulators |
Zero errors, read xor_and | Block holds all-ones; reference returns 0 — the documented difference, asserted explicitly |
One error with XOR 0x80 | xor_or == xor_and == 0x80; SIG_STUCK_BIT |
Two errors, 0x80 and 0x40 | xor_or == 0xC0, xor_and == 0x00; not a stuck bit |
| Errors only in lane 3 | lane_and has one bit; SIG_LANE_LOCAL |
| Errors only at beat 0 | SIG_FIRST_BEAT; experiment names burst length |
Errors only at beat BEATS−1 | SIG_LAST_BEAT |
| All failing addresses 512-aligned | addr_or low 9 bits clear; correlated mask has 9 bits |
Report an error with expected == observed | P4 fires |
Initialise xor_and to zero instead of all ones | P7 fires — the documented initialisation bug |
| Inject reads only | err_writes == 0; P9 holds |
| 70,000 errors into one beat bin | Bin saturates; shape preserved, magnitude lost |
Set ecc_correction_active and request a bit verdict | Classifier refuses — §12 |
hours_needed_for_zero_to_mean_something(40, 8, 5) | Returns 1.0 hour |
| Run with the scoreboard disconnected | All 12 properties pass; all 14 covers empty |
Two reports are worth publishing, and the second is the one this chapter exists to prevent:
TWO PASSING CORRUPTION INVESTIGATIONS
(A) the evidence block was never connected
all 12 properties PASS
err_count 0
first_valid 0
xor_or 0x0000000000000000
xor_and 0xFFFFFFFFFFFFFFFF <-- initial
------------------------------------------------
cover err_valid 0 hits
... all 14 covers 0 hits
ten of twelve properties are implications and never armed. The
two INVARIANTS -- direction partition and ECC bound -- pass
because 0 + 0 == 0 and 0 <= 0.
and note xor_and reads ALL ONES, which an unwary report would
render as "every bit failed every time". It is the
initialisation value, and the only field that says so is
err_count.
(B) a confident, self-consistent, wrong conclusion
stimulus : 4,212 errors on a device with on-die correction
ENABLED; nobody recorded that
err_count 4,212
err_with_ecc_active 4,212
xor_and 0x0000000000400000 (bit 22)
xor_or 0x0000000000400000 (bit 22)
lane_or 0x04 (lane 2)
beat_hist uniform
------------------------------------------------
all 12 properties PASS
all 14 covers HIT
the structure is perfect: ONE bit, every time, one lane,
uniform across beats. SIG_STUCK_BIT. The obvious conclusion is
a stuck path at bit 22, and a week of board and package work
follows.
§12 says the conclusion is unavailable. With correction
active, the observed bit positions are a function of the fault
pattern AND the correction applied -- and mis-correction can
make a bit appear wrong that never failed. The underlying
fault may be at entirely different positions.
the classifier's bit_position_verdict() returns a REFUSAL
here, and err_with_ecc_active == err_count is the field that
triggers it.
diagnosis : (A) is the vacuity case, with an extra trap: an
all-ones AND accumulator reads like a catastrophic finding and
is an initialisation value. (B) is §12 -- the investigation was
not wrong about the structure, it was wrong about what the
structure was of.
the fix : (A) read err_count before any accumulator. (B) record
which correction layers are active WITH the evidence, and if the
mechanism can be disabled for a debug run, do that first -- it
is the highest-value experiment in this chapter.18. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
LANES = 1, BEATS = 1, BANKS = 1 | Elaboration fails | $clog2(1) is 0; index vectors would be [-1:0] |
DATA_W not a multiple of LANES | Elaboration fails | lane slicing would be ill-defined |
| Zero errors | xor_and reads all ones | initialisation; err_count is the field that says so |
xor_and initialised to zero | P7 fires | a register that cannot produce a finding |
| One error | xor_or == xor_and | a single sample cannot distinguish stuck from scattered |
| Two errors, disjoint XORs | xor_and == 0 | not a stuck bit |
Error with expected == observed | P4 fires | the scoreboard and block disagree |
| 70,000 errors in one beat bin | Bin saturates | shape preserved, magnitude lost |
| Correction active | Bit verdict refused | §12 — the composition is not invertible |
| Correction layer unspecified | Instrument limitation, stated | §16's callout — one bit cannot name a layer |
| Reads only | err_writes == 0, P9 holds | §4 — eliminates write-side settings |
| Lower frequency, errors per second halve | Nothing proved | §7 — accesses per second halved too |
| Narrowing to one bank stops the failure | A finding, not a failed reduction | §15 — the mechanism spans banks |
| 20-minute clean run after a 40-per-8-hours baseline | Proves nothing | §3 — expected 1.67 errors |
Rows three and four are a pair worth holding together: the AND accumulator reads catastrophic when empty and reads nothing when initialised wrongly, and only err_count distinguishes the first from a real finding.
19. Misconceptions
“The corrupted value tells you what happened.” §1. The XOR does. One wrong byte is two numbers; 0x80 is one bit in position 7.
“One error is enough to start diagnosing.” §1, §3. One error cannot distinguish stuck from scattered, persistent from intermittent, or correlated from uncorrelated.
“It failed at address X, so address X is bad.” §6. What matters is which address bits correlate across many failures, and what those bits select under the active mapping policy.
“A clean interface means a clean array.” §12. On-die correction repairs faults within its bound and does not report them. Absence of observed corruption is not absence of faults.
“On-die ECC will tell us if something is wrong.” §12. It is a bounded-fault mechanism, not an error-reporting one, and 25.4's visibility section rules out that inference.
“The failing bit is bit 22.” §12. With correction active, mis-correction can make a bit appear wrong that never failed. The observed positions are fault composed with correction.
“Lowering the frequency fixed it.” §7. Five digital hypotheses come before the channel, and lower frequency also means fewer accesses per second. Report per access.
“It only happens with random data, so it is a channel problem.” §7. Pattern dependence narrows; it does not identify. A failure on one specific value is almost certainly logic, not channel.
“Errors near refresh mean retention.” §8, and 28.3 §9 in full. Bucket by window position first: start-clustered is a restart bug.
“The reproducer got smaller and the failure went away, so we are close.” §15. A reduction that kills the failure is evidence that the removed dimension was necessary. Back off one step.
“No errors in twenty minutes means it is fixed.” §3. At 40 errors per 8 hours, twenty minutes expects 1.67. Compute the duration before claiming the result.
“A self-consistent structure is a diagnosis.” §16's callout, report (B). A broken harness accumulates a beautiful and fictitious distribution, and a corrected device accumulates a real one about the wrong thing.
20. Interview Reasoning
A read returns one wrong byte. What do you record? The XOR of expected and observed, the beat index, the lane bitmap, the address, the direction, and whether any correction layer was active. The value alone is nearly useless.
Why the XOR rather than the value? Because the XOR is a bit pattern you can accumulate. OR over many errors gives every position that ever failed; AND gives every position that failed every time, and together they separate a stuck bit from scattered flips.
What is your first fork? Persistence. Read the same address a thousand times — a thousand failures is a stuck fault, three is marginal, and zero means the reproducer is wrong.
What is your second? Direction. Read-only and write-only tests each eliminate a whole family of settings, and few mechanisms are direction-asymmetric.
Errors are always at beat 0. What does that suggest and how do you test it? Something at burst start — strobe gating, turnaround, the preamble region. Change the burst length: a burst-relative mechanism moves the failing index, a fixed datapath fault does not.
How do you find address correlation? Accumulate OR and AND over failing addresses. A bit set in AND was 1 every time; a bit clear in OR was 0 every time. Either way it is correlated, and the map says which field it selects.
The failing bit is always bit 22. What do you do first? Check whether on-die correction was active. If it was, the conclusion is unavailable — mis-correction can make a bit appear wrong that never failed. If it was not, remap the lanes and see whether the failing bit moves.
Why is that remap experiment so strong? It splits the space with no new instrumentation: if the bit follows the lane, the fault is in the lane path; if it stays, it is downstream of the remap.
Baseline is 40 errors in 8 hours. A change produces zero errors in 20 minutes. What do you conclude? Nothing. Twenty minutes expects 1.67 errors at that rate. Run long enough that zero would be surprising.
Your reproducer stops failing when you narrow to one bank. Have you lost the bug? No — you have learned the mechanism involves more than one bank. Record it and back off one dimension.
21. Exercises
-
§1 argues for the XOR over the value. Construct the corruption for which the value is more informative than the XOR, or prove none exists.
-
§5 gives four OR/AND combinations. For each, name a mechanism and the experiment that would confirm it, then say which two combinations a single error cannot distinguish.
-
§6's example implicates bit 9 and bits 0–8. Using 8.6's map, state what that narrows to under two different mapping policies, and say what the report must include to be reproducible.
-
§12 argues bit-position conclusions do not survive correction while lane and beat conclusions do. Construct the fault where a lane conclusion also fails to survive, and say what makes it exceptional.
-
§14's classifier refuses a bit verdict under correction. Design the additional evidence that would let it offer a qualified one, and say what the qualification would have to say.
-
§3's arithmetic gives one hour for an expected 5 errors. Derive the duration for a 100× improvement claim at the same baseline, and state the assumption your derivation makes about the error process.
-
§15 reduces one dimension at a time. Derive the worst-case number of runs for six dimensions, then design a cheaper order using §9's signature to choose which dimension to reduce first.
-
A colleague concludes a stuck bit at position 22 from report (B)'s data and proposes a board respin. Write the one-paragraph objection, and name the single experiment that would settle it.
22. Where This Goes
A corrupted byte is consistent with every failure family in this module, so the investigation begins with structure rather than cause. The XOR is the diagnostic quantity and its OR and AND over many errors separate a stuck path from scattered flips; lane, beat and address structure each eliminate different families; sixteen axes narrow the space and two or three usually settle it; and on-die correction means the observed structure is a composition of fault and correction whose bit-position component is not invertible from the interface.
Four results carry forward. Accumulate the XOR, not the value, and initialise the AND accumulator to all ones or it can never produce a finding. First-error capture and structure accumulation are different jobs — one latches and refuses, the other never latches. Errors per access, and a duration computed from the baseline rate, or no experiment after the first is interpretable. And record which correction layers are active with the evidence, because report (B)'s week of board work was spent on a structure that was real and about the wrong thing.
Two things stay open. A self-consistent structure is not a diagnosis — §16's callout is explicit that a broken harness produces a beautiful one — so every signature yields a list and an experiment rather than a cause. And the instrument cannot name which correction layer is active, which §16 records as a limitation rather than asserting past it.
Chapter 28.6 takes the one hypothesis this chapter keeps deferring. Several signatures here — pattern dependence, frequency sensitivity, lane-local failure after every digital cause is excluded — are the ones people escalate to the physical channel, and often too early. The question there is not what the channel is doing. It is narrower and answerable: what digital observation actually justifies moving a hypothesis into a domain where RTL cannot follow — and what that domain then requires, which is instrumentation no assertion can substitute for.
Continue learning
Related tutorials
- Related topic
Ranks
A rank is the set of devices that together supply the controller's data width. Because ranks share one physical data bus, only one may drive it at a time — which makes a rank a question of resource ownership, and means adding ranks adds capacity without adding bandwidth.
- Related topic
Dual Sub-Channels
Fourteen command bits with no RAS, CAS or WE at all, commands over one or two cycles, and CS_n inside the command code — plus a mode-register space duplicated per sub-channel.
- Related topic
Training Failures
A pass bitmap is a projection of four independent failure domains onto one bit per setting. Eleven signatures, each consistent with several causes, and the experiment that separates them.
- Related topic
Timing Violations
A violation names its own evidence: two commands, one shared resource, a required separation and an actual one. Reconstructing those four facts is the whole investigation.
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.
