Skip to content
VLSI Mentor

DDR · Module 22

High-Speed Effects

At DDR5 rates loss dominates and absorbing is no longer enough. Two responses remain: predict the interference and subtract it, or accept corruption and detect it — one is not RTL, the other is a provable polynomial.

Three chapters have offered three responses to a degraded channel. Chapter 22.1 absorbs — present an impedance so less energy returns. 22.2 explained what is being absorbed and why it arrives late. 22.3 re-encodes — change the pattern the channel is asked to carry, and pay for it with a pin.

Both run out. Absorbing cannot help interference that arrives because the channel is lossy rather than mismatched (22.3 §2). Re-encoding bounds a bit count and not a margin (22.3 §14). And 22.3 §4 established that loss compounds with rate for two reasons at once — the signal's content moves higher where attenuation is greater, and the unit interval shrinks so a slowed transition has less time to settle.

So two responses remain, and this chapter is both. Predict the interference and subtract it. Or accept that corruption will occur and detect it afterwards.

The module's central law, for the last time:

A DDR signal arrives degraded by effects that are electrical, not logical. Termination, encoding and protection do not remove those effects — they bound them, detect them, or trade one for another.

This chapter is where “detect them” finally appears, and the qualification is the bluntest in the module: write CRC does not prevent corruption. The corrupt data reaches the DRAM core. The standard says so explicitly, and §9 is about what that means for whoever has to recover.

1. Why Absorbing Stops Being Enough

Chapter 22.3 §4 gave the mechanism; here is the consequence stated as a progression.

At modest rates a DDR channel's dominant impairment is reflection. The channel is short relative to the bit interval, loss is small over the frequencies in use, and the energy that spoils a decision is energy that turned around at a discontinuity. Termination addresses this directly, which is why on-die termination arrived with DDR2 and why 22.1 is a chapter about absorbing.

As the rate rises, two things happen together:

Effect
The signal's content moves to higher frequencyLoss is frequency-dependent, so attenuation rises
The unit interval shrinksA slowed transition has less time to settle inside it

Neither of those is helped by termination. Termination reduces what comes back; it does nothing about what was taken away on the journey out, and nothing about a transition that is still settling when its interval ends.

And 22.2 §3 adds a third, which is the one people find surprising: a reflection's round trip is a fixed physical time, but a unit interval is not. Double the rate and the same discontinuity's energy now lands twice as many unit intervals later. It has not got worse; it has moved further out in k — which §4 shows is what decides whether anything can be done about it.

So the progression is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   lower rate        reflections dominate        TERMINATE
                                                 (22.1)

   higher rate       loss begins to dominate,    RE-ENCODE
                     and reflections move        (22.3 -- bounds
                     further out in k             a count, not
                                                  a margin)

   higher still      neither is sufficient       PREDICT AND
                                                 SUBTRACT (§3)
                                                 or DETECT (§6)

That is the chapter, and it is also the honest reason DDR5 added a receiver feature that DDR4 did not have. One technical account of DDR5 signal integrity puts it directly: before DDR5 no equalisation was needed to obtain an adequate eye opening, and with the push to higher speed and lower power, equalisation became a necessity.

2. Three Families, and What Each Can Reach

It is worth laying the four mitigations this module covers side by side, because their reach is genuinely different and choosing wrongly wastes effort.

FamilyMechanismReachesDoes not reach
Absorb (22.1)Present an impedance at a junctionReflections from that junctionLoss; other junctions; crosstalk
Re-encode (22.3)Change the pattern presentedLevel-count-limited channelsAnything not limited by level count
Predict and subtract (§3)Model the interference and remove itPost-cursor ISI within the model's memoryCrosstalk; pre-cursor; arrivals beyond reach
Detect (§6)Check a code over the frameCorruption after the factNothing is prevented

Two rows deserve emphasis.

Predict-and-subtract reaches only post-cursor interference. It works by using bits it has already decided to correct bits it has not yet decided. So it can address energy from the past arriving in the present — which is exactly 22.2 §3's mechanism — and it can do nothing about crosstalk, which arrives simultaneously with its aggressor rather than delayed (22.3 §3).

Detect prevents nothing, and that is not a weakness of the implementation. It is the definition. §9 shows the standard is explicit that corrupt data reaches the DRAM core before the check completes.

The four mitigation families this module covers and where each one acts, in four rows. The top row names the families: absorb, which is termination from chapter twenty-two point one; re-encode, which is data bus inversion from twenty-two point three; predict and subtract, which is equalisation; and detect, which is write cyclic redundancy checking. The second row holds the channel itself, the receiver, the equaliser taps, and the seventy-two-bit write frame. The third row holds the checksum generator in the controller, the checksum comparison inside the device, and the alert signal by which a mismatch is reported. The bottom row is the controller retry. Edges show that absorbing and re-encoding both act on the channel, that the channel feeds the receiver, that the equaliser taps also feed the receiver because they act at the decision rather than on the channel, that the frame and the detect family both feed the generator, that the generator's output travels the channel, that the channel feeds the device's comparison, and that the comparison reports through alert to the retry which resubmits the frame. The structure shows the four families acting at three different places: two on the channel, one at the receiver's decision, and one on the frame after the fact.Absorbterminate — 22.1Re-encodeDBI — 22.3Predictequalise — §3Detectwrite CRC — §6The channel22.2's junctionsReceiverone decisionDFE taps4 taps, 4 UI — §472-bit frame10 UI — §7CRC generatein the controllerCRC comparein the deviceALERT_na pulse — §10Controller retrycoherency is ours — §912

The diagram makes §2's table structural. The four families act at three different places. Absorb and re-encode both attach to the channel — they change what travels. Predict attaches to the receiver, not the channel, because a DFE acts at the decision rather than on the wire. And detect attaches to the frame, downstream of everything, which is why it is the only family whose action happens after the corruption has already occurred.

The edge from retry back to the frame is §9's recovery loop, and it is the only feedback path in the diagram.

3. Decision-Feedback Equalisation

The predict-and-subtract family's representative in DDR is the decision-feedback equaliser, and DDR5 specifies one.

The mechanism, stated carefully: the receiver makes a decision on each received bit, and feeds a modified version of that decision back into the receiver so that subsequent bits of the opposite polarity are emphasised. In other words, having decided that bit N was a one, the receiver knows roughly what contribution bit N will make to bit N+1, N+2 and so on — and subtracts it before deciding those.

DDR5 specifies four taps, each tap corresponding to one unit interval, correcting up to four unit intervals after the current received bit.

That sentence is worth unpacking into its three separate claims, because each one is a design fact.

Four taps. The equaliser remembers four previous decisions. More taps means more memory and more area and power; four is where DDR5's trade landed.

Each tap is one unit interval. The taps are not arbitrary filter coefficients at arbitrary delays — they are aligned to the bit rate, one per bit period. This is what makes it a symbol-spaced equaliser and what makes 22.2 §3's k the right unit for reasoning about it.

Up to four unit intervals of reach. Combined with k, this gives a hard boundary, and §4 is that boundary.

4. What Is Reachable, and What Is Not

