Skip to content
VLSI Mentor

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:

MechanismWhere it actsWhat it does on an error
Decision feedback equalisationthe receiver, before samplingreshapes — there is no “error” yet
Write CRC / Read CRCthe link, per burstdetects; asserts ALERT_n
On-die ECCinside the arraycorrects, internally
Directed refresh managementthe array, over timeprevents, 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 ECCWith on-die ECC
Early marginal cellsvisible as correctable errors at the interfacecorrected internally; interface clean
Warning before failurea rising rate over timelittle or none
First host-visible symptoma gradual trendan 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 CRCRead CRC
Who computesthe controllerthe device
Who checksthe devicethe controller
Who is toldthe controller, via ALERT_nthe controller, directly
Recoveryretry the writeretry 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:

FieldRole
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 refreshedtDRFMabtDRFMsb
BRC 2Always ±1, Ratio ±2280 ns240 ns
BRC 3Always ±1, Ratio ±2, ±3420 ns360 ns
BRC 4Always ±1, Ratio ±2, ±3, ±4560 ns480 ns

And Table 4 — tRRF by device density, the per-row refresh duration:

Parameter8 Gb16 Gb24 Gb32 Gb
tRRFab (MIN)70707070
tRRFsb (MIN)60606060

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:

BRCScope2 × tRRF × BRCPublished
2ab2 × 70 × 2 = 280280 nsmatch
3ab2 × 70 × 3 = 420420 nsmatch
4ab2 × 70 × 4 = 560560 nsmatch
2sb2 × 60 × 2 = 240240 nsmatch
3sb2 × 60 × 3 = 360360 nsmatch
4sb2 × 60 × 4 = 480480 nsmatch

All six, exactly. Now the useful part — regrouping the equation explains the factor of two, which the specification states without interpreting:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   tDRFM  =  (2 x tRRF) x BRC
          =  (2 x BRC)  x tRRF
             \________/    \__/
              rows          per-row
              refreshed     duration

2 × 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:

BRCNeighbour rows× tRRFab=
2±1, ±2 → 4 rows4 × 70280
3±1, ±2, ±3 → 6 rows6 × 70420
4±1, ±2, ±3, ±4 → 8 rows8 × 70560

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:

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

Now DRFM as a fraction of that interval. DERIVED, every cell recomputed:

BRC / scopetDRFM% of a 3.906 µs interval% of a 1.953 µs interval
BRC2 same-bank240 ns6.14%12.29%
BRC2 all-bank280 ns7.17%14.34%
BRC3 all-bank420 ns10.75%21.50%
BRC4 same-bank480 ns12.29%24.58%
BRC4 all-bank560 ns14.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

Four DDR5 integrity mechanisms at four layers, in four rows. The top row holds the four things the mechanisms act on: the electrical link carrying data and strobes, the cell array itself, the pattern of row activations, and the device temperature. The second row holds the four mechanisms: decision feedback equalisation which reshapes the signal at the receiver before sampling, cyclic redundancy check which detects errors on the link in both directions, on-die error correction which repairs faults inside the array, and directed refresh management which refreshes a row's neighbours to prevent disturbance. The third row holds the three things a host can observe: the alert pin pulsed by a CRC failure, the error scrub and transparency path that makes internal correction visible, and the refresh management obligation the controller must track and discharge. The bottom row is the host's total view, which receives only those three. Edges run from the link into equalisation and from equalisation into CRC, from the array into on-die correction and from there into scrub, from activations and temperature into refresh management and from there into the obligation, from CRC into the alert pin, and from all three observable paths into the host view.The linkDQ / DQS, per burstThe cell arraymarginal cells — 2.1, 2.2Activationsthe disturbance driver —§7Temperaturehalves the interval — §10DFEreshapes; no error yet —§11CRC, both waysdetects; retry — §5, 22.4On-die ECCcorrects, internally — §2DRFMprevents, by refreshing —§7ALERT_na pulse, system-dependent— §6Scrub, transparencyrestores visibility — §3,§4RFM obligationthe controller's to track— §14Host's total viewonly these three — §412

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

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

The 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

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

An 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

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

dfe_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

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

