Skip to content
VLSI Mentor

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:

FieldInformation
Addressone point in a space of billions
Expected vs observedthe difference, which is the useful part
Directionread or write — §3
Timeone instant

The difference is where the structure lives. Not the value — the XOR.

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

AxisValuesWhat it discriminates
Directionread / writewhich datapath, and which side's settings — 28.4 §8
Persistencepersistent / intermittenta stuck fault from a marginal one — §3
Bit positiona bitmapa stuck bit from scattered flips — §1
Byte lanea bitmapa per-lane path from a shared one
Burst beata histograma burst-position effect from a uniform one
Address bitscorrelated / uncorrelatedthe address path and the map
Banka bitmapa per-bank resource
Rowboundary-correlated or notpage crossing, activate timing
Ranka bitmaploading, termination, rank selection
Patterndata-dependent or notdata-dependent channel behaviour
Frequencysensitive or nottiming and channel
Temperaturesensitive or notretention, drift, channel
Refresh phasecorrelated or not28.3 §9's window histogram
Training statebefore / after a retrain28.1
Power transitioncorrelated or notstate restored incorrectly after a transition
Boot typecold / warm19.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.

PersistentIntermittent
Definitionthe same access fails every timethe same access fails sometimes
Mechanism classa stuck fault: a broken connection, a wrong stored value, a decode errora marginal one: timing, channel, thermal, a race
Reproducertrivial — one accessneeds a rate and a duration
Frequency sensitivityusually noneusually present
Cheapest next stepread the same address repeatedlyestablish 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.

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

ResultSupportsEliminates
Reads onlyread capture, read timing, the read reference levelwrite-side settings and the write datapath
Writes onlywrite timing, write levelling, the write reference level, the data maskread-side settings
Both, similarlya shared resource: the array, the address path, the clock, a shared settingdirection-specific settings
Both, one worsea shared cause with asymmetric marginnothing — 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 / ANDMeans
OR has one bit, AND has one bitalways the same bit — a stuck path
OR has one bit, AND is zerothat bit, not every time — marginal on one path
OR is wide, AND is zeroscattered — not a per-bit fault
OR is wide, AND is widemany 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 distributionSupports
Uniform across beatsnot a burst-position effect
First beat onlyburst start: strobe gating, turnaround, the preamble region
Last beat onlyburst end: postamble, gating closing early
First and lastthe edges of the strobe window — 20.5
One middle beat, always the samea counter or mux selecting wrongly at that position
Growing towards the endsomething 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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 resultSupportsDoes not establish
Only with high-transition patternsdata-dependent channel behaviourany specific electrical mechanism
Only with one specific valuea stuck or aliased value, or a decode casea channel effect at all
Independent of patterna mechanism that does not care about datathat 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 helpsCheaper than a channel measurement?
A timing parameter conversion error — 28.2 §11yes
The wrong frequency bin's settingsyes
A max(nCK, ns) dual-term crossover — 28.2 §12yes
A committed calibration valid only at the trained frequency — 28.4 §5yes
Reduced traffic rate at lower frequency, so less exposureyes
Genuine channel marginno — 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.

CorrelationRoute toWhat to read first
Position in the refresh window28.3 §9that chapter's bucket histogram — start-clustered versus end-clustered
Immediately after a retrain28.1the committed tap and its window width
Immediately after a power-state exit28.4 §5whether settings were re-established or restored
Only on the first boot of the daytemperature, and 19.5's cold paththe 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.

SignaturePlausible mechanismsContradicted byNext discriminating experiment
Same bit, every timestuck path, broken connection, a wrong bit in a register fieldthe bit moves when lanes are remappedswap the lane mapping; the failing bit should move with the lane
One lane, scattered bitsthat lane's delay setting, capture, or physical paththe lane changes with the deviceswap the device; if the lane stays, it is board or package
First beat onlystrobe gating at burst start, turnaround, preamblechanging burst length moves nothingchange burst length — 12.1; the beat index should scale
Last beat onlypostamble, gating closing earlythe failure survives a longer burst unchangedas above
One middle beat, fixeda counter or mux wrong at that positionthe index changes with the starting columnvary the starting column — 12.2
Address bit correlatedthe address path, the map, aliasingthe bit is uncorrelated under a different mapping policychange the mapping policy — 18.1
Row-boundary clusteredactivate/precharge around a page crossingboundary-crossing and non-crossing rates are equalgenerate crossing and non-crossing streams separately
Pattern-dependentdata-dependent channel behaviouronly one specific value failsclassify: transitions or a single value — §7
Frequency-sensitivefive digital causes, then channel — §7errors per access are flatre-measure per access at both frequencies
Temperature-sensitiveretention, drift, stale calibration, channela retrain at temperature fixes itretrain at the new temperature
Refresh-window correlated28.3 §9's fourerrors are uniform in the windowbucket 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 cycles
A ten-cycle read burst trace. The clock runs throughout. The data strobe is active across cycles one to eight, covering an eight-beat burst. The beat index lane counts zero through seven across those cycles. The expected data lane shows the eight expected bytes in order. The observed data lane matches the expected data on every beat except beat five, where the expected value of hexadecimal three three is observed as hexadecimal B three. The mismatch output pulses high only on that beat. The lane bitmap lane shows all zeros except on the mismatching beat, where it shows hexadecimal eight zero, indicating that the failing bit is in the most significant lane position of that byte. The captured signal rises on the mismatching beat and stays high for the rest of the trace, because first-error capture is sticky and must not be overwritten by later errors. The first marker at cycle one notes the burst beginning. The second marker at cycle six notes the mismatch at beat five, with the expected and observed values differing by a single bit. The third marker at cycle six notes that the exclusive-or of expected and observed is hexadecimal eight zero, which is one bit in position seven, and that this XOR rather than the observed value is the diagnostic quantity. The single phase spans cycles one to eight and covers the eight-beat burst.one 8-beat burstone 8-beat burstburst starts, 8 beatsburst starts, 8 beatsbeat 5 mismatch: 33 vs B3beat 5 mismatch: 33 vs B3XOR = 80: one bit, position 7XOR = 80: one bit, position7CKdqsbeat0012345677expected0A53C7E112233445555observed0A53C7E1122B3445555mismatcherr_xor0000000000080000000capturedt0t1t2t3t4t5t6t7t8t9