Combine §3's four-unit-interval reach with 22.2 §3's round-trip argument and the result is a layout criterion derived from a receiver specification:

Round tripCorruptsWithin a four-tap reach
k = 1 UIbit N+1yes
k = 2 UIbit N+2yes
k = 3 UIbit N+3yes
k = 4 UIbit N+4yes
k = 5 UIbit N+5no
k = 6 UIbit N+6no

A discontinuity's distance decides whether its reflection is correctable. Close ones — package, vias, the device's own pins — return within a few unit intervals and are addressable. Far ones — a connector, the other end of a long module trace — return outside the equaliser's memory and are not.

Three consequences, and the third is the one that closes the module's argument.

Equalisation does not replace termination; it complements it. Termination reduces the magnitude of every reflection regardless of distance. Equalisation removes the residue of those close enough to model. A design needs both, addressing different parts of the same problem.

The tap values are not constants. A DFE's coefficients depend on the channel, so they have to be found — which makes tap selection a search over configurable state, using observable pass/fail information, exactly as Module 21 described for four other resources. 21.4 §6's cost argument applies unchanged: adding an axis multiplies the probe count rather than adding to it.

And 22.2 §3's rate observation now has teeth. Raising the data rate moves a fixed-time round trip further out in k. A discontinuity at k = 4 at one rate sits at k = 8 at double the rate — inside the equaliser's reach at the lower rate and outside it at the higher. That is a mechanism by which a channel can be comfortable at one speed grade and not merely marginal but uncorrectable at the next, with nothing changed but the clock.

5. Why There Is No Equaliser in This Chapter's RTL

The refusal, stated at the point it applies, as this module has done throughout.

A DFE is a mixed-signal circuit. Its taps scale an analog contribution and subtract it from an analog input before a decision is made. What it manipulates is amplitude; what it produces is a decision that would otherwise have gone the other way.

Would needWhy it is unavailable
Tap coefficients as analog weightsContinuous quantities in volts
The received waveformContinuous amplitude, which no digital signal carries
The summing nodeAnalog addition before the decision
The slicer's threshold behaviourThe very thing 20.3 §1 declined to model

An RTL block with four coefficient parameters and a subtraction would be a filter over numbers that do not exist, and it would teach that a DDR receiver has access to amplitude. It does not — 20.3 §3 established that a captured sample carries no quality indication, and that is precisely what a DFE would need as input.

What is digital about equalisation is the search for its coefficients, and Module 21 already owns the shape of that: apply a candidate, stimulate, observe a pass or fail, classify, update, commit or fail. A DFE tap sweep is 21.4's two-axis problem with more axes, and the honest observation is that it inherits 21.4 §7's difficulty — no conversion exists between a tap code and a delay code, so no combined figure of merit is meaningful.

So this chapter builds detection instead, and detection is genuinely, completely digital.

6. Write CRC

DDR4 adds a cyclic redundancy check over write data. The specification is precise enough to implement and to verify, and every fact in this section is from JESD79-4.

It is write-only. The standard states plainly that DDR4 supports CRC for write operation and does not support CRC for read operation.

The polynomial is the ATM-8 HEC, x⁸ + x² + x + 1.

The frame is 72 bits, the first serial data bit is D[71], and the initial condition is all zeros.

The same polynomial and combinatorial logic are used on GDDR5, which the standard notes explicitly — so this is a borrowed, already-proven construction rather than a DDR-specific invention.

And the standard gives an implementation cost that is unusually concrete: a combinatorial implementation of this 8-bit CRC over 72 bits of data contains 272 two-input XOR gates arranged in eight trees six XOR gates deep.

7. The Frame

The frame's structure is where DBI, CRC and device width all meet, and the details are specified per width.

The frame length is fixed at 10 unit intervals when CRC is enabled, for both BL8 and BC4 — burst length on the fly is supported with CRC enabled. That is why the standard refers to “transfer 9”: transfers 0 through 9, ten in all, for an eight-beat burst.

The controller must send ones in transfer 9 when CRC is enabled. For an x8, it must additionally send ones in transfers 8 and 9 of the DBI_n lane when DBI is enabled; for an x16, on both the DBIL_n and DBIU_n lanes.

The tree's input population differs by width:

WidthCRC tree inputsUpper bits
x872Upper 8 are the DBI inputs if DBI is enabled; all ones if disabled — D[71:64]
x16Two identical 72-bit treesUpper 8 of each are DBI inputs, or ones — D[143:136] and D[71:64]
x432D[71:32] are all ones

Three observations.

DBI and CRC share the frame. Chapter 22.3 §8 established that DBI costs no extra pins; §7's table shows where it does cost something — 8 of the 72 tree inputs, about 11.1%, recomputed. When DBI is off those bits are ones, so the tree is the same size either way and the CRC covers the DBI flags when they are present.

That is why the DBI failure of 22.3 §15 becomes detectable here. A corrupted DBI_n inverts a whole byte group with no local indication — and because the DBI bits are inside the CRC frame, the check covers them. The detection that chapter could not provide is provided here, and it is provided because of a frame-layout decision rather than by an extra mechanism.

And an x4's tree is padded, not shrunk. The tree is 32 inputs with the upper 40 driven to ones, rather than a smaller polynomial. The same 8-bit CRC and the same logic serve every width, which is consistent with the GDDR5 reuse noted in §6: one construction, populated differently.

Data Bus Inversion is not allowed during an MPR read, which matters for Module 21's training: the pattern on the wire during MPR-based training is the pattern in the register, unmodified.

8. The Detection Guarantee, Verified Independently

The standard publishes a detection table, and this is one place where the claims can be checked rather than taken, because the polynomial is given and the arithmetic is finite.

The published claims, for the 72-bit frame:

Error typeDetection
Random single-bit error100%
Random double-bit error100%
Random odd-count error100%
Random one multi-bit UI vertical column error, excluding DBI bits100%

Each of the first three was independently recomputed from the polynomial rather than accepted:

Single-bit: verified exhaustively. For a 72-bit frame, flipping each of the 72 bit positions in turn changes the checksum in every case.

Double-bit: verified exhaustively. All 2,556 distinct pairs of bit positions change the checksum.

Odd-count: verified structurally and then empirically. A polynomial divisible by (x + 1) detects every odd-weight error. Evaluating x⁸ + x² + x + 1 at x = 1 over GF(2) gives 1 + 1 + 1 + 1 = 0, so (x + 1) is a factor — which proves the class, not merely samples it. 3,000 random odd-weight error patterns were then checked and all were detected, as a consistency check on the implementation rather than as the proof.

9. CRC Does Not Prevent Corruption

This is the section that makes write CRC honest, and the standard's own words are blunter than most descriptions of the feature.

The DRAM may write data to its core without waiting for the CRC check on full writes. If bad data is written to the core, the controller will retry the transaction and overwrite the bad data, and the controller is responsible for data coherency.

Read that as a sequence:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   controller sends a write frame  ────────►  DRAM receives it

                                    ┌───────────────┴───────────────┐
                                    ▼                               ▼
                          writes it to the core          computes the CRC
                          WITHOUT WAITING                and compares
                                    │                               │
                                    │                    mismatch ──┤
                                    ▼                               ▼
                          corrupt data is now         asserts ALERT_n
                          IN MEMORY                   as a pulse

                                    ┌───────────────────────────────┘

                          controller retries the write and
                          overwrites -- and is responsible
                          for having kept the data to retry
                          WITH, and for coherency meanwhile

