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 frequency | Loss is frequency-dependent, so attenuation rises |
| The unit interval shrinks | A 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:
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.
| Family | Mechanism | Reaches | Does not reach |
|---|---|---|---|
| Absorb (22.1) | Present an impedance at a junction | Reflections from that junction | Loss; other junctions; crosstalk |
| Re-encode (22.3) | Change the pattern presented | Level-count-limited channels | Anything not limited by level count |
| Predict and subtract (§3) | Model the interference and remove it | Post-cursor ISI within the model's memory | Crosstalk; pre-cursor; arrivals beyond reach |
| Detect (§6) | Check a code over the frame | Corruption after the fact | Nothing 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 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 trip | Corrupts | Within a four-tap reach |
|---|---|---|
k = 1 UI | bit N+1 | yes |
k = 2 UI | bit N+2 | yes |
k = 3 UI | bit N+3 | yes |
k = 4 UI | bit N+4 | yes |
k = 5 UI | bit N+5 | no |
k = 6 UI | bit N+6 | no |
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 need | Why it is unavailable |
|---|---|
| Tap coefficients as analog weights | Continuous quantities in volts |
| The received waveform | Continuous amplitude, which no digital signal carries |
| The summing node | Analog addition before the decision |
| The slicer's threshold behaviour | The 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:
| Width | CRC tree inputs | Upper bits |
|---|---|---|
| x8 | 72 | Upper 8 are the DBI inputs if DBI is enabled; all ones if disabled — D[71:64] |
| x16 | Two identical 72-bit trees | Upper 8 of each are DBI inputs, or ones — D[143:136] and D[71:64] |
| x4 | 32 | D[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 type | Detection |
|---|---|
| Random single-bit error | 100% |
| Random double-bit error | 100% |
| Random odd-count error | 100% |
| Random one multi-bit UI vertical column error, excluding DBI bits | 100% |
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:
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 meanwhileFour 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:
| Quantity | Value |
|---|---|
CRC error to ALERT_n latency — tCRC_ALERT | maximum 13 ns, DDR4-1600 through 2400 |
ALERT_n pulse width — CRC_ALERT_PW | 6 to 10 nCK, all four bins |
| Pulse-width measurement | From the device driving low to the device releasing and the controller pulling up |
| Applies to | x4, 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
// ---------------------------------------------------------------------
// 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);
endmoduleThe 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:
| Beat | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| Byte | 0x00 | 0xFF | 0x55 | 0xAA | 0x0F | 0xF0 | 0x33 | 0xCC |
With beat 0 in the frame's low byte and D[71:64] = 0xFF:
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 abstractThe 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
// ---------------------------------------------------------------------
// 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;
endmoduleTwo 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
// 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
| Case | Behaviour | Why |
|---|---|---|
| Frame intact | No mismatch | P1 |
| Single bit flipped anywhere in 72 | Mismatch, always | §8, verified exhaustively |
| Any two bits flipped | Mismatch, always | §8, all 2,556 pairs |
| Any odd number of bits flipped | Mismatch, always | (x+1) is a factor — structural, not sampled |
| DBI enabled on one side only | Mismatch on every write; err_frame_misconfigured | §7 — the frame differs, so the checksums must |
| x4 configuration | 32 populated, upper 40 padded to ones | §7; padding with zeros would disagree with every device |
| Bit order reversed | Self-consistent and wrong | §11 — the loop must run downward from D[71] |
Pulse shorter than PULSE_MIN | Classified as the other class | §10's shape discrimination |
Pulse longer than PULSE_MAX | Classified as the other class | Could also be §10's daisy chain under multiple errors |
| Report with nothing outstanding | err_report_without_outstanding | Another device on the chain, or the parity class |
| Retention full | err_overflow; the write is not retained | The guarantee is lost for that write — never silent |
| Window expires with no report | Entry released | The only condition under which data may be freed |
| Retry itself corrupted | Another report, another retry | CRC 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.
// 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| Check | What it establishes |
|---|---|
| Reference and DUT agree on 10,000 random 72-bit frames | Two independent implementations of one definition |
Replay §12's frame; expect 0xF3 | The published worked value, from the RTL |
Flip D[0] of §12's frame; expect 0xF4 | The 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_MAX | Both band edges classify as CRC |
Recovery: pulse of PULSE_MIN − 1 and PULSE_MAX + 1 | Both classify as the other class |
Recovery: fill retention, then issue; expect err_overflow and no retention | The guarantee's loss is visible |
| Recovery: report arriving after a window expired | An entry already released cannot be retried |
That last check is the one that produces the module's final instructive failure:
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
| Symptom | Likely cause | How to confirm |
|---|---|---|
| CRC mismatch on every write | DBI enabled on one side only — §7 | err_frame_misconfigured; compare MR5 A11 with the controller |
| CRC mismatch on every write, config agrees | Bit order or padding — §11 | Compare against a known frame; §12's 0xF3 |
| Occasional mismatches, pattern-dependent | A real channel problem — 22.3 §5 | Vary the pattern; CRC is reporting truthfully |
| Mismatches rise sharply with rate | Loss compounding — §1 | Termination will not help; §3's family may |
ALERT_n pulses misclassified | Band edges, or the daisy chain — §15 | Log pulse_len; check against the published bounds |
| Retries never stop | The channel corrupts retries too — §14 | CRC bounds nothing here; the channel needs fixing |
| Retried the wrong transaction | WINDOW too short — §16 | Size it from tCRC_ALERT plus the pulse plus synchronisation |
err_overflow under load | Retention too shallow for the window | Depth must cover writes in flight during a whole window |
| A stale error reported forever | The sticky bit was never cleared — §10 | It stays set until an explicit MRS clears it |
| Error reported, nothing acts on it | ALERT_n unmonitored — 6.12 §3 | The classic failure: the system reports and nobody listens |
| Corrupt data read back before a retry | §9's window — the core write did not wait | Not a bug; CRC provides no interlock |
| Works with CRC disabled, fails with it enabled | The frame changed to 10 UI — §7 | Write 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
-
Compute the checksum for §12's frame with DBI enabled and flags
0x5Ain 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? -
§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.
-
Prove that a polynomial with
(x + 1)as a factor detects every odd-weight error, then check whetherx⁸ + x² + x + 1has any other factors and what, if anything, they guarantee. -
§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. -
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.
-
err_overflowmeans a write was not retained and therefore cannot be retried. Derive the retention depth needed so that overflow is impossible, in terms ofWINDOWand the maximum write issue rate. What does the answer say about enabling CRC on a high-throughput controller? -
A four-tap DFE reaches four unit intervals. Given a channel with discontinuities at
k = 2,k = 3andk = 7, state what equalisation, termination and re-encoding each contribute, and what remains unaddressed. -
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
Related tutorials
- Related topic
DDR5
A 16-beat burst on a 64-bit channel would double granularity. DDR5 halves the channel instead, into two independent 32-bit sub-channels — preserving access size exactly while doubling the number of independent request streams.
- Related topic
DDR5 New Capabilities
On-die ECC corrects inside the array, CRC checks the link and pulses a pin, refresh management prevents disturbance. Four mechanisms, four verbs, and they do not compose into one guarantee.
- Related topic
The Refresh Requirement
Leakage produces a rule about the passage of time rather than about any operation. What the maintenance operation actually does, why it costs device availability, and how a digital design tracks a deadline, arbitrates it against traffic, and proves it never silently drops the obligation.
- Related topic
Restore Operations
Sensing consumed the stored state, so something must put it back. What restoration drives, why it covers a whole row, why a restored row is then cheap to access again, and an educational control model that cannot skip a prerequisite the array is unable to enforce.
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.