CaseBehaviourWhy
BRC 2, all-bank4 rows × 70 = 280 ns§9's identity; P1, P2
BRC 4, all-bank8 rows × 70 = 560 nsTwice BRC2, exactly
BRC 4, same-bank8 × 60 = 480 nsThe uniform 14.3% scope saving
BRC of 1 or 5 requestederr_brc_unsupported; not clampedP3 — clamping corrupts every budget
TRRF_SB above TRRF_AB$fatal at elaborationInverts the only saving the scope offers
Row-count and published forms disagree$fatal at elaboration§9's identity enforced in parameterisation
Density changedNothing changes§8 — tRRF is identical at 8/16/24/32 Gb
DRFM before configurationReported; not accountedP5
DRFM while RFM not requiredReported; still accountedLegal, but spends availability for no obligation
Activation coincident with a same-bank DRFMCounted, then clearedP7 — it cannot be un-happened
All-bank DRFMEvery bank's count clearedP6
Obligation with no threshold programmedNo obligation raisedP8 — an obligation against nothing is an opinion
Every bank over thresholdn_banks_due equals BANKSP9, and the count-vs-index guard
Threshold crossed, no DRFM issuedever_exceeded_unserved stickyP10 — lateness survives a later discharge
Link fault, CRC on, ALERT_n not connectedsilently_lost; err_crc_without_alert§6 — the check is performed and discarded
Bounded cell fault, no transparency or scrubsilently_corrected, not lost§4 — correct outcome, blind host
Cell fault beyond the boundAlways host_informedP15 — nothing left to hide it
Disturbance with RFM disabledhandled_by_nobody; silently_lostNo layer prevents what RFM was for
Two fault origins assertederr_multiple_fault_originsA fault has one origin
DFE enabled or disabledNo 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
CheckWhat it establishes
forms_agree() for all six (scope, BRC) pairs§9's identity, table against equation
DUT t_drfm_ns against lookup() at all sixThree representations agree — table, equation, RTL
Sweep BRC 0…7; confirm only 2, 3, 4 enter forceP3, and the unsupported cover
Set TRRF_SB above TRRF_ABElaboration fails rather than inverting the saving
Narrow DUR_W until BRC4 overflowsElaboration fails rather than wrapping
Issue DRFM before configuringP5; cnt_drfm unchanged
Activate one bank to threshold, then all-bank DRFMP6 — every count cleared
Activate and same-bank DRFM in the same cycleP7 — the activation survives
Drive every bank over thresholdn_banks_due == BANKS; P9
Cross the threshold and withhold DRFMever_exceeded_unserved; then discharge and confirm it stays
Route one fault of each of the four originsAll four handler covers
Link fault with alert_connected lowsilently_lost; P12
Bounded cell fault with transparency and scrub both offsilently_corrected; P13
The same fault with transparency onhost_informed instead
Cell fault beyond the bound, every layer disabledStill host_informed; P15
Assert two fault origins togethererr_multiple_fault_origins
Toggle dfe_enabled throughoutNo 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:

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

SymptomLikely causeHow to confirm
Availability lower than budgetedDRFM cost underestimated — §10ns_spent_in_drfm; BRC4 all-bank is 14.34% of a cool interval
Availability collapses when hotThe interval halved and DRFM did not — §1028.67% of a hot interval at BRC4; the two multiply
tDRFM budget wrong by a fixed factorAn unsupported BRC was clampedP3; err_brc_unsupported should have fired
Budget assumed to scale with densityIt does not — §8tRRF is 70/60 at every density
Same-bank DRFM no cheaper than expectedThe saving is per-row and uniform — 14.3%t_rrf_ns; 60 against 70, not a larger factor
RFM obligation never raisedNo threshold programmederr_no_threshold; P8 is correct behaviour
Obligation raised constantlyThreshold too low for the activation patternworst_bank, worst_count
Disturbance errors despite RFM enabledObligations raised and not dischargedever_exceeded_unserved
Activation counts look low under loadCoincident-discharge ordering bugP7; an activation must survive a same-cycle DRFM
Clean interface, then a sudden uncorrectable errorOn-die ECC hid the decline — §4Enable transparency and scrub; the trend was never visible
Corrupted writes complete successfullyALERT_n not connected — §6err_crc_without_alert; silently_lost
CRC enabled but no retries ever observedSame causeThe retry lives in the controller, and it never heard
A “silent events” count that mixes good and badThe two silences merged — §18silently_corrected against silently_lost
DFE misconfiguredPresents 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

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

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

  3. §8 shows tRRF is 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.

  4. Remove the elaboration loop from §13 and parameterise TRRF_SB above TRRF_AB. Which properties fire, and which do not? Explain why P1 still passes.

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

  6. §15's arbiter treats alert_connected as an input. Argue whether a controller should refuse to enable write CRC when ALERT_n is unconnected, using §6's callout and 22.4's retry model.

  7. Construct the fault sequence that produces silently_corrected and silently_lost in the same run, and specify the minimum logging a system needs to distinguish them after the fact.

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

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.