Four consequences, and they are the real engineering content of the feature.

There is a window in which memory holds data the system knows is wrong. Between the core write and the retry, the addressed location contains corruption. Anything that reads it in that window gets the corrupt value, and CRC provides no interlock preventing that.

The controller must be able to retry, which means it must still have the data. A write that has been acknowledged upstream and whose data has been released cannot be retried. So write CRC imposes a buffering requirement on the controller: write data must be retained until the ALERT_n window for it has passed. §13's block owns that bookkeeping.

Coherency is explicitly the controller's problem. The standard says so. The device offers detection and nothing else — no ordering guarantee, no interlock, no automatic retry.

And the standard notes the controller must also handle page state. It is responsible for opening any pages needed and ensuring retries are done coherently — so a retry is not a replayed transaction but potentially a re-activated row and a rescheduled command, which puts it squarely in 17.1's territory.

10. The Error Report

The report path was analysed in 6.12 §3, which established that ALERT# is the device's only output, carries both the command/address parity class and the CRC class, has no selector, and forces the controller to disambiguate from context. This section supplies the verified specifics that chapter did not have.

The standard confirms the ambiguity in its own words: the controller has no way to distinguish between CRC errors and command/address parity errors other than to read the DRAM mode registers, and doing so is very time consuming in a multi-rank configuration.

So the specification's answer is to make the two classes distinguishable by shape rather than by content:

QuantityValue
CRC error to ALERT_n latency — tCRC_ALERTmaximum 13 ns, DDR4-1600 through 2400
ALERT_n pulse width — CRC_ALERT_PW6 to 10 nCK, all four bins
Pulse-width measurementFrom the device driving low to the device releasing and the controller pulling up
Applies tox4, x8 and x16 alike

CRC errors are sent as a pulse; a parity error is held long. That is the disambiguation, and it is why 6.12 §3 said the pulse duration carries information and the specification defines it rather than leaving it vague.

Two further verified facts shape the recovery:

The error status is sticky. The device sets a CRC Error Clear bit in MR5 and a CRC Error Status bit in MPR3 of page 1, and the clear bit remains set until the host clears it explicitly with an MRS command. So the event survives the pulse — a controller that misses the pulse can still discover the error by reading, and a controller that never clears it will see a stale error forever.

ALERT_n is a daisy chain. The standard notes the pulse may be seen longer than specified at the controller if there are multiple CRC errors, because the signal is a daisy-chained bus. So pulse width is a reliable discriminator for a single error and a less reliable one under a burst of them — which is exactly when a system is most likely to be in trouble.

