DDR · Module 25
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.
Chapter 2.2 §2 describes a loss mechanism that is not leakage: “cell-to-cell disturbance under heavy repeated access,” and states that “the mitigations belong to Module 25.” Chapter 2.1 §6 explains why on-die correction became normal and defers “the generation-specific details of what a particular standard requires” to the same place. Chapter 4.6 §3 names the pressure behind four mechanisms and assigns the refresh-management command and tracking obligation to “Modules 15 and 25.”
This chapter is where those arrive. And it can do something the earlier chapters could not: check a published timing equation against its own published table, which §9 does for all six values.
The harder subject is the one none of the earlier chapters could reach. A DDR5 system has four integrity mechanisms operating at four different layers, and they do not compose into a single guarantee. One corrects inside the array and hides what it corrected. One checks the link and reports on a pin whose use is “system-dependent.” One fights a physical effect by spending refresh time. One reshapes the signal before any of the others sees it. §15's arbiter is about what that means for a controller, and §4 is about the visibility it costs.
1. Four Mechanisms, Four Layers
The addendum's feature list and function matrix name all four. DEVICE-VERIFIED, with each mechanism's layer added:
| Mechanism | Where it acts | What it does on an error |
|---|---|---|
| Decision feedback equalisation | the receiver, before sampling | reshapes — there is no “error” yet |
| Write CRC / Read CRC | the link, per burst | detects; asserts ALERT_n |
| On-die ECC | inside the array | corrects, internally |
| Directed refresh management | the array, over time | prevents, by refreshing neighbours |
Four layers, four verbs. Reshapes, detects, corrects, prevents — and no two of them are the same kind of action.
That is why they do not compose. A mechanism that prevents does not help with an error that has already occurred. A mechanism that detects does not repair. A mechanism that corrects hides what it repaired. §4 and §15 are about the consequences.
DEVICE-VERIFIED, the function matrix marks all of these supported at x4, x8 and x16: DFE, WRITE CRC, READ CRC, On-Die-ECC, and ECC Transparency and Error Scrub. The feature list adds “on-die ECC (bounded fault)” and “ECC transparency and error scrub” as separate bullets.
2. On-Die ECC Is a Bounded-Fault Mechanism
The feature list's parenthetical is the important part. DEVICE-VERIFIED: “On-die ECC (bounded fault).”
“Bounded fault” is a scope statement, not a strength statement. It says the mechanism handles faults up to a bound and does not claim anything beyond it. Chapter 4.6 §3 gives the reason it exists at all: “pushing to a denser process produces more cells that are marginal,” and on-die ECC “exists to make a denser array economically viable, which is a manufacturing argument rather than a reliability feature for the host's benefit.”
3. Transparency and Scrub Are the Interesting Half
Two capabilities are listed alongside the correction itself, and they exist because correction alone creates a problem.
DEVICE-VERIFIED: “ECC transparency and error scrub” — one feature-list bullet, and a matching function-matrix row “ECC Transparency and Error Scrub” supported at all three widths.
The names describe what each addresses.
Transparency is a mode in which the correction can be seen through. A mechanism that silently corrects gives a host no way to learn that anything was wrong; a transparency capability is what makes the internal activity observable at all.
Scrub is the act of reading, correcting and writing back, so that a correctable error does not sit in the array accumulating company until it becomes uncorrectable.
Both are answers to the same problem, from opposite ends. Scrub reduces how long a corrected-but-not-repaired error persists. Transparency reduces how invisible it is. And §4 is why both are needed.
4. The Visibility Problem
Chapter 4.6 §3 states this consequence sharply and it is worth quoting because it is the crux of the chapter: “Errors the device corrects internally are, by construction, less visible to the host. A system that used to see a rising correctable-error rate as an early warning of a failing part may now see a clean interface until the internal correction is exhausted. On-die ECC improves the part and degrades the host's visibility into it.”
So on-die ECC changes the shape of a failure's observable history, and the change is not favourable to prediction:
| Without on-die ECC | With on-die ECC | |
|---|---|---|
| Early marginal cells | visible as correctable errors at the interface | corrected internally; interface clean |
| Warning before failure | a rising rate over time | little or none |
| First host-visible symptom | a gradual trend | an uncorrectable error |
The failure goes from gradual to abrupt at the host's observation point — not because the device got worse, but because the interface now hides the early part.
Which is what transparency and scrub are for. Transparency restores some observability deliberately; scrub reduces the accumulation that the invisibility permits. Neither is a substitute for system-level ECC, and 4.6 §3 is explicit that treating one as the other “is a category error: one protects the array's manufacturability, the other protects the system's data.”
Chapter 25.2 §8 verified what the system-level mechanism costs on a DDR5 module: four check bits per 32-bit sub-channel, carried on the module's own data pins. The two mechanisms are not alternatives at different price points — they protect different things, and a module has both.
5. CRC Goes Both Ways
DEVICE-VERIFIED: the function matrix lists WRITE CRC and READ CRC as separate rows, both supported at x4, x8 and x16.
Chapter 22.4 built a write-CRC generator and an error-recovery block, and established from the polynomial that CRC detects and recovery is a retry. That analysis holds and is not repeated.
What this chapter adds is that DDR5 protects both directions, which has an asymmetry worth naming:
| Write CRC | Read CRC | |
|---|---|---|
| Who computes | the controller | the device |
| Who checks | the device | the controller |
| Who is told | the controller, via ALERT_n | the controller, directly |
| Recovery | retry the write | retry the read |
The asymmetry is in who learns. On a write, the device detects and must inform the controller through a dedicated pin — §6 is about that pin. On a read, the controller detects for itself and needs no signalling at all.
So ALERT_n exists for the write direction, and a read-CRC failure is a purely local discovery.
6. ALERT_n, and What “System-Dependent” Means
DEVICE-VERIFIED, from the addendum's pin description for ALERT_n, an input/output pin:
If there is an error in CRC, ALERT_n drives LOW for the period time interval and returns HIGH. During connectivity test mode, the pin functions as an input. “Usage of this signal is system-dependent”, and where it is not connected, the pin must be bonded appropriately.
Three properties, and the third is a real system hazard.
It is a pulse, not a level. Low for an interval, then back high. So it reports an event and not a state — and a host that samples it periodically can miss one entirely, which is the same observability problem 25.3 §11 found in PWR_GOOD and solves the same way: with a sticky record.
It is bidirectional, functioning as an input in connectivity test mode. So a driver must not assume it owns the net.
And its usage is system-dependent, which means the mechanism's value is a system property rather than a device one. The device detects the error and pulses the pin regardless. Whether anything happens next depends entirely on whether the system connected it and acts on it.
7. The Disturbance Mechanism, and the Command Against It
Chapter 2.2 §2 describes the physics and defers the mitigation here. Chapter 4.6 §3 states the mechanism's necessity: “repeated, targeted activation of one row can degrade its neighbours faster than the normal refresh interval repairs them,” and that this “is a direct consequence of the density trend.”
DEVICE-VERIFIED, from the addendum's own Directed Refresh Management section: the DRFM command refreshes the ±1, ±2, ±3 and ±4 physically adjacent neighbouring rows, with the set depending on configuration. There are all-bank (DRFMab) and same-bank (DRFMsb) forms.
Two register fields govern it. DEVICE-VERIFIED:
| Field | Role |
|---|---|
MR58:OP[0] | the RFM requirement bit; set to 1 when RFM is required |
MR59:OP[2:1] | the Bounded Refresh Configuration (BRC) |
MR59:OP[3] | the BRC support level — R/W on this device, which supports all BRC options |
And the addendum notes that the relevant specifications apply “if the RFM requirement bit is set to 1 (MR58:OP[0]=1) or ARFM is set to level A, B, or C.”
So RFM is conditional. A device may or may not require it, and the controller learns which from a register rather than from the part number.
8. The Bounded Refresh Configuration
DEVICE-VERIFIED, the addendum's Table 5 — Bounded Refresh Configuration and tDRFM:
BRC (MR59:OP[2:1]) | Rows refreshed | tDRFMab | tDRFMsb |
|---|---|---|---|
| BRC 2 | Always ±1, Ratio ±2 | 280 ns | 240 ns |
| BRC 3 | Always ±1, Ratio ±2, ±3 | 420 ns | 360 ns |
| BRC 4 | Always ±1, Ratio ±2, ±3, ±4 | 560 ns | 480 ns |
And Table 4 — tRRF by device density, the per-row refresh duration:
| Parameter | 8 Gb | 16 Gb | 24 Gb | 32 Gb |
|---|---|---|---|---|
tRRFab (MIN) | 70 | 70 | 70 | 70 |
tRRFsb (MIN) | 60 | 60 | 60 | 60 |
The per-row duration is identical at every density — 70 for all-bank, 60 for same-bank. That is worth noticing: the cost of refreshing one neighbour row does not grow with capacity, so a denser part pays the same per-row price.
The addendum states the equation as tDRFM = (2 × tRRF) × BRC.
9. The Equation Explains Itself
An equation and the table it generates are two independent statements, and checking one against the other is the kind of verification 22.4 did for its CRC polynomial. This one checks out exactly, and the checking reveals what the equation means.
DERIVED, all six values recomputed:
| BRC | Scope | 2 × tRRF × BRC | Published | |
|---|---|---|---|---|
| 2 | ab | 2 × 70 × 2 = 280 | 280 ns | match |
| 3 | ab | 2 × 70 × 3 = 420 | 420 ns | match |
| 4 | ab | 2 × 70 × 4 = 560 | 560 ns | match |
| 2 | sb | 2 × 60 × 2 = 240 | 240 ns | match |
| 3 | sb | 2 × 60 × 3 = 360 | 360 ns | match |
| 4 | sb | 2 × 60 × 4 = 480 | 480 ns | match |
All six, exactly. Now the useful part — regrouping the equation explains the factor of two, which the specification states without interpreting:
tDRFM = (2 x tRRF) x BRC
= (2 x BRC) x tRRF
\________/ \__/
rows per-row
refreshed duration2 × BRC is the number of neighbour rows, because §7 established that the neighbours come in pairs — ±1, ±2, and so on, one row on each side. DERIVED, and it reproduces the table under this reading too:
| BRC | Neighbour rows | × tRRFab | = |
|---|---|---|---|
| 2 | ±1, ±2 → 4 rows | 4 × 70 | 280 |
| 3 | ±1, ±2, ±3 → 6 rows | 6 × 70 | 420 |
| 4 | ±1, ±2, ±3, ±4 → 8 rows | 8 × 70 | 560 |
So the equation is not two arbitrary factors — it is a row count times a per-row cost, and the row count is two per BRC level because a row has two neighbours at each distance. The 2 is the two directions.
10. What DRFM Costs
A duration in nanoseconds is hard to weigh. Against the device's own refresh interval it becomes concrete.
DEVICE-VERIFIED, the addendum's refresh specification: TC maximum up to 95 °C, with 32 ms / 8192-cycle refresh up to 85 °C and 16 ms / 8192-cycle above 85 °C to 95 °C.
DERIVED, recomputed:
32 ms / 8192 = 3.906 us per refresh interval (up to 85 C)
16 ms / 8192 = 1.953 us per refresh interval (85-95 C)
exactly 2x shorter when hotNow DRFM as a fraction of that interval. DERIVED, every cell recomputed:
| BRC / scope | tDRFM | % of a 3.906 µs interval | % of a 1.953 µs interval |
|---|---|---|---|
| BRC2 same-bank | 240 ns | 6.14% | 12.29% |
| BRC2 all-bank | 280 ns | 7.17% | 14.34% |
| BRC3 all-bank | 420 ns | 10.75% | 21.50% |
| BRC4 same-bank | 480 ns | 12.29% | 24.58% |
| BRC4 all-bank | 560 ns | 14.34% | 28.67% |
A single BRC4 all-bank DRFM command consumes over a quarter of a hot refresh interval. That is a large fraction of the device's availability for one command, and it is the cost 4.6 §3 describes as “a mechanism added to hold the line.”
Two readings, and the second is the one that compounds.
The cost rises with the configured distance, linearly. §9's regrouping says why — twice the rows, twice the time — so choosing BRC4 over BRC2 doubles the per-command cost.
And it doubles again when the device is hot, because the interval it competes with halves. So a hot device running BRC4 pays four times the availability cost of a cool device running BRC2, for the same command. Temperature and configuration multiply, and 2.7 establishes that the temperature driving the shorter interval is the same temperature that makes disturbance matter more.
11. Decision Feedback Equalisation
The fourth mechanism, and the only one that acts before there is an error to speak of.
DEVICE-VERIFIED: the feature list includes decision feedback equalisation (DFE); the function matrix marks DFE supported at x4, x8 and x16; and the functional block diagrams show a DFE block and DFE Control Logic in the input path, alongside MR53[4:0] and MR53[6:5] taps near the loopback, output and phase control.
Chapter 4.6 §3 explains what it does and why it is the culmination of a progression: ZQ calibration “adjusted impedance against a reference,” DDR4's reference-voltage training “adjusted a threshold against the measured signal,” and equalisation “adjusts for the channel's dynamic behaviour — not a static offset but a response that depends on the data history.” That chapter calls the progression “from designed, to measured once, to measured and adapted per bit.”
What this chapter adds is its position in the integrity stack, which §1's table gives: DFE acts before sampling, so it has no error to correct and no error to report. It changes the probability that the layers above it see an error at all.
That makes it the only one of the four whose success is invisible by construction. CRC reports detections, on-die ECC can be made transparent, DRFM has a command and a duration. DFE working well produces nothing observable — the errors it prevents simply never appear at any layer — and DFE working badly shows up as somebody else's CRC failure.
This chapter does not model DFE. Chapter 22.2 and 22.3 own the electrical subject matter, and 22.3 established at length that a synthesisable block cannot compute an eye or an impedance. A DFE model in RTL would be the fake-physics error that module refuses, and §15's arbiter therefore treats DFE as a layer whose configuration state is known and whose effect is not.
12. The Integrity Stack
Two things the structure makes visible that §1's table cannot.
DFE has no path to the host at all. Every other mechanism reaches the bottom row through something — a pin, a scrub path, an obligation. DFE's only output is into CRC, which is §11's point drawn: its success is the absence of somebody else's failure, and there is no arrow that carries good news.
And the host's view is three arrows wide while the mechanisms are four. That is §4's visibility problem as a structural fact rather than an argument. The array's marginal cells reach the host only through the scrub and transparency path — if that path is not enabled, the ecc node has no outgoing edge to the host at all, and the first thing the host learns is an uncorrectable error.
13. The Bounded-Refresh Block
// ---------------------------------------------------------------------
// drfm_bounded_refresh -- the DEVICE-VERIFIED BRC configuration, with
// tDRFM computed as (rows x per-row duration) and checked against the
// published equation's own form.
//
// CLASSIFICATION: educational, synthesisable.
//
// VERIFIED IDENTITY (§9): tDRFM = (2 x tRRF) x BRC, which regroups to
// (2 x BRC) rows x tRRF per row. All six published values reproduce
// under both readings. This block computes the ROW-COUNT form and
// asserts it equals the published form, so the regrouping is enforced
// rather than merely asserted in prose.
//
// WHAT IT DOES NOT MODEL:
// - the array, disturbance physics, or what a neighbour row IS
// - refresh scope or scheduling (Module 15 owns both, with
// refresh_scope_ledger and refresh_credit_ledger)
// - whether RFM is REQUIRED, which is MR58:OP[0] and an input here
//
// tRRF IS DENSITY-INDEPENDENT on this device (§8): 70 all-bank and 60
// same-bank at 8, 16, 24 and 32 Gb. So density is NOT a parameter,
// and adding one would imply a dependence the table denies.
// ---------------------------------------------------------------------
module drfm_bounded_refresh #(
// DEVICE-VERIFIED per-row durations, in nanoseconds.
parameter int TRRF_AB = 70,
parameter int TRRF_SB = 60,
// DEVICE-VERIFIED BRC range: 2, 3 or 4.
parameter int BRC_MIN = 2,
parameter int BRC_MAX = 4,
parameter int BRC_W = 3,
parameter int DUR_W = 12,
parameter int CNT_W = 24
) (
input logic clk,
input logic rst_n,
// ── Configuration. MR59:OP[2:1] carries BRC; MR58:OP[0] says
// whether RFM is required at all (§7).
input logic cfg_valid,
input logic [BRC_W-1:0] cfg_brc,
input logic rfm_required,
input logic cfg_same_bank, // DRFMsb vs DRFMab
// ── A DRFM command issued.
input logic drfm_issue,
// ── Configuration state.
output logic [BRC_W-1:0] brc_in_force,
output logic brc_valid,
// §9: 2 x BRC neighbour rows, one on each side at each distance.
output logic [BRC_W:0] rows_refreshed,
output logic [DUR_W-1:0] t_drfm_ns,
output logic [DUR_W-1:0] t_rrf_ns,
// ── Accounting.
output logic [CNT_W-1:0] cnt_drfm,
output logic [CNT_W-1:0] ns_spent_in_drfm,
output logic err_brc_unsupported,
output logic err_issue_without_config,
output logic err_issue_when_not_required,
output logic any_error
);
initial begin
if (TRRF_AB < 1 || TRRF_SB < 1)
$fatal(1, "drfm_bounded_refresh: per-row durations must be positive");
if (TRRF_SB > TRRF_AB)
// §9's callout: same-bank is the cheaper per-row scope, 60
// against 70, uniformly at every BRC level. A parameterisation
// reversing that inverts the only saving the scope offers.
$fatal(1, "drfm_bounded_refresh: same-bank per-row cost (%0d) must not exceed all-bank (%0d)",
TRRF_SB, TRRF_AB);
if (BRC_MIN != 2 || BRC_MAX != 4)
$fatal(1, "drfm_bounded_refresh: BRC range is 2..4 (device-verified)");
if (BRC_W < 3)
$fatal(1, "drfm_bounded_refresh: BRC_W too narrow for BRC_MAX");
// The verified identity, checked at elaboration for every legal
// BRC and both scopes: the row-count form must equal the
// published (2 x tRRF) x BRC form, and must fit DUR_W.
for (int b = BRC_MIN; b <= BRC_MAX; b++) begin
if (((2 * b) * TRRF_AB) != ((2 * TRRF_AB) * b))
$fatal(1, "drfm_bounded_refresh: row-count form disagrees at BRC%0d ab", b);
if (((2 * b) * TRRF_SB) != ((2 * TRRF_SB) * b))
$fatal(1, "drfm_bounded_refresh: row-count form disagrees at BRC%0d sb", b);
if (((2 * b) * TRRF_AB) > ((1 << DUR_W) - 1))
$fatal(1, "drfm_bounded_refresh: DUR_W too narrow for BRC%0d (%0d ns)",
b, (2 * b) * TRRF_AB);
end
end
logic [BRC_W-1:0] brc_q;
logic have_q, sb_q, required_q;
logic [CNT_W-1:0] n_drfm, ns_acc;
logic e_brc, e_nocfg, e_notreq;
// Declared above the continuous assigns that read it.
logic brc_ok;
assign brc_ok = (cfg_brc >= BRC_W'(BRC_MIN)) && (cfg_brc <= BRC_W'(BRC_MAX));
// §9's regrouping, computed rather than tabulated.
logic [BRC_W:0] rows_c;
logic [DUR_W-1:0] per_row_c, tdrfm_c;
assign rows_c = have_q ? ((BRC_W+1)'(brc_q) << 1) : '0;
assign per_row_c = sb_q ? DUR_W'(TRRF_SB) : DUR_W'(TRRF_AB);
assign tdrfm_c = have_q ? DUR_W'(rows_c * per_row_c) : '0;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
brc_q <= '0; have_q <= 1'b0; sb_q <= 1'b0; required_q <= 1'b0;
n_drfm <= '0; ns_acc <= '0;
e_brc <= 1'b0; e_nocfg <= 1'b0; e_notreq <= 1'b0;
end else begin
e_brc <= 1'b0; e_nocfg <= 1'b0; e_notreq <= 1'b0;
if (cfg_valid) begin
if (!brc_ok) begin
// An unsupported BRC is REJECTED, not clamped. Clamping
// would leave the controller budgeting a tDRFM the device
// does not use, and every availability figure after that
// would be wrong by a known factor.
e_brc <= 1'b1;
end else begin
brc_q <= cfg_brc;
sb_q <= cfg_same_bank;
required_q <= rfm_required;
have_q <= 1'b1;
end
end
if (drfm_issue) begin
if (!have_q) begin
e_nocfg <= 1'b1;
end else begin
// §7: the specifications apply when RFM is required. A DRFM
// issued when it is not is reported -- it is not illegal,
// but it spends availability for no obligation.
if (!required_q) e_notreq <= 1'b1;
if (n_drfm != {CNT_W{1'b1}}) n_drfm <= n_drfm + CNT_W'(1);
if (ns_acc < ({CNT_W{1'b1}} - CNT_W'(tdrfm_c)))
ns_acc <= ns_acc + CNT_W'(tdrfm_c);
end
end
end
end
assign brc_in_force = brc_q;
assign brc_valid = have_q;
assign rows_refreshed = rows_c;
assign t_drfm_ns = tdrfm_c;
assign t_rrf_ns = per_row_c;
assign cnt_drfm = n_drfm;
assign ns_spent_in_drfm = ns_acc;
assign err_brc_unsupported = e_brc;
assign err_issue_without_config = e_nocfg;
assign err_issue_when_not_required = e_notreq;
assign any_error = e_brc | e_nocfg | e_notreq;
endmoduleThe elaboration loop checks §9's identity for every legal BRC and both scopes. That is unusual for an elaboration guard and it is the point: the regrouping is the chapter's contribution, so it is enforced in the parameterisation rather than asserted only in prose. A parameter set where the two forms disagree does not build.
And density is deliberately not a parameter. §8 verified that tRRF is identical at 8, 16, 24 and 32 Gb — so adding a density input would imply a dependence the published table denies, and would invite an integrator to look for a scaling that does not exist.
14. The Activation Tracker
// ---------------------------------------------------------------------
// rfm_activation_tracker -- counts activations per bank since that
// bank's last DRFM, and raises the obligation when a count crosses a
// threshold.
//
// CLASSIFICATION: educational, synthesisable.
//
// COLLISION NOTE: 17.1's activate_concurrency_budget governs how many
// activations may be IN FLIGHT within a rolling window (tFAW's
// subject). This block counts how many have OCCURRED since the last
// DRFM to a bank, which has a different reset condition and a
// different purpose. Both are activation counters and they are not
// the same counter.
//
// WHAT IT DOES NOT MODEL:
// - disturbance physics, or which rows are physically adjacent.
// §7: DRFM refreshes +/-1..+/-4 NEIGHBOURS, and physical
// adjacency is a device-internal mapping this block cannot see.
// - any real threshold. The threshold is an INPUT, because the
// device-verified material gives BRC and tDRFM but no activation
// count at which RFM becomes due.
// - the device's own internal counters, which exist and are not
// visible at the interface.
// ---------------------------------------------------------------------
module rfm_activation_tracker #(
parameter int BANKS = 32, // 25.1: x4/x8 verified count
parameter int BANK_W = $clog2(BANKS),
parameter int ACT_W = 16,
// A COUNT of banks up to BANKS needs clog2(BANKS+1).
parameter int BANKCNT_W = $clog2(BANKS + 1),
parameter int CNT_W = 24
) (
input logic clk,
input logic rst_n,
input logic rfm_required,
// Threshold at which a bank's accumulation raises the obligation.
input logic [ACT_W-1:0] act_threshold,
input logic threshold_valid,
// ── Activity.
input logic act_valid,
input logic [BANK_W-1:0] act_bank,
// ── A DRFM discharging the obligation. All-bank clears every
// bank's count; same-bank clears one.
input logic drfm_valid,
input logic drfm_all_bank,
input logic [BANK_W-1:0] drfm_bank,
// ── Obligation.
output logic obligation_due,
output logic [BANKCNT_W-1:0] n_banks_due,
output logic [BANK_W-1:0] worst_bank,
output logic [ACT_W-1:0] worst_count,
// ── Accounting.
output logic [CNT_W-1:0] cnt_drfm_ab,
output logic [CNT_W-1:0] cnt_drfm_sb,
output logic [CNT_W-1:0] cnt_threshold_crossings,
// Sticky: an obligation that went unserved is evidence even after
// a later DRFM clears the count.
output logic ever_exceeded_unserved,
output logic err_act_bank_out_of_range,
output logic err_no_threshold,
output logic err_saturated,
output logic any_error
);
initial begin
if (BANKS < 2 || (BANKS & (BANKS - 1)) != 0)
// The lower bound matters: a power-of-two test alone accepts 0
// and 1, either of which makes BANK_W zero and the bank index
// meaningless.
$fatal(1, "rfm_activation_tracker: BANKS must be a power of two >= 2");
if (BANK_W != $clog2(BANKS))
$fatal(1, "rfm_activation_tracker: BANK_W must address BANKS");
if (BANKCNT_W <= BANK_W)
// A count up to BANKS needs one more bit than an index into it.
// Too narrow and n_banks_due cannot represent "all of them".
$fatal(1, "rfm_activation_tracker: BANKCNT_W too narrow to count BANKS");
end
logic [ACT_W-1:0] acc [BANKS];
logic e_rng, e_thr, e_sat, ever_uns;
logic [CNT_W-1:0] n_ab, n_sb, n_cross;
// Declared above the continuous assigns that read them.
logic bank_ok;
assign bank_ok = ({{(32-BANK_W){1'b0}}, act_bank} < 32'(BANKS));
logic [BANKCNT_W-1:0] n_due;
logic [BANK_W-1:0] w_bank;
logic [ACT_W-1:0] w_cnt;
always_comb begin
n_due = '0;
w_bank = '0;
w_cnt = '0;
for (int b = 0; b < BANKS; b++) begin
if (threshold_valid && (acc[b] >= act_threshold))
n_due = n_due + BANKCNT_W'(1);
if (acc[b] > w_cnt) begin
w_cnt = acc[b];
w_bank = BANK_W'(b);
end
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int b = 0; b < BANKS; b++) acc[b] <= '0;
e_rng <= 1'b0; e_thr <= 1'b0; e_sat <= 1'b0; ever_uns <= 1'b0;
n_ab <= '0; n_sb <= '0; n_cross <= '0;
end else begin
e_rng <= 1'b0; e_thr <= 1'b0; e_sat <= 1'b0;
if (act_valid && !bank_ok) e_rng <= 1'b1;
if (act_valid && rfm_required && !threshold_valid) e_thr <= 1'b1;
// ── Accumulate. A DRFM arriving in the same cycle clears
// AFTER this activation is counted, so an activation is
// never lost to a coincident discharge.
if (act_valid && bank_ok) begin
if (acc[act_bank] == {ACT_W{1'b1}}) e_sat <= 1'b1;
else begin
acc[act_bank] <= acc[act_bank] + ACT_W'(1);
if (threshold_valid && ((acc[act_bank] + ACT_W'(1)) == act_threshold))
if (n_cross != {CNT_W{1'b1}}) n_cross <= n_cross + CNT_W'(1);
end
end
// ── Discharge.
if (drfm_valid) begin
if (drfm_all_bank) begin
for (int b = 0; b < BANKS; b++) acc[b] <= '0;
if (n_ab != {CNT_W{1'b1}}) n_ab <= n_ab + CNT_W'(1);
end else begin
acc[drfm_bank] <= '0;
if (n_sb != {CNT_W{1'b1}}) n_sb <= n_sb + CNT_W'(1);
end
end
// Sticky evidence: a threshold was crossed and the cycle ended
// without a DRFM discharging it.
if (threshold_valid && (n_due != '0) && !drfm_valid) ever_uns <= 1'b1;
end
end
assign obligation_due = threshold_valid && (n_due != '0);
assign n_banks_due = n_due;
assign worst_bank = w_bank;
assign worst_count = w_cnt;
assign cnt_drfm_ab = n_ab;
assign cnt_drfm_sb = n_sb;
assign cnt_threshold_crossings = n_cross;
assign ever_exceeded_unserved = ever_uns;
assign err_act_bank_out_of_range = e_rng;
assign err_no_threshold = e_thr;
assign err_saturated = e_sat;
assign any_error = e_rng | e_thr | e_sat;
endmoduleAn activation coincident with a DRFM is counted, then cleared. The sequential block increments before the discharge assignment, so the later write wins for that bank — which is correct, because an activation that happened cannot be un-happened by a refresh arriving in the same cycle. The opposite ordering would silently lose activations exactly when the system is busiest.
And ever_exceeded_unserved is sticky for the reason 24.2 §11's trust flag is. A threshold crossed and not discharged is evidence of a controller that fell behind its obligation, and a later DRFM clearing the count must not also clear the record that it was late.
15. The Layer Arbiter
// ---------------------------------------------------------------------
// integrity_layer_arbiter -- which of four mechanisms owns a fault,
// and whether the host finds out.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHY THIS BLOCK EXISTS: §1 establishes four mechanisms with four
// different verbs -- reshapes, detects, corrects, prevents -- at four
// layers. They do NOT compose into one guarantee, and the question a
// controller actually faces is "who handles this, and do I hear about
// it". That question has an answer and it is not in any datasheet
// because it depends on system choices (§6).
//
// WHAT IT DOES NOT MODEL:
// - any code, polynomial, or actual correction. 22.4 owns the CRC
// and proved its properties from the polynomial; on-die ECC's
// code is not published (§2's callout).
// - DFE's EFFECT. §11: it changes the probability of an error
// existing, which is not computable here, and 22.3 established
// that a synthesisable block must not fake physics.
// - any fault RATE. This routes a fault that has occurred.
// ---------------------------------------------------------------------
module integrity_layer_arbiter #(
parameter int CNT_W = 24
) (
input logic clk,
input logic rst_n,
// ── Which layers exist and are usable. §6: ALERT_n's usage is
// "system-dependent", so reachability is a system property and
// must be an input rather than an assumption.
input logic dfe_enabled,
input logic crc_enabled,
input logic alert_connected,
input logic ondie_ecc_enabled,
input logic ecc_transparency_enabled,
input logic scrub_enabled,
input logic rfm_enabled,
// ── A fault, by origin. Exactly one may be asserted.
input logic fault_link, // corrupted in transit
input logic fault_cell_bounded, // within on-die ECC's bound
input logic fault_cell_beyond, // beyond it
input logic fault_disturb, // a neighbour degraded
// ── Verdict: which layer handles it.
output logic handled_by_crc,
output logic handled_by_ondie_ecc,
output logic prevented_by_rfm,
output logic handled_by_nobody,
// ── Visibility: does the host learn?
output logic host_informed,
output logic silently_corrected,
output logic silently_lost,
// ── Accounting.
output logic [CNT_W-1:0] cnt_informed,
output logic [CNT_W-1:0] cnt_silent_correct,
output logic [CNT_W-1:0] cnt_silent_loss,
output logic err_multiple_fault_origins,
output logic err_crc_without_alert,
output logic err_ecc_without_visibility,
output logic any_error
);
logic [CNT_W-1:0] n_inf, n_sc, n_sl;
logic e_multi, e_crc, e_ecc;
// Declared above the continuous assigns that read them.
logic any_fault;
assign any_fault = fault_link | fault_cell_bounded |
fault_cell_beyond | fault_disturb;
logic one_fault;
assign one_fault = $onehot({fault_link, fault_cell_bounded,
fault_cell_beyond, fault_disturb});
// ── Routing. Each verb of §1 handles exactly its own fault class.
logic h_crc, h_ecc, p_rfm, h_none;
assign h_crc = one_fault && fault_link && crc_enabled;
assign h_ecc = one_fault && fault_cell_bounded && ondie_ecc_enabled;
assign p_rfm = one_fault && fault_disturb && rfm_enabled;
// Nobody handles it: a link fault with CRC off, a bounded cell
// fault with ECC off, a disturbance with RFM off, or ANY fault
// beyond on-die ECC's bound -- which no layer here repairs.
assign h_none = one_fault && !h_crc && !h_ecc && !p_rfm;
// ── Visibility, which is the chapter's subject.
// CRC informs only if ALERT_n is actually connected (§6).
// On-die ECC informs only through transparency or scrub (§4).
logic informed, silent_ok, silent_bad;
assign informed = (h_crc && alert_connected) ||
(h_ecc && (ecc_transparency_enabled || scrub_enabled)) ||
(fault_cell_beyond && one_fault);
// Corrected, and the host never learns: the §4 visibility problem.
assign silent_ok = h_ecc && !ecc_transparency_enabled && !scrub_enabled;
// Detected or unhandled, and the host never learns: the §6 hazard.
assign silent_bad = one_fault && !informed && !silent_ok;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_inf <= '0; n_sc <= '0; n_sl <= '0;
e_multi <= 1'b0; e_crc <= 1'b0; e_ecc <= 1'b0;
end else begin
e_multi <= 1'b0; e_crc <= 1'b0; e_ecc <= 1'b0;
if (any_fault && !one_fault) e_multi <= 1'b1;
// §6's hazard, as a configuration error rather than a fault:
// CRC enabled with no path for its report.
if (crc_enabled && !alert_connected) e_crc <= 1'b1;
// §4's hazard: correction with no visibility path at all.
if (ondie_ecc_enabled && !ecc_transparency_enabled && !scrub_enabled)
e_ecc <= 1'b1;
if (one_fault) begin
if (informed && (n_inf != {CNT_W{1'b1}})) n_inf <= n_inf + CNT_W'(1);
if (silent_ok && (n_sc != {CNT_W{1'b1}})) n_sc <= n_sc + CNT_W'(1);
if (silent_bad && (n_sl != {CNT_W{1'b1}})) n_sl <= n_sl + CNT_W'(1);
end
end
end
assign handled_by_crc = h_crc;
assign handled_by_ondie_ecc = h_ecc;
assign prevented_by_rfm = p_rfm;
assign handled_by_nobody = h_none;
assign host_informed = informed;
assign silently_corrected = silent_ok;
assign silently_lost = silent_bad;
assign cnt_informed = n_inf;
assign cnt_silent_correct = n_sc;
assign cnt_silent_loss = n_sl;
assign err_multiple_fault_origins = e_multi;
assign err_crc_without_alert = e_crc;
assign err_ecc_without_visibility = e_ecc;
assign any_error = e_multi | e_crc | e_ecc;
endmoduledfe_enabled is an input that nothing reads, and that is deliberate. §11 established that DFE changes the probability an error exists rather than handling one, so a routing model has nothing to route to it. The port exists so the omission is visible in the interface rather than looking like an oversight — and removing it would suggest DFE is not part of the stack, which §12's diagram shows it is.
And silently_corrected and silently_lost are separate outputs. One is on-die ECC doing its job with the visibility path off — a correct outcome with a blind host, which is §4's problem. The other is a fault nobody handled or nobody reported, which is data loss. Collapsing them into one “silent” count would merge the acceptable with the unacceptable, and the whole point of the arbiter is that those are different.
16. What the Assertions Prove
// Bind unit note: P1-P5 reference drfm_bounded_refresh's internals,
// P6-P10 rfm_activation_tracker's, P11-P15 integrity_layer_arbiter's.
// P1 -- §9's identity, as a runtime invariant as well as an
// elaboration check: tDRFM is the row count times the per-row cost.
property p_tdrfm_is_rows_times_per_row;
@(posedge clk) disable iff (!rst_n)
brc_valid |-> (t_drfm_ns == DUR_W'(rows_refreshed * t_rrf_ns));
endproperty
assert property (p_tdrfm_is_rows_times_per_row);
// P2 -- §9: the row count is two per BRC level, because neighbours
// come in pairs.
property p_rows_is_two_per_brc;
@(posedge clk) disable iff (!rst_n)
brc_valid |-> (rows_refreshed == ((BRC_W+1)'(brc_in_force) << 1));
endproperty
assert property (p_rows_is_two_per_brc);
// P3 -- FORBIDDEN. An unsupported BRC never enters force. Clamping
// would leave the controller budgeting a tDRFM the device never uses.
property p_bad_brc_never_in_force;
@(posedge clk) disable iff (!rst_n)
brc_valid |-> ((brc_in_force >= BRC_W'(BRC_MIN)) &&
(brc_in_force <= BRC_W'(BRC_MAX)));
endproperty
assert property (p_bad_brc_never_in_force);
// P4 -- the same-bank scope is never more expensive per row than
// all-bank. §9's callout: 60 against 70, uniformly.
property p_same_bank_is_never_costlier;
@(posedge clk) disable iff (!rst_n)
(t_rrf_ns <= DUR_W'(TRRF_AB));
endproperty
assert property (p_same_bank_is_never_costlier);
// P5 -- FORBIDDEN. A DRFM before configuration is reported and never
// accounted, so the availability figures cannot be polluted.
property p_no_accounting_before_config;
@(posedge clk) disable iff (!rst_n)
(drfm_issue && !brc_valid)
|=> (err_issue_without_config && (cnt_drfm == $past(cnt_drfm, 1)));
endproperty
assert property (p_no_accounting_before_config);
// P6 -- an all-bank DRFM discharges EVERY bank. §14: that is what
// distinguishes it from the same-bank form.
property p_all_bank_clears_everything;
@(posedge clk) disable iff (!rst_n)
(drfm_valid && drfm_all_bank && !act_valid) |=> (n_banks_due == '0);
endproperty
assert property (p_all_bank_clears_everything);
// P7 -- FORBIDDEN. An activation coincident with a DRFM is never
// lost. §14: an activation that happened cannot be un-happened.
property p_activation_never_lost_to_discharge;
@(posedge clk) disable iff (!rst_n)
(act_valid && drfm_valid && !drfm_all_bank && (act_bank == drfm_bank)
&& (worst_count != {ACT_W{1'b1}}))
|=> (worst_count <= $past(worst_count, 1) + ACT_W'(1));
endproperty
assert property (p_activation_never_lost_to_discharge);
// P8 -- the obligation is never raised without a threshold. An
// obligation against an unknown threshold is an opinion.
property p_no_obligation_without_threshold;
@(posedge clk) disable iff (!rst_n)
(!threshold_valid |-> !obligation_due);
endproperty
assert property (p_no_obligation_without_threshold);
// P9 -- the due-bank count never exceeds the bank count. Guards the
// COUNT-vs-INDEX trap: a too-narrow BANKCNT_W would wrap at BANKS.
property p_due_count_bounded;
@(posedge clk) disable iff (!rst_n)
(n_banks_due <= BANKCNT_W'(BANKS));
endproperty
assert property (p_due_count_bounded);
// P10 -- an unserved obligation is never forgotten. A later DRFM
// clears the count and must not clear the record of lateness.
property p_unserved_record_is_sticky;
@(posedge clk) disable iff (!rst_n)
($past(ever_exceeded_unserved, 1) |-> ever_exceeded_unserved);
endproperty
assert property (p_unserved_record_is_sticky);
// P11 -- FORBIDDEN. At most one layer handles a fault. §1: four
// verbs, and a fault has one origin, so two owners is a routing bug.
property p_at_most_one_handler;
@(posedge clk) disable iff (!rst_n)
$onehot0({handled_by_crc, handled_by_ondie_ecc,
prevented_by_rfm, handled_by_nobody});
endproperty
assert property (p_at_most_one_handler);
// P12 -- FORBIDDEN. CRC never informs the host when ALERT_n is not
// connected. §6: a detection nobody listens to is not a detection.
property p_crc_cannot_inform_without_alert;
@(posedge clk) disable iff (!rst_n)
(handled_by_crc && !alert_connected && !fault_cell_beyond)
|-> !host_informed;
endproperty
assert property (p_crc_cannot_inform_without_alert);
// P13 -- FORBIDDEN. On-die ECC never informs the host without a
// visibility path. §4's problem, as a property.
property p_ecc_cannot_inform_without_visibility;
@(posedge clk) disable iff (!rst_n)
(handled_by_ondie_ecc && !ecc_transparency_enabled && !scrub_enabled)
|-> (silently_corrected && !host_informed);
endproperty
assert property (p_ecc_cannot_inform_without_visibility);
// P14 -- the three visibility outcomes are mutually exclusive, and
// exactly one holds for a single fault. Merging "corrected but
// invisible" with "lost" would hide the distinction §15 exists for.
property p_visibility_is_a_partition;
@(posedge clk) disable iff (!rst_n)
($onehot({fault_link, fault_cell_bounded, fault_cell_beyond, fault_disturb})
|-> $onehot({host_informed, silently_corrected, silently_lost}));
endproperty
assert property (p_visibility_is_a_partition);
// P15 -- a fault beyond on-die ECC's bound always reaches the host.
// §2: "bounded fault" is a scope statement, and beyond the bound
// there is nothing to hide the failure.
property p_beyond_bound_always_visible;
@(posedge clk) disable iff (!rst_n)
(fault_cell_beyond && $onehot({fault_link, fault_cell_bounded,
fault_cell_beyond, fault_disturb}))
|-> host_informed;
endproperty
assert property (p_beyond_bound_always_visible);
// ── Cover.
// Every BRC level in force, at both scopes.
cover property (@(posedge clk) disable iff (!rst_n)
brc_valid && (brc_in_force == BRC_W'(2)));
cover property (@(posedge clk) disable iff (!rst_n)
brc_valid && (brc_in_force == BRC_W'(4)));
cover property (@(posedge clk) disable iff (!rst_n)
brc_valid && (t_rrf_ns == DUR_W'(TRRF_SB)));
cover property (@(posedge clk) disable iff (!rst_n)
brc_valid && (t_rrf_ns == DUR_W'(TRRF_AB)));
// Each forbidden condition ATTEMPTED -- vacuous otherwise.
cover property (@(posedge clk) disable iff (!rst_n) err_brc_unsupported);
cover property (@(posedge clk) disable iff (!rst_n) err_issue_without_config);
cover property (@(posedge clk) disable iff (!rst_n) err_issue_when_not_required);
// Both DRFM scopes discharging, and an activation coincident with one.
cover property (@(posedge clk) disable iff (!rst_n) drfm_valid && drfm_all_bank);
cover property (@(posedge clk) disable iff (!rst_n) drfm_valid && !drfm_all_bank);
cover property (@(posedge clk) disable iff (!rst_n) act_valid && drfm_valid);
// An obligation raised, and one left unserved.
cover property (@(posedge clk) disable iff (!rst_n) obligation_due);
cover property (@(posedge clk) disable iff (!rst_n) ever_exceeded_unserved);
cover property (@(posedge clk) disable iff (!rst_n) n_banks_due == BANKCNT_W'(BANKS));
// EVERY fault class routed, and every visibility outcome reached.
cover property (@(posedge clk) disable iff (!rst_n) handled_by_crc);
cover property (@(posedge clk) disable iff (!rst_n) handled_by_ondie_ecc);
cover property (@(posedge clk) disable iff (!rst_n) prevented_by_rfm);
cover property (@(posedge clk) disable iff (!rst_n) handled_by_nobody);
// THE TWO SILENT OUTCOMES, separately. §15: one is acceptable and
// one is data loss, and a suite that cannot tell them apart has not
// tested the distinction the block exists for.
cover property (@(posedge clk) disable iff (!rst_n) silently_corrected);
cover property (@(posedge clk) disable iff (!rst_n) silently_lost);
cover property (@(posedge clk) disable iff (!rst_n) err_crc_without_alert);
cover property (@(posedge clk) disable iff (!rst_n) err_ecc_without_visibility);17. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
| BRC 2, all-bank | 4 rows × 70 = 280 ns | §9's identity; P1, P2 |
| BRC 4, all-bank | 8 rows × 70 = 560 ns | Twice BRC2, exactly |
| BRC 4, same-bank | 8 × 60 = 480 ns | The uniform 14.3% scope saving |
| BRC of 1 or 5 requested | err_brc_unsupported; not clamped | P3 — clamping corrupts every budget |
TRRF_SB above TRRF_AB | $fatal at elaboration | Inverts the only saving the scope offers |
| Row-count and published forms disagree | $fatal at elaboration | §9's identity enforced in parameterisation |
| Density changed | Nothing changes | §8 — tRRF is identical at 8/16/24/32 Gb |
| DRFM before configuration | Reported; not accounted | P5 |
| DRFM while RFM not required | Reported; still accounted | Legal, but spends availability for no obligation |
| Activation coincident with a same-bank DRFM | Counted, then cleared | P7 — it cannot be un-happened |
| All-bank DRFM | Every bank's count cleared | P6 |
| Obligation with no threshold programmed | No obligation raised | P8 — an obligation against nothing is an opinion |
| Every bank over threshold | n_banks_due equals BANKS | P9, and the count-vs-index guard |
| Threshold crossed, no DRFM issued | ever_exceeded_unserved sticky | P10 — lateness survives a later discharge |
Link fault, CRC on, ALERT_n not connected | silently_lost; err_crc_without_alert | §6 — the check is performed and discarded |
| Bounded cell fault, no transparency or scrub | silently_corrected, not lost | §4 — correct outcome, blind host |
| Cell fault beyond the bound | Always host_informed | P15 — nothing left to hide it |
| Disturbance with RFM disabled | handled_by_nobody; silently_lost | No layer prevents what RFM was for |
| Two fault origins asserted | err_multiple_fault_origins | A fault has one origin |
| DFE enabled or disabled | No effect on routing | §11, §15 — it changes probability, not ownership |
Rows fifteen and sixteen are the pair this chapter exists to distinguish. Both are silent; one is a correct outcome and one is data loss, and a design that logs a single “silent event” counter cannot tell them apart.
18. DV
// Independent DRFM duration reference. Holds the published Table 5
// values as an explicit lookup rather than computing them, so
// agreement with the DUT's computed form is evidence that the
// equation and the table agree — which is §9's whole claim.
// SIMULATION-ONLY.
class drfm_duration_ref;
// Published tDRFM in ns, indexed [scope][brc]: scope 0 = ab, 1 = sb.
int published_ns [2][5];
function void build();
foreach (published_ns[s,b]) published_ns[s][b] = -1;
published_ns[0][2] = 280; published_ns[0][3] = 420; published_ns[0][4] = 560;
published_ns[1][2] = 240; published_ns[1][3] = 360; published_ns[1][4] = 480;
endfunction
function int lookup(bit same_bank, int brc);
if (brc < 2 || brc > 4) return -1;
return published_ns[same_bank][brc];
endfunction
// The row-count reading of §9, computed independently.
function int by_row_count(bit same_bank, int brc);
int per_row = same_bank ? 60 : 70;
return (2 * brc) * per_row;
endfunction
function bit forms_agree(bit same_bank, int brc);
return lookup(same_bank, brc) == by_row_count(same_bank, brc);
endfunction
endclass| Check | What it establishes |
|---|---|
forms_agree() for all six (scope, BRC) pairs | §9's identity, table against equation |
DUT t_drfm_ns against lookup() at all six | Three representations agree — table, equation, RTL |
| Sweep BRC 0…7; confirm only 2, 3, 4 enter force | P3, and the unsupported cover |
Set TRRF_SB above TRRF_AB | Elaboration fails rather than inverting the saving |
Narrow DUR_W until BRC4 overflows | Elaboration fails rather than wrapping |
| Issue DRFM before configuring | P5; cnt_drfm unchanged |
| Activate one bank to threshold, then all-bank DRFM | P6 — every count cleared |
| Activate and same-bank DRFM in the same cycle | P7 — the activation survives |
| Drive every bank over threshold | n_banks_due == BANKS; P9 |
| Cross the threshold and withhold DRFM | ever_exceeded_unserved; then discharge and confirm it stays |
| Route one fault of each of the four origins | All four handler covers |
Link fault with alert_connected low | silently_lost; P12 |
| Bounded cell fault with transparency and scrub both off | silently_corrected; P13 |
| The same fault with transparency on | host_informed instead |
| Cell fault beyond the bound, every layer disabled | Still host_informed; P15 |
| Assert two fault origins together | err_multiple_fault_origins |
Toggle dfe_enabled throughout | No routing output changes — §11 |
The twelfth and thirteenth checks together produce the report worth publishing, because they are two silent outcomes that a single counter would merge:
THE TWO SILENCES, WHICH ARE NOT THE SAME SILENCE
Same stimulus count, two configurations.
(A) bounded cell fault, on-die ECC ON, transparency OFF, scrub OFF
handled_by_ondie_ecc 1
host_informed 0
silently_corrected 1 <-- data is CORRECT
silently_lost 0
err_ecc_without_visibility asserted
the data was repaired. The host does not know a cell is
marginal, so it cannot predict the part's decline (§4) -- but
nothing was lost.
(B) link fault, CRC ON, ALERT_n NOT CONNECTED
handled_by_crc 1
host_informed 0
silently_corrected 0
silently_lost 1 <-- data is WRONG
err_crc_without_alert asserted
the device detected the corrupted write and pulsed a pin
nobody is listening to. 22.4 established CRC recovery is a
RETRY, and the retry lives in the controller -- so no retry
happens, and the corrupted write completes "successfully".
a design logging one "silent events" counter sees 2 and cannot
tell these apart. One is the acceptable cost of on-die ECC; the
other is undetected data corruption on a system that paid for
the check bits and the bandwidth to send them.
the fix : silently_corrected and silently_lost are SEPARATE
outputs and separate required covers (§16). And the two
configuration errors -- err_ecc_without_visibility and
err_crc_without_alert -- fire at BUILD-CONFIGURATION time
rather than waiting for a fault, which is the only point at
which either is cheap to fix.19. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Availability lower than budgeted | DRFM cost underestimated — §10 | ns_spent_in_drfm; BRC4 all-bank is 14.34% of a cool interval |
| Availability collapses when hot | The interval halved and DRFM did not — §10 | 28.67% of a hot interval at BRC4; the two multiply |
tDRFM budget wrong by a fixed factor | An unsupported BRC was clamped | P3; err_brc_unsupported should have fired |
| Budget assumed to scale with density | It does not — §8 | tRRF is 70/60 at every density |
| Same-bank DRFM no cheaper than expected | The saving is per-row and uniform — 14.3% | t_rrf_ns; 60 against 70, not a larger factor |
| RFM obligation never raised | No threshold programmed | err_no_threshold; P8 is correct behaviour |
| Obligation raised constantly | Threshold too low for the activation pattern | worst_bank, worst_count |
| Disturbance errors despite RFM enabled | Obligations raised and not discharged | ever_exceeded_unserved |
| Activation counts look low under load | Coincident-discharge ordering bug | P7; an activation must survive a same-cycle DRFM |
| Clean interface, then a sudden uncorrectable error | On-die ECC hid the decline — §4 | Enable transparency and scrub; the trend was never visible |
| Corrupted writes complete successfully | ALERT_n not connected — §6 | err_crc_without_alert; silently_lost |
| CRC enabled but no retries ever observed | Same cause | The retry lives in the controller, and it never heard |
| A “silent events” count that mixes good and bad | The two silences merged — §18 | silently_corrected against silently_lost |
| DFE misconfigured | Presents as CRC failures, not DFE errors | §11 — DFE has no failure output of its own |
Row ten is the signature 4.6 §3 warned about and this chapter quantifies the shape of: a clean interface is no longer evidence of a healthy part, and the transition from clean to uncorrectable can be abrupt because the gradual part happened where the host could not see it.
Row fourteen is the one that misdirects. DFE problems surface as somebody else's failures, because DFE has no error output — so a rising CRC rate is a reason to look at equalisation, not only at the link.
20. Misconceptions
“DDR5's integrity features add up to stronger protection.” §1. Four mechanisms with four different verbs — reshapes, detects, corrects, prevents — at four layers. They do not compose into one guarantee.
“On-die ECC protects the system's data.” §2, and 4.6 §3 calls the confusion a category error. It exists to make a denser array economically viable. System-level ECC is a separate mechanism, and 25.2 §8 verified its cost as four check bits per sub-channel.
“Bounded fault means strong correction.” §2. It is a scope statement — faults up to a bound — and this chapter does not know the code, the correction strength or the overhead, because none is published in the retrieved material.
“On-die ECC is purely an improvement.” §4. It improves the part and degrades the host's visibility into it, turning a gradual warning into an abrupt failure at the observation point.
“Transparency and scrub are optional extras.” §3. They are the answers to the problem correction creates, from opposite ends — one restores observability, the other limits accumulation.
“DDR5 CRC protects writes.” §5. Both directions — write CRC and read CRC are separate function-matrix rows. The asymmetry is in who learns.
“ALERT_n reports a state.” §6. It pulses — low for an interval, then high — so a host that polls can miss one entirely.
“CRC being supported means writes are protected.” §6's callout. Usage of ALERT_n is system-dependent, and recovery is a controller-issued retry. Unconnected, the check is performed and the result discarded.
“RFM is always required.” §7. It is conditional on MR58:OP[0] or an ARFM level — a register, not a part number.
“tDRFM is an opaque mode-dependent number.” §9. It is (2 × BRC) neighbour rows × tRRF per row, which reproduces all six published values and explains the factor of two as the two directions.
“A denser part pays more per DRFM.” §8. tRRF is identical at 8, 16, 24 and 32 Gb. The per-row cost does not scale with capacity.
“DRFM is a small overhead.” §10. A BRC4 all-bank DRFM is 28.67% of a hot refresh interval. Configuration and temperature multiply.
“DFE failures show up as DFE errors.” §11. DFE acts before sampling and has no error output. Its failures appear as CRC failures, and its successes are invisible.
“A silent event is a silent event.” §18. One is on-die ECC correcting with the visibility path off — data correct. The other is a detected error nobody heard — data wrong.
21. Interview Reasoning
Name DDR5's integrity mechanisms and what each does on an error. Decision feedback equalisation reshapes before sampling, so there is no error yet. CRC detects on the link and recovery is a retry. On-die ECC corrects inside the array. Refresh management prevents disturbance by refreshing neighbours.
Why don't they compose into one guarantee? Four different verbs at four layers. Prevention does not help an error that occurred; detection does not repair; correction hides what it repaired. Each covers a different fault class and a system needs all of them.
What does “on-die ECC, bounded fault” tell you? That it handles faults up to a bound and claims nothing beyond it. It is a scope statement, not a strength statement — and the code is not published on the part consulted here.
What is the downside of on-die ECC? It degrades the host's visibility. Correctable errors that used to appear at the interface as an early warning are now corrected internally, so the first host-visible symptom can be an uncorrectable error rather than a rising trend.
Derive tDRFM for BRC4, all-bank. The published equation is 2 × tRRF × BRC; regrouped, it is (2 × BRC) neighbour rows times tRRF per row. At BRC4 that is 8 rows × 70 ns = 560 ns, which matches the published table.
Why is there a factor of two in that equation? Neighbours come in pairs — ±1, ±2, and so on — so a BRC level of n covers 2n rows, one on each side at each distance.
How much does a DRFM cost in availability? Against a 3.906 µs refresh interval, a BRC4 all-bank DRFM at 560 ns is 14.34%. Above 85 °C the interval halves to 1.953 µs and the same command becomes 28.67%.
Does tRRF scale with device density? No — 70 ns all-bank and 60 ns same-bank at 8, 16, 24 and 32 Gb on the part consulted. The per-row cost is density-independent.
Your system has write CRC enabled and ALERT_n unconnected. What have you got? The check bits' bandwidth cost and no protection. The device detects the corrupted write and pulses a pin nobody reads; the retry lives in the controller, so the corrupted write completes and reports success.
You see a rising CRC error rate. Where do you look? At the link and at equalisation. DFE has no error output of its own, so a DFE problem presents as somebody else's CRC failures.
You log one “silent error” counter and it is non-zero. Is that acceptable? Unanswerable as logged. On-die ECC correcting without a visibility path is a correct outcome with a blind host; a detected link error nobody heard is data corruption. They must be counted separately.
22. Exercises
-
Verify §9's identity for a hypothetical BRC5, then explain why §13's block refuses to elaborate for it and what would have to be retrieved to support it.
-
Using §10's figures, compute the fraction of availability consumed if a controller must issue one BRC4 all-bank DRFM every four refresh intervals, at both temperatures. State the assumption you must add.
-
§8 shows
tRRFis density-independent while the number of rows grows with density. Derive what that implies about total DRFM cost per unit capacity, and say whether it favours denser or sparser parts. -
Remove the elaboration loop from §13 and parameterise
TRRF_SBaboveTRRF_AB. Which properties fire, and which do not? Explain why P1 still passes. -
§14's tracker counts an activation coincident with a DRFM. Construct the opposite ordering, then find the access pattern that loses the most activations under it and say why it is the worst case.
-
§15's arbiter treats
alert_connectedas an input. Argue whether a controller should refuse to enable write CRC whenALERT_nis unconnected, using §6's callout and 22.4's retry model. -
Construct the fault sequence that produces
silently_correctedandsilently_lostin the same run, and specify the minimum logging a system needs to distinguish them after the fact. -
§4 says on-die ECC turns a gradual failure into an abrupt one at the host. Design the smallest use of transparency and scrub that restores a usable early-warning signal, and state what it costs in bandwidth and in time.
23. Where the Module Ends
DDR5's architecture is now built across four chapters, and it is worth stating what they together establish.
Chapter 25.1 closed a retrieval that 16.3 §4 had recorded as the seventh DDR5 question this curriculum could not answer — eight bank groups of four banks at x4 and x8, four groups of four at x16, with page sizes of 1, 1 and 2 KB that multiply back to exactly 16 Gb. 25.2 paid the debt three chapters left — a fourteen-bit command bus with no strobes at all, commands over one or two cycles, CS_n inside the command code, and two sub-channels each carrying an independent copy. 25.3 built the module's power — one 5 V bulk input becoming five generated supplies, with a power-good signal that is a six-term conjunction. And this chapter built the integrity layers and found that they do not compose.
Four results from this chapter carry beyond it.
A published equation can explain itself when regrouped. tDRFM = (2 × tRRF) × BRC reproduces all six of its own table's values, and rewriting it as (2 × BRC) rows times tRRF per row turns two opaque factors into a row count and a per-row cost — which then predicts the linear scaling and the uniform same-bank saving that the printed form hides.
DRFM is not a small overhead. Up to 28.67% of a hot refresh interval for one command, with configuration and temperature multiplying rather than adding.
On-die ECC improves the part and blinds the host, which makes transparency and scrub load-bearing rather than optional — and makes a clean interface weak evidence of a healthy part.
And a detection mechanism with no path to a listener is not a detection mechanism. ALERT_n's usage being “system-dependent” means write CRC's value is a system property, and an unconnected pin converts the whole mechanism into pure overhead.
Three things this module verified and could not finish, recorded rather than glossed.
The core datasheet never retrieved. Every chapter here is built on a die-revision addendum that explicitly supersedes parts of a document this module could not obtain, plus a module-level specification. So no DDR5 command truth table and no nCCD, nRRD or nFAW value appears anywhere in Module 25 — the timing questions 16.3 §4 opened are still open, and 25.1 §1's callout says so explicitly.
Two named features are not characterised. Chapter 25.2 §6 could not define 2N mode, which the addendum lists as JEDEC-mandatory without describing, and this chapter could not characterise on-die ECC's code. In both cases the reasonable expectation is stated and labelled as an expectation rather than a citation.
And the regulator efficiency that would make 25.3 §2's transport saving a net figure is not published, so local regulation's total benefit remains uncomputed while its transport component is quantified at roughly 20×.
What the four chapters have in common is that their most useful findings contradicted a reasonable expectation. A x16 part is not a wider x8 but a different organisation with half the banks and twice the page. A x32 interface's two sub-channels hold exactly the same 32,768 bytes open as one wide channel would. A conservative power-good implementation that ANDs every rail never lets the module come up. And four integrity mechanisms, all present and all working, can still leave a corrupted write completing successfully.
None of those came from reasoning about how DDR5 ought to work. Each came from multiplying published numbers together until the structure was visible, and then checking the result against a second published statement — which is the only method these two modules have used.
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
DDR4
DDR4 is the generation where prefetch depth stops changing. With granularity already at a cache line, the rate had to come from overlapping independent accesses instead — which is what bank groups are, and why peak bandwidth became conditional on the access pattern.
- Related topic
Bank Groups
Not all bank pairs are equally independent. A bank group is the scope at which the internal column data path is shared, and the three-way classification of a request against its predecessor is the interface every later timing module consumes.
- Related topic
CKE — Clock Enable
CKE decides whether a device samples commands at all. It qualifies using its previous value rather than its current one — and in DDR5 the function survives while the dedicated pin does not.
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.