The 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

A sequence diagram with five participants showing how a corruption investigation proceeds from a scoreboard mismatch to a ranked hypothesis list. The scoreboard reports a mismatch, and the first-error capture block latches the earliest evidence, which is the address, the exclusive-or of expected and observed, the beat index, the lane bitmap and the direction, and then refuses to overwrite it when later errors arrive. In parallel the structure accumulator keeps accumulating from every subsequent error rather than latching, because the distribution over a thousand errors is what carries the information that a single error does not. The accumulator reports the exclusive-or OR-bitmap and AND-bitmap over bit positions, the beat histogram, and the OR and AND over failing addresses, which together answer whether a bit is always the same bit, whether a beat position is implicated, and which address bits correlate with failure. The classifier then asks the ECC layer whether correction is in play, because with on-die correction the observed structure is not the underlying fault structure and any conclusion about the fault must be qualified. Finally the classifier returns a ranked hypothesis list with a next discriminating experiment for each, and it explicitly refuses to return a single cause, because every signature in this chapter is consistent with several mechanisms.From a mismatch to a ranked hypothesis listScoreboardFirst errorStructureECC layerClassifiermismatch: addr, xor,beat, lanefirst wins, latchedevery later errortooxor OR/AND, beathistogramaddr OR/AND:correlated bitsis correction inplay?observed != faultstructureranked list + nextexperiment

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.

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

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

The 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

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

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

DimensionReduce towardStop when
Address rangeone page, then one addressthe rate drops materially
Patternone value, then one bitthe failure stops
Burst lengththe shortest that failsshorter no longer fails
Banksone bankthe rate drops
Traffic ratethe lowest that still failsthe rate becomes unmeasurable
Durationthe 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

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

17. DV — Testing the Evidence

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
CheckWhat it establishes
Replay §10's single error; compare every fieldLatched capture equals the reference's first entry
50,000 random errors through bothIncremental and rescanning structure agree on all six accumulators
Zero errors, read xor_andBlock holds all-ones; reference returns 0 — the documented difference, asserted explicitly
One error with XOR 0x80xor_or == xor_and == 0x80; SIG_STUCK_BIT
Two errors, 0x80 and 0x40xor_or == 0xC0, xor_and == 0x00; not a stuck bit
Errors only in lane 3lane_and has one bit; SIG_LANE_LOCAL
Errors only at beat 0SIG_FIRST_BEAT; experiment names burst length
Errors only at beat BEATS−1SIG_LAST_BEAT
All failing addresses 512-alignedaddr_or low 9 bits clear; correlated mask has 9 bits
Report an error with expected == observedP4 fires
Initialise xor_and to zero instead of all onesP7 fires — the documented initialisation bug
Inject reads onlyerr_writes == 0; P9 holds
70,000 errors into one beat binBin saturates; shape preserved, magnitude lost
Set ecc_correction_active and request a bit verdictClassifier refuses — §12
hours_needed_for_zero_to_mean_something(40, 8, 5)Returns 1.0 hour
Run with the scoreboard disconnectedAll 12 properties pass; all 14 covers empty

Two reports are worth publishing, and the second is the one this chapter exists to prevent:

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

CaseBehaviourWhy
LANES = 1, BEATS = 1, BANKS = 1Elaboration fails$clog2(1) is 0; index vectors would be [-1:0]
DATA_W not a multiple of LANESElaboration failslane slicing would be ill-defined
Zero errorsxor_and reads all onesinitialisation; err_count is the field that says so
xor_and initialised to zeroP7 firesa register that cannot produce a finding
One errorxor_or == xor_anda single sample cannot distinguish stuck from scattered
Two errors, disjoint XORsxor_and == 0not a stuck bit
Error with expected == observedP4 firesthe scoreboard and block disagree
70,000 errors in one beat binBin saturatesshape preserved, magnitude lost
Correction activeBit verdict refused§12 — the composition is not invertible
Correction layer unspecifiedInstrument limitation, stated§16's callout — one bit cannot name a layer
Reads onlyerr_writes == 0, P9 holds§4 — eliminates write-side settings
Lower frequency, errors per second halveNothing proved§7 — accesses per second halved too
Narrowing to one bank stops the failureA finding, not a failed reduction§15 — the mechanism spans banks
20-minute clean run after a 40-per-8-hours baselineProves 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. §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.

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

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

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

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

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

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

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

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.