11. The CRC Generator

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// write_crc_gen -- DDR4 write CRC generation and checking.
//
// CLASSIFICATION: educational, synthesisable.
//
// ALGORITHM: ATM-8 HEC, x^8 + x^2 + x + 1, over a 72-bit frame, first
// serial bit D[71], initial condition all zeros. Per JESD79-4, which
// also notes the same construction is used on GDDR5.
//
// AUTHORED FROM THE POLYNOMIAL. The standard prints explicit XOR
// equations for this function; they are not reproduced here. The
// bit-serial step below is the polynomial's definition, and the
// unrolled loop is the tool's business rather than a transcription.
//
// WHAT IT DOES NOT MODEL:
//   - anything electrical. It is arithmetic over bits.
//   - whether a PASS means the channel is healthy. It means this
//     frame's 72 bits arrived consistent with their checksum.
//   - the DRAM's core write, which per §9 does NOT wait for this.
// ---------------------------------------------------------------------
module write_crc_gen #(
  // Frame width. 72 for x8 and for each of an x16's two trees; an x4
  // populates 32 and pads the upper 40 with ones (§7).
  parameter int FRAME_W = 72,
  // Populated data bits, counted from bit 0. The rest are padded to 1.
  parameter int DATA_W  = 64,
  // The polynomial's low byte: x^8 + x^2 + x + 1 -> 0000_0111.
  parameter logic [7:0] POLY = 8'h07
) (
  input  logic                clk,
  input  logic                rst_n,

  // ── Generate side (the controller).
  input  logic                gen_valid,
  input  logic [DATA_W-1:0]   gen_data,
  // The DBI flags, which live in the frame's upper bits when DBI is
  // enabled and are all ones when it is not (§7). Passing them in
  // explicitly is what makes the CRC cover them -- and therefore what
  // makes 22.3's silent DBI-flag corruption detectable.
  input  logic                gen_dbi_en,
  input  logic [7:0]          gen_dbi_bits,
  output logic [7:0]          gen_crc,

  // ── STREAMING side. A real controller does not have all 72 bits at
  //    once: the frame arrives over transfers (§7's 10 UI). This path
  //    accumulates across beats and is the shape a datapath actually
  //    instantiates; the combinational path above is the reference.
  input  logic                str_start,      // first beat of a frame
  input  logic                str_valid,
  input  logic [7:0]          str_byte,       // one byte per beat, MSB first
  output logic [7:0]          str_crc,        // running value
  output logic [3:0]          str_beats,      // beats absorbed so far

  // ── Check side (the device).
  input  logic                chk_valid,
  input  logic [DATA_W-1:0]   chk_data,
  input  logic                chk_dbi_en,
  input  logic [7:0]          chk_dbi_bits,
  input  logic [7:0]          chk_crc_rx,
  output logic                chk_mismatch,

  // ── Observability.
  output logic [7:0]          chk_crc_local,
  output logic [15:0]         cnt_mismatches,

  output logic                err_frame_misconfigured
);

  // ── Elaboration guards.
  initial begin
    if (FRAME_W < 9)
      $fatal(1, "write_crc_gen: FRAME_W (%0d) too small for an 8-bit CRC", FRAME_W);
    if (DATA_W > FRAME_W - 8)
      $fatal(1, "write_crc_gen: DATA_W (%0d) leaves no room for the upper 8 bits",
             DATA_W);
    if (POLY[0] != 1'b1)
      // A CRC polynomial must have a non-zero constant term, or the
      // register can never be disturbed by the lowest input bit.
      $fatal(1, "write_crc_gen: POLY must have its constant term set");
  end

  // ── One bit of the shift-and-conditionally-XOR recurrence. This IS
  //    the polynomial: shift the register up, and if the bit leaving
  //    the top differs from the incoming data bit, fold the polynomial
  //    back in.
  function automatic logic [7:0] crc_step(input logic [7:0] cur,
                                          input logic       d);
    logic feedback;
    feedback = cur[7] ^ d;
    crc_step = {cur[6:0], 1'b0} ^ (feedback ? POLY : 8'h00);
  endfunction

  // ── Assemble the frame. Bits [DATA_W-1:0] are data; the top 8 are
  //    the DBI flags or ones; anything between is padding at one,
  //    which is the x4 case of §7.
  function automatic logic [FRAME_W-1:0] build_frame(
      input logic [DATA_W-1:0] d,
      input logic              dbi_en,
      input logic [7:0]        dbi);
    build_frame = {FRAME_W{1'b1}};          // pad everything to ones
    build_frame[DATA_W-1:0]        = d;
    build_frame[FRAME_W-1 -: 8]    = dbi_en ? dbi : 8'hFF;
  endfunction

  // ── The whole frame, MSB first. JESD79-4's convention is that the
  //    first serial data bit is the frame's top bit, so the loop runs
  //    downward -- getting this direction wrong produces a checksum
  //    that is self-consistent and disagrees with every real device.
  function automatic logic [7:0] crc_frame(input logic [FRAME_W-1:0] f);
    logic [7:0] acc;
    acc = 8'h00;                            // initial condition: zeros
    for (int i = FRAME_W - 1; i >= 0; i--)
      acc = crc_step(acc, f[i]);
    crc_frame = acc;
  endfunction

  logic [FRAME_W-1:0] gen_frame, chk_frame;
  always_comb begin
    gen_frame = build_frame(gen_data, gen_dbi_en, gen_dbi_bits);
    chk_frame = build_frame(chk_data, chk_dbi_en, chk_dbi_bits);
  end

  assign gen_crc       = crc_frame(gen_frame);
  assign chk_crc_local = crc_frame(chk_frame);

  // ── The device's comparison. §9: this result arrives AFTER the core
  //    write may already have happened.
  assign chk_mismatch = chk_valid && (chk_crc_local != chk_crc_rx);

  // ── The streaming accumulator. Eight crc_step applications per beat,
  //    MSB first within the byte, so the bit order matches the
  //    combinational path exactly. Reusing crc_step rather than writing
  //    a second recurrence is what makes the two paths provably the
  //    same function -- §14's P10 checks them against each other.
  logic [7:0] str_acc;
  logic [3:0] str_cnt;

  function automatic logic [7:0] crc_byte(input logic [7:0] cur,
                                          input logic [7:0] b);
    logic [7:0] acc;
    acc = cur;
    for (int i = 7; i >= 0; i--)
      acc = crc_step(acc, b[i]);
    crc_byte = acc;
  endfunction

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      str_acc <= 8'h00;
      str_cnt <= '0;
    end else if (str_start) begin
      // A new frame restarts from the all-zero initial condition, and
      // absorbs this beat in the same cycle -- otherwise the first
      // beat of every frame would be dropped.
      str_acc <= crc_byte(8'h00, str_byte);
      str_cnt <= 4'd1;
    end else if (str_valid) begin
      str_acc <= crc_byte(str_acc, str_byte);
      if (str_cnt != 4'hF) str_cnt <= str_cnt + 4'd1;
    end
  end

  assign str_crc   = str_acc;
  assign str_beats = str_cnt;

  logic [15:0] mm;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)                              mm <= '0;
    else if (chk_mismatch && (mm != 16'hFFFF)) mm <= mm + 16'd1;
  end
  assign cnt_mismatches = mm;

  // A generator and checker configured with different DBI enables
  // would compute over different frames and disagree on every write --
  // a configuration error that looks exactly like a channel fault.
  assign err_frame_misconfigured = gen_valid && chk_valid
                                && (gen_dbi_en != chk_dbi_en);

endmodule

The streaming path exists because the combinational one is not what a datapath instantiates. §7's frame occupies ten unit intervals, so the bits arrive over transfers, and a controller accumulates as they go. Both paths call the same crc_step, which is deliberate: it means §14's P10 checks the accumulation rather than re-proving the recurrence, and the accumulation is the only part a streaming version can get wrong.

Three further things in that block are worth pointing out, and the last one is the bug most likely to reach silicon.

crc_step is the polynomial, not a table. Shift up, and fold the polynomial back when the departing top bit differs from the incoming data bit. That is the definition of polynomial division over GF(2), and writing it this way means the parameter POLY genuinely selects the polynomial rather than indexing a precomputed constant.

build_frame puts padding at one, not zero. §7 is explicit: an x4's upper 40 bits are ones, and an x8's upper 8 are ones when DBI is disabled. Padding with zeros would produce a checksum that is internally consistent and wrong against every device.

And the loop runs downward. JEDEC's convention is that the first serial bit is D[71] — the frame's top bit. A loop written upward computes a valid CRC over the bit-reversed frame, agrees with itself perfectly in any self-contained testbench, and disagrees with every real device. §16's DV table checks the direction explicitly for exactly that reason.

12. A Worked Frame

Every number here was computed from the implementation above and cross-checked against an independent reference.

Eight beats on an x8 with DBI disabled, so the frame's upper 8 bits are ones:

Beat01234567
Byte0x000xFF0x550xAA0x0F0xF00x330xCC

With beat 0 in the frame's low byte and D[71:64] = 0xFF:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   D[71:0]  = 0xFF_CC33F00FAA55FF00
   CRC      = 0xF3   (binary 1111_0011)

   flip a single bit -- D[0] -- and nothing else:
   CRC      = 0xF4

   the checksums differ, which is §8's single-bit guarantee
   on one concrete instance rather than in the abstract

The checksum is 0xF3. Flipping D[0] alone changes it to 0xF4. That single pair is not a proof of anything — §8's exhaustive checks are the proof — but it is the arithmetic made concrete, and it is the value §16's testbench replays.

13. The Error Recovery Path

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crc_error_recovery -- the controller-side obligations write CRC
// imposes: retention, pulse classification, retry, and not releasing
// data that may still be needed.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT DOES NOT MODEL:
//   - the channel, the corruption, or ALERT_n's electrical behaviour
//   - the daisy chain of §10, which can stretch an observed pulse
//     beyond its specified width when several devices report at once
//   - the DRAM's core write, which per §9 does not wait
//
// WHY IT EXISTS: §9 -- "the controller is responsible for data
// coherency". A device that detects and reports imposes a buffering
// and scheduling requirement on whatever must recover.
// ---------------------------------------------------------------------
module crc_error_recovery #(
  // Entries of write data retained awaiting their report window.
  parameter int DEPTH = 8,
  // Cycles from a write leaving until its report window closes. Must
  // cover tCRC_ALERT plus the pulse. POLICY: derived from the speed
  // bin's published figures (§10), not knowable here.
  parameter int WINDOW = 24,
  // Pulse width bounds that classify a report as the CRC class rather
  // than the parity class (§10). CRC_ALERT_PW is 6..10 nCK.
  parameter int PULSE_MIN = 6,
  parameter int PULSE_MAX = 10,
  parameter int IDX_W = (DEPTH  <= 1) ? 1 : $clog2(DEPTH),
  // A COUNT of occupied entries up to DEPTH needs clog2(DEPTH+1).
  parameter int OCC_W = $clog2(DEPTH + 1),
  parameter int WIN_W = (WINDOW <= 1) ? 1 : $clog2(WINDOW + 1),
  parameter int PLS_W = $clog2(PULSE_MAX + 2)
) (
  input  logic               clk,
  input  logic               rst_n,

  // ── A write leaving the controller. Its data must be retained.
  input  logic               wr_issued,
  input  logic [IDX_W-1:0]   wr_tag,

  // ── ALERT_n as observed, already synchronised. Active low.
  input  logic               alert_n,

  // ── Retry request out.
  output logic               retry_req,
  output logic [IDX_W-1:0]   retry_tag,

  // ── Release: this entry's window has closed with no report, so its
  //    data may be freed. NOTHING may be freed before this.
  output logic               release_valid,
  output logic [IDX_W-1:0]   release_tag,

  // ── Observability.
  output logic [OCC_W-1:0]   occupancy,
  output logic               classified_crc,
  output logic               classified_other,
  output logic [PLS_W-1:0]   pulse_len,
  output logic [15:0]        cnt_retries,

  output logic               err_overflow,
  output logic               err_report_without_outstanding
);

  initial begin
    if (DEPTH < 1)   $fatal(1, "crc_error_recovery: DEPTH must be >= 1");
    if (WINDOW < 1)  $fatal(1, "crc_error_recovery: WINDOW must be >= 1");
    if (PULSE_MIN > PULSE_MAX)
      $fatal(1, "crc_error_recovery: PULSE_MIN exceeds PULSE_MAX");
  end

  // ── Retention. One countdown per entry; an entry is outstanding
  //    until its window expires or it is retried.
  logic             busy  [DEPTH];
  logic [WIN_W-1:0] left  [DEPTH];
  logic [IDX_W-1:0] tag_q [DEPTH];

  // Declared with the state because the assigns below read them.
  logic             rty_q, rel_q;
  logic [IDX_W-1:0] rty_tag_q, rel_tag_q;
  logic [PLS_W-1:0] pls;
  logic             cls_crc, cls_other;
  logic [15:0]      rty_cnt;

  logic [OCC_W-1:0] occ;
  always_comb begin
    occ = '0;
    for (int i = 0; i < DEPTH; i++) if (busy[i]) occ = occ + OCC_W'(1);
  end

  logic any_outstanding;
  assign any_outstanding = (occ != '0);

  // ── Pulse measurement. A CRC report is a pulse of bounded width; a
  //    parity report is held long (§10). So the classification is made
  //    on the RELEASING edge, when the width is finally known --
  //    not on assertion, when it is not.
  logic alert_q;
  logic pulse_ended;
  assign pulse_ended = (alert_q == 1'b0) && (alert_n == 1'b1);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int i = 0; i < DEPTH; i++) begin
        busy[i]  <= 1'b0;
        left[i]  <= '0;
        tag_q[i] <= '0;
      end
      alert_q   <= 1'b1;
      pls       <= '0;
      cls_crc   <= 1'b0;
      cls_other <= 1'b0;
      rty_q     <= 1'b0;
      rel_q     <= 1'b0;
      rty_tag_q <= '0;
      rel_tag_q <= '0;
      rty_cnt   <= '0;
    end else begin
      alert_q   <= alert_n;
      rty_q     <= 1'b0;
      rel_q     <= 1'b0;
      cls_crc   <= 1'b0;
      cls_other <= 1'b0;

      // ── Measure the pulse while it is low, saturating one above the
      //    max so an over-long assertion is distinguishable from one
      //    exactly at the bound.
      if (alert_n == 1'b0) begin
        if (pls <= PLS_W'(PULSE_MAX)) pls <= pls + PLS_W'(1);
      end

      // ── Accept a new write into retention.
      if (wr_issued) begin
        if (occ == OCC_W'(DEPTH)) begin
          // Overflow. Reported, and the write is NOT retained -- which
          // means it cannot be retried. That is a real loss of the
          // guarantee, so it must never be silent.
        end else begin
          // Explicit first-free search, written as a priority loop so
          // exactly one slot is claimed per cycle. `claimed` is a
          // loop-control temporary, so it is assigned BLOCKING -- it
          // must take effect within this iteration to gate the next.
          // The block is NAMED so the temporary has a hierarchical
          // name and is visible in a waveform.
          begin : claim_slot
            logic claimed;
            claimed = 1'b0;
            for (int i = 0; i < DEPTH; i++)
              if (!claimed && !busy[i]) begin
                busy[i]  <= 1'b1;
                left[i]  <= WIN_W'(WINDOW);
                tag_q[i] <= wr_tag;
                claimed  = 1'b1;
              end
          end
        end
      end

      // ── Age every outstanding entry; release those whose window
      //    closes with no report against them.
      for (int i = 0; i < DEPTH; i++) begin
        if (busy[i]) begin
          if (left[i] > WIN_W'(1)) begin
            left[i] <= left[i] - WIN_W'(1);
          end else begin
            busy[i]   <= 1'b0;
            rel_q     <= 1'b1;
            rel_tag_q <= tag_q[i];
          end
        end
      end

      // ── Classify on the releasing edge and act.
      if (pulse_ended) begin
        if ((pls >= PLS_W'(PULSE_MIN)) && (pls <= PLS_W'(PULSE_MAX))) begin
          cls_crc <= 1'b1;
          // Retry the OLDEST outstanding entry. §10: the pulse does
          // not say which transaction, so a controller either backs up
          // conservatively or correlates. This takes the conservative
          // route and says so.
          begin : pick_oldest
            logic found;
            found = 1'b0;
            for (int i = 0; i < DEPTH; i++)
              if (!found && busy[i]) begin
                rty_q     <= 1'b1;
                rty_tag_q <= tag_q[i];
                busy[i]   <= 1'b0;
                found     = 1'b1;
              end
          end
          if (rty_cnt != 16'hFFFF) rty_cnt <= rty_cnt + 16'd1;
        end else begin
          // Out of the CRC pulse band: the other error class, whose
          // handling belongs to 6.12 rather than here.
          cls_other <= 1'b1;
        end
        pls <= '0;
      end
    end
  end

  assign retry_req     = rty_q;
  assign retry_tag     = rty_tag_q;
  assign release_valid = rel_q;
  assign release_tag   = rel_tag_q;
  assign occupancy     = occ;
  assign classified_crc   = cls_crc;
  assign classified_other = cls_other;
  assign pulse_len     = pls;
  assign cnt_retries   = rty_cnt;

  assign err_overflow = wr_issued && (occ == OCC_W'(DEPTH));
  // A report with nothing outstanding cannot be a CRC error on our
  // traffic. It is either the other class or a report for someone
  // else's transaction on the daisy chain (§10).
  assign err_report_without_outstanding = pulse_ended && !any_outstanding;

endmodule

Two design decisions in that block carry the chapter's argument.

Classification happens on the releasing edge, not on assertion. The discriminator between the two error classes is pulse width (§10), and a width is not known until the pulse ends. A block that classified on assertion would have to guess, and guessing wrong means treating a parity error — which invalidates the controller's model of device state — as a retryable write.

Retry targets the oldest outstanding entry, conservatively, and says so at the line. §10 established that the pulse does not identify a transaction, and the standard offers two options: back up conservatively knowing the worst-case delay, or build something more intelligent that correlates the error to a rank or a transaction. This takes the first, which is correct and not optimal — and naming that in the comment is the difference between a simplification and a hidden assumption.

And err_overflow is the property that matters most. If retention is full, a new write cannot be retained, which means it cannot be retried — so the CRC guarantee is silently lost for that write. §14's P4 forbids releasing an entry early for the same reason: the entire mechanism rests on data still being available when the report arrives.

14. What the Assertions Prove

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind unit note: P1-P3 constrain write_crc_gen, which is purely
// combinational; they are written for a bind unit that supplies a
// sampling clock and they constrain a FUNCTION. P4-P9 constrain
// crc_error_recovery and see its clk and rst_n directly.

// P1 -- generator and checker agree when the frame is intact. If this
// fails, the two paths are not computing the same function.
property p_intact_frame_matches;
  @(posedge clk)
    ((chk_data == gen_data) && (chk_dbi_en == gen_dbi_en)
      && (chk_dbi_bits == gen_dbi_bits) && (chk_crc_rx == gen_crc)
      && chk_valid)
      |-> !chk_mismatch;
endproperty
assert property (p_intact_frame_matches);

// P2 -- a mismatch is reported only when the checksums differ. Guards
// against a checker that reports on any valid frame.
property p_mismatch_iff_differs;
  @(posedge clk)
    chk_mismatch |-> (chk_crc_local != chk_crc_rx);
endproperty
assert property (p_mismatch_iff_differs);

// P3 -- the DBI enable must agree across the two sides, because it
// changes the frame (§7). A disagreement produces a mismatch on every
// write and looks exactly like a channel fault.
property p_dbi_config_agrees;
  @(posedge clk)
    (gen_valid && chk_valid) |-> (err_frame_misconfigured == (gen_dbi_en != chk_dbi_en));
endproperty
assert property (p_dbi_config_agrees);

// P10 -- the streaming path and the combinational path compute the
// same function. Both call crc_step, so this proves the ACCUMULATION
// is right rather than proving the recurrence twice -- which is the
// only part the streaming version could get wrong.
property p_streaming_matches_parallel;
  @(posedge clk) disable iff (!rst_n)
    (str_beats == 4'd9) |-> (str_crc == gen_crc);
endproperty
assert property (p_streaming_matches_parallel);

// P4 -- THE safety property of §13. An entry is released only when its
// window has actually expired, never early. Releasing early discards
// data that may still need retrying, which silently voids the whole
// mechanism.
property p_no_early_release;
  @(posedge clk) disable iff (!rst_n)
    release_valid |-> (occupancy <= $past(occupancy, 1));
endproperty
assert property (p_no_early_release);

// P5 -- a retry is only ever issued for a CRC-class classification.
property p_retry_needs_crc_class;
  @(posedge clk) disable iff (!rst_n)
    retry_req |-> classified_crc;
endproperty
assert property (p_retry_needs_crc_class);

// P6 -- the two classifications are mutually exclusive. A pulse is one
// class or the other, never both.
property p_class_exclusive;
  @(posedge clk) disable iff (!rst_n)
    !(classified_crc && classified_other);
endproperty
assert property (p_class_exclusive);

// P7 -- classification only ever happens when a pulse has ended.
// Classifying on assertion would mean guessing the width (§13).
property p_classify_on_pulse_end;
  @(posedge clk) disable iff (!rst_n)
    (classified_crc || classified_other) |-> $past(pulse_ended, 1);
endproperty
assert property (p_classify_on_pulse_end);

// P8 -- a pulse inside the specified band classifies as CRC; one
// outside it does not. This is §10's shape-based disambiguation.
property p_band_decides_class;
  @(posedge clk) disable iff (!rst_n)
    (classified_crc && $past(pulse_ended, 1))
      |-> (($past(pulse_len, 1) >= PLS_W'(PULSE_MIN))
        && ($past(pulse_len, 1) <= PLS_W'(PULSE_MAX)));
endproperty
assert property (p_band_decides_class);

// P9 -- occupancy never exceeds the depth. If it could, an entry would
// be overwritten and its data lost.
property p_occupancy_bounded;
  @(posedge clk) disable iff (!rst_n)
    (occupancy <= OCC_W'(DEPTH));
endproperty
assert property (p_occupancy_bounded);

// ── Cover.
cover property (@(posedge clk) chk_mismatch);
// A complete frame absorbed by the streaming path.
cover property (@(posedge clk) disable iff (!rst_n) (str_beats == 4'd9));
// A frame restarted mid-stream: str_start arriving before the previous
// frame completed, which must reset the accumulator rather than fold in.
cover property (@(posedge clk) disable iff (!rst_n)
                  str_start && (str_beats != '0) && (str_beats < 4'd9));
cover property (@(posedge clk) gen_dbi_en && (gen_crc != 8'h00));
cover property (@(posedge clk) disable iff (!rst_n) classified_crc);
cover property (@(posedge clk) disable iff (!rst_n) classified_other);
cover property (@(posedge clk) disable iff (!rst_n) err_overflow);
// A pulse exactly at each band edge -- the boundaries the
// classification turns on, which an off-by-one would misclassify.
cover property (@(posedge clk) disable iff (!rst_n)
                  pulse_ended && ($past(pulse_len, 0) == PLS_W'(PULSE_MIN)));
cover property (@(posedge clk) disable iff (!rst_n)
                  pulse_ended && ($past(pulse_len, 0) == PLS_W'(PULSE_MAX)));
// A report with nothing outstanding -- §10's daisy chain, or the
// other class arriving unprompted.
cover property (@(posedge clk) disable iff (!rst_n)
                  err_report_without_outstanding);
// Retention full while a write is issued: the guarantee is lost for
// that write, and a regression must have seen it happen.
cover property (@(posedge clk) disable iff (!rst_n)
                  err_overflow ##[1:8] retry_req);

P4 is the property to keep if only one survives. Write CRC's entire value depends on the controller still holding the data when the report arrives (§9), and an early release is the one bug that removes the guarantee while leaving every other observable healthy.

The two band-edge covers exist because §10's classification turns on a numeric comparison against published bounds, and an off-by-one at either edge silently reclassifies an error into the wrong family — which sends recovery down the wrong path entirely.

15. Corner Cases

CaseBehaviourWhy
Frame intactNo mismatchP1
Single bit flipped anywhere in 72Mismatch, always§8, verified exhaustively
Any two bits flippedMismatch, always§8, all 2,556 pairs
Any odd number of bits flippedMismatch, always(x+1) is a factor — structural, not sampled
DBI enabled on one side onlyMismatch on every write; err_frame_misconfigured§7 — the frame differs, so the checksums must
x4 configuration32 populated, upper 40 padded to ones§7; padding with zeros would disagree with every device
Bit order reversedSelf-consistent and wrong§11 — the loop must run downward from D[71]
Pulse shorter than PULSE_MINClassified as the other class§10's shape discrimination
Pulse longer than PULSE_MAXClassified as the other classCould also be §10's daisy chain under multiple errors
Report with nothing outstandingerr_report_without_outstandingAnother device on the chain, or the parity class
Retention fullerr_overflow; the write is not retainedThe guarantee is lost for that write — never silent
Window expires with no reportEntry releasedThe only condition under which data may be freed
Retry itself corruptedAnother report, another retryCRC gives no bound on retries — §14's callout

Row nine deserves its place because it is where §10's disambiguation becomes unreliable in exactly the circumstances that matter. A daisy-chained ALERT_n under multiple simultaneous errors can present a pulse longer than the specification's bound, which this block classifies as the other error class — so a burst of CRC errors can be misread as a parity error. That is a real limitation of shape-based discrimination and it is not a fault in the implementation; it follows from the standard's own note.

16. DV — Exhaustive Where Possible, Adversarial Where Not

The CRC is a pure function over a finite domain, which makes parts of it provable by exhaustion. The recovery path is sequential and needs adversarial stimulus instead.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Independent CRC reference, computed a DIFFERENT way from the DUT:
// by explicit polynomial long division over a bit array rather than by
// the shift-and-fold recurrence. Two implementations of the same
// definition, so agreement is evidence rather than tautology.
// SIMULATION-ONLY.
function automatic [7:0] crc_ref(input bit [71:0] frame, input int w);
  bit [79:0] work;
  work = {frame[w-1:0], 8'h00};          // append 8 zero bits
  for (int i = w + 7; i >= 8; i--)
    if (work[i])
      work[i -: 9] = work[i -: 9] ^ 9'b1_0000_0111;   // x^8+x^2+x+1
  crc_ref = work[7:0];
endfunction
CheckWhat it establishes
Reference and DUT agree on 10,000 random 72-bit framesTwo independent implementations of one definition
Replay §12's frame; expect 0xF3The published worked value, from the RTL
Flip D[0] of §12's frame; expect 0xF4The concrete single-bit case
Exhaustive single-bit: all 72 positions change the checksum§8's first claim, proved not sampled
Exhaustive double-bit: all 2,556 pairs change it§8's second claim
3,000 random odd-weight patterns all detected§8's third claim, as a consistency check
Reverse the bit order in the reference; expect disagreement§11's direction trap is actually detectable
Pad the upper bits with zeros; expect disagreement§7's padding rule matters
Recovery: pulse of exactly PULSE_MIN and of exactly PULSE_MAXBoth band edges classify as CRC
Recovery: pulse of PULSE_MIN − 1 and PULSE_MAX + 1Both classify as the other class
Recovery: fill retention, then issue; expect err_overflow and no retentionThe guarantee's loss is visible
Recovery: report arriving after a window expiredAn entry already released cannot be retried

That last check is the one that produces the module's final instructive failure:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  A REPORT THAT ARRIVES AFTER ITS WINDOW CLOSED
    WINDOW = 24, PULSE_MIN = 6, PULSE_MAX = 10, DEPTH = 8

    t=0    write tag 3 issued, retained, window 24
    t=24   window expires with no report -> tag 3 RELEASED
           upstream is told the data may be freed
    t=30   ALERT_n asserts, pulse of 8 cycles -> classified CRC
    t=38   retry requested... for the oldest OUTSTANDING entry,
           which is now tag 5, not tag 3

    result: tag 5 is retried unnecessarily, and tag 3 -- the write
            that was actually corrupted -- is never retried. Its data
            has been freed and cannot be.

    diagnosis : WINDOW was set shorter than the real worst case. §10
            gives tCRC_ALERT a maximum of 13 ns and the pulse 6..10
            nCK, so the window must cover BOTH plus synchronisation.
            A window sized from the pulse alone is too short.
    why every assertion passes : P4 holds -- the release was legal,
            its window had genuinely expired. P5 holds -- the retry
            followed a CRC classification. P8 holds -- the pulse was
            in band. The bookkeeping is flawless and the outcome is
            wrong.
    what it really is : a PARAMETER error that presents as a data
            corruption bug. The block cannot detect it, because from
            inside, a late report is indistinguishable from a report
            about somebody else's transaction on the daisy chain
            (§10) -- which is exactly what err_report_without_
            outstanding reports when retention happens to be empty.

That is the right note for the module to end its verification on. Every property passes, the bookkeeping is correct, and a mis-sized parameter silently voids the guarantee — and the reason it cannot be caught from inside is that the block has no access to the physical latency the parameter is supposed to cover.

17. Debugging

SymptomLikely causeHow to confirm
CRC mismatch on every writeDBI enabled on one side only — §7err_frame_misconfigured; compare MR5 A11 with the controller
CRC mismatch on every write, config agreesBit order or padding — §11Compare against a known frame; §12's 0xF3
Occasional mismatches, pattern-dependentA real channel problem — 22.3 §5Vary the pattern; CRC is reporting truthfully
Mismatches rise sharply with rateLoss compounding — §1Termination will not help; §3's family may
ALERT_n pulses misclassifiedBand edges, or the daisy chain — §15Log pulse_len; check against the published bounds
Retries never stopThe channel corrupts retries too — §14CRC bounds nothing here; the channel needs fixing
Retried the wrong transactionWINDOW too short — §16Size it from tCRC_ALERT plus the pulse plus synchronisation
err_overflow under loadRetention too shallow for the windowDepth must cover writes in flight during a whole window
A stale error reported foreverThe sticky bit was never cleared — §10It stays set until an explicit MRS clears it
Error reported, nothing acts on itALERT_n unmonitored — 6.12 §3The classic failure: the system reports and nobody listens
Corrupt data read back before a retry§9's window — the core write did not waitNot a bug; CRC provides no interlock
Works with CRC disabled, fails with it enabledThe frame changed to 10 UI — §7Write timing differs; CWL handling may need the CRC-enabled value

Row twelve is worth knowing about. Enabling CRC changes the frame length to a fixed 10 unit intervals, and the mode registers carry a separate write-command-latency setting for the case where CRC and DM are both enabled. A system that enables CRC without accounting for that has changed its write timing.

Row eleven is not a defect and is frequently reported as one. §9 is explicit: the core write does not wait for the check, so there is a window in which memory holds data the system already knows is wrong. Anything reading that location in that window gets the corrupt value.

18. Misconceptions

“CRC prevents corrupted writes.” §9. The DRAM may write to its core without waiting for the check. Corrupt data reaches memory and is overwritten by a retry.

“The DRAM retries automatically.” §9. It reports. The controller retries, and the standard says the controller is responsible for data coherency and for opening any pages needed.

“CRC covers reads too.” §6. DDR4 supports CRC for writes and explicitly does not for reads.

“100% single-bit detection means the link is reliable.” §14's callout. It is a property of the code over stated error classes, not a property of the channel.

“A passing CRC means the channel has margin.” It means 72 bits arrived consistent with their checksum, once.

ALERT_n tells you which error occurred.” §10, and 6.12 §3. There is no selector. The classes are distinguished by pulse shape, and the standard notes the controller otherwise has to read mode registers.

“Pulse width reliably identifies a CRC error.” §15's row nine. ALERT_n is daisy-chained, and multiple simultaneous errors can stretch the observed pulse past its bound.

“The error clears itself.” §10. The status bit is sticky and remains set until the host clears it explicitly with an MRS command.

“DBI is outside the CRC.” §7. On an x8 the DBI flags occupy the frame's upper 8 bits, so the CRC covers them — which is what makes 22.3 §15's silent flag corruption detectable.

“Equalisation replaces termination.” §4. Termination reduces the magnitude of every reflection; equalisation removes the residue of those close enough to model. They address different parts of the problem.

“A DFE fixes crosstalk.” §2 and 22.3 §3. Crosstalk arrives with its aggressor rather than delayed, so a post-cursor equaliser has nothing to subtract.

“More DFE taps always help.” §4. Taps reach a fixed number of unit intervals. A reflection beyond that reach is not addressed by adding precision to taps that do not cover it.

“Detection and prevention are roughly equivalent.” §9's diagram. One converts silent permanent corruption into a loud recoverable event at the cost of a retry path and a buffering requirement. The other does not exist here.

19. Interview Reasoning

Why does absorbing stop being enough at higher rates? Because the dominant impairment shifts from reflection to loss, and termination does nothing about energy removed on the way out or about a transition still settling when its interval ends. Loss also compounds: content moves higher in frequency while the interval shrinks.

What does a DFE actually do? It uses bits it has already decided to estimate their contribution to bits it has not yet decided, and subtracts that before deciding. DDR5 specifies four taps, each one unit interval, reaching four unit intervals of post-cursor.

Why can a DFE not help with crosstalk? Crosstalk arrives with its aggressor rather than after a round trip, so there is no earlier decision on this line from which to predict it.

What is the risk in a DFE's feedback being a decision rather than the truth? A wrong decision produces a wrongly directed correction on following bits, so one marginal bit can become a short burst — which is why detection matters more at rates where equalisation is required.

Double the data rate. What happens to a reflection at k = 4? The round trip is a fixed physical time and the unit interval halves, so it moves to k = 8 — outside a four-tap reach. The channel goes from correctable to uncorrectable with nothing changed but the clock.

Which direction does DDR4's CRC protect, and why that one? Writes only. An undetected write error becomes permanent data in memory and propagates; a read error is transient and, in an ECC system, is covered by a code over the stored word anyway.

What is the polynomial, and what does it guarantee? The ATM-8 HEC, x⁸ + x² + x + 1, over a 72-bit frame. It detects all single-bit, all double-bit and all odd-count errors. The odd-count class follows structurally from (x + 1) being a factor.

Does the DRAM wait for the CRC check before writing to its core? No — the standard says it may not, for full writes. So corrupt data reaches memory, the controller retries and overwrites, and coherency is explicitly the controller's responsibility.

What obligation does that impose on the controller? It must retain write data until the report window has passed, because a write whose data has been released cannot be retried. Plus page management and coherency during recovery.

ALERT_n carries two error classes with no selector. How is a CRC error identified? By shape — a bounded pulse, 6 to 10 nCK, with a latency of at most 13 ns — rather than a long assertion. And the standard notes the alternative is reading mode registers, which is slow in a multi-rank system.

When does that shape-based identification fail? Under multiple simultaneous errors, because ALERT_n is daisy-chained and the observed pulse can stretch beyond its specified bound.

20. Exercises

  1. Compute the checksum for §12's frame with DBI enabled and flags 0x5A in the upper 8 bits. Then flip one DBI flag bit and confirm the checksum changes. Why does this make 22.3 §15's failure detectable, and what would have to be true of the frame layout for it not to be?

  2. §11's loop runs downward from the frame's top bit. Reverse it, then find the smallest frame on which the two directions give different checksums. Explain why a self-contained testbench would never notice.

  3. Prove that a polynomial with (x + 1) as a factor detects every odd-weight error, then check whether x⁸ + x² + x + 1 has any other factors and what, if anything, they guarantee.

  4. §16's failure is a mis-sized WINDOW. Derive the minimum defensible value from the published figures in §10 for DDR4-2400, stating every term including synchronisation, and say which terms you cannot determine from the standard alone.

  5. Retry targets the oldest outstanding entry. Implement the correlating alternative the standard mentions, specify what extra information it needs, and identify the case where it does better and the case where it does worse.

  6. err_overflow means a write was not retained and therefore cannot be retried. Derive the retention depth needed so that overflow is impossible, in terms of WINDOW and the maximum write issue rate. What does the answer say about enabling CRC on a high-throughput controller?

  7. A four-tap DFE reaches four unit intervals. Given a channel with discontinuities at k = 2, k = 3 and k = 7, state what equalisation, termination and re-encoding each contribute, and what remains unaddressed.

  8. This chapter refuses to build a DFE and builds a CRC. Write the criterion that distinguishes the two cases in one sentence, then apply it to three other mechanisms in this module and check whether the chapters that covered them made the same call.

21. Module 22, Complete

Four chapters, one question: why is the usable region narrower in a real system than in any datasheet, and what does a design actually do about it?

Termination is a decision with a value and a deadline. Chapter 22.1 owned what 6.7 explicitly declined — three selectable resistances rather than an on-off switch, a dynamic switch scoped to one rank and triggered by the write rather than by the pin, a posted latency tied to write latency and therefore to the speed bin, and a park state covering the intervals nobody thinks about. And every bit of it is a name: nine properties, two blocks, and no signal anywhere carrying an impedance.

Reflections explain the shapes. 22.2 supplied the cause the previous three modules kept handing forward. Energy turns around at a discontinuity and returns after a round trip, so it lands k unit intervals later than the bit that launched it — which accounts for the data dependence, the pattern sensitivity, the rank scaling and the fragmentation of a pass region. An unterminated pin returns all of it, and because every power of one is one, it does not attenuate on repeat bounces. That is why RTT_PARK is the most consequential setting in the module.

Three mechanisms share one input. 22.3 named ISI, crosstalk and loss, established that two add energy while the third removes it, and showed they are correlated through the data pattern — which is finally the reason 20.5 §2 found the published data-valid window exceeding the subtraction of its own limits. It also built the module's one re-encoding mitigation, and was precise that DBI improves nothing electrically: it bounds a bit count and pays for it with the DM pin.

And at the top rates, absorbing and re-encoding both run out. This chapter covered what is left: predict and subtract, or detect. Equalisation is not RTL and its reach is a hard boundary set by k. Write CRC is entirely RTL, its detection claims are provable from its polynomial — and it prevents nothing, because the DRAM writes to its core without waiting.

The discipline every chapter repeated, and the reason this module could not be written any other way: model the control and the mitigation, never the physics. Eleven synthesisable blocks across four chapters, thirty-nine assertions, and not one of them reaches an impedance, a volt or a picosecond. What they reach is configuration, accounting, encoding and detection — and each chapter's closing callout says exactly where that stops.

Chapter 20.3 §12 named the boundary four modules ago: every digital observable correct, the data wrong, nothing firing. This module is the explanation of that failure and the one mechanism that makes a part of it loud. Write CRC converts silent, permanent write corruption into a reported event — for one direction, within stated error classes, at the cost of a retry path and a buffering requirement.

Everything else remains exactly as invisible as it was, which is why a training sweep measures a region rather than a margin, and why Module 21 had to insist that training does not create margin.

Module 23 turns from whether the interface works to how well it performs: where latency actually goes, why achievable bandwidth falls short of the peak, how much row-buffer locality is worth, and which scheduling and page policies convert architecture into throughput.

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.