DDR · Module 25
On-DIMM PMIC
One 5 V bulk input becomes three device rails and two low-dropout outputs, and the single power-good signal is a conjunction of six conditions — so it fires six times more often than any rail fails.
A memory module used to be a passive carrier of devices. Power arrived from the board, already regulated, and the module's job was to distribute it. A DDR5 module is not that.
It takes a single bulk supply and makes its own. Chapter 4.6 §3 establishes why — “regulating locally means the long path carries a higher voltage at a lower current,” and “it also makes each module responsible for its own supply quality” — and this chapter is what that relocation actually consists of, read from a named module specification.
The result includes one detail worth the chapter on its own. The module's single power-good signal is the conjunction of six independent conditions, which makes it both the most useful signal on the module and the least specific. §12 is about that trade.
1. One Input, Five Outputs
DEVICE-VERIFIED, from the Micron 262-pin DDR5 SODIMM core specification's operating-conditions table:
| Voltage | Role | |
|---|---|---|
VIN_Bulk | 5 V | bulk input DC supply from the system |
VDD | 1.1 V | supply voltage from the PMIC |
VDDQ | 1.1 V | I/O supply voltage from the PMIC |
VPP | 1.8 V | pump voltage from the PMIC |
| 1.8 V LDO output | 1.8 V | from the PMIC to the hub |
| 1.0 V LDO output | 1.0 V | a second low-dropout output |
The die addendum agrees on the device side: VDD = VDDQ = 1.1 V (NOM) and VPP = 1.8 V (NOM). Two documents, one device-facing answer.
So the connector carries one regulated supply and the module makes five. That is the relocation in a sentence, and three details in the table are worth drawing out.
VPP is a pump voltage. It is the high rail a DRAM array needs to drive a wordline hard enough to move charge — the requirement 2.1 establishes is physical rather than a design choice. At 1.8 V it is the highest device rail and it is not negotiable downward.
Two of the five outputs are low-dropout regulators, not buck converters, and one of them is explicitly “from the PMIC to the hub.” So the module's own telemetry chip has a supply the DRAM devices do not share — §7 is about why that matters.
And VDD and VDDQ are the same voltage from separate outputs. Both 1.1 V, both from the PMIC, listed separately. Chapter 24.5 §1 works through why rails at the same voltage are kept separate for noise isolation rather than for their values — a different standard, the same electrical reasoning.
2. Why Regulating Locally Wins
Chapter 4.6 §3 names the pressure: “the resistive loss and noise on that path scale with the current and worsen as the supply voltage falls.” Two verified voltages turn that into arithmetic.
The model, stated: to deliver a given power P at voltage V, the current is P / V, and resistive loss in the delivery path is I² R. Halving the voltage doubles the current and quadruples the loss.
DERIVED, recomputed for each rail against the 5 V bulk input:
| Rail | Voltage | Current ratio vs 5 V | Loss ratio | Reduction |
|---|---|---|---|---|
VDD / VDDQ | 1.1 V | 0.2200 | 0.04840 | 95.16% (20.66×) |
VPP | 1.8 V | 0.3600 | 0.12960 | 87.04% (7.72×) |
| 1.0 V LDO | 1.0 V | 0.2000 | 0.04000 | 96.00% (25.00×) |
Delivering the same power at 5 V instead of 1.1 V costs about a twentieth of the resistive loss. That is the whole argument, and it is a large factor rather than a marginal one.
Two things the table shows that the prose version does not.
The benefit is largest for the lowest rail. The 1.0 V output gains 96% and VPP at 1.8 V gains 87% — still substantial, but less. So the case for local regulation strengthens as rails drop, which is why this became standard at DDR5's voltages rather than earlier.
And the connector count collapses. Five regulated supplies would each need their own path across the connector with its own loss and its own noise; one bulk input needs one. DERIVED: five regulated supplies over the connector become one.
3. The Bulk Input Is 5 V on This Module, and Not Universally
A caution that matters because the number is easy to over-generalise.
DEVICE-VERIFIED: VIN_Bulk is 5 V on the named 262-pin SODIMM. The specification's own note adds that “designers must account for any system voltage drops at anticipated power levels to ensure the required VIN supply voltage is maintained” — so the 5 V is a requirement at the module's pins, not a nominal to be approached.
This chapter does not claim 5 V for every DDR5 module form. SODIMMs, UDIMMs and registered DIMMs are different products with different power budgets, and the specification consulted covers one of them. A widely repeated figure for server DIMMs is 12 V; the document retrieved here does not state it, and 24.1 §8's rule — verify or qualify, never inherit — applies to a bulk-supply voltage exactly as it applies to a timing parameter.
So every figure in §2 is computed against a verified 5 V. Recomputing against a different bulk voltage changes the ratios, and §14's block takes the input voltage as a parameter for that reason rather than hard-coding one.
4. PWR_GOOD Is a Conjunction of Six Things
Here is the signal that makes the module's power architecture legible, and the datasheet's description of it is unusually precise.
DEVICE-VERIFIED, from the SODIMM specification's pin description for PWR_GOOD — an open-drain, input/output pin:
The PMIC ensures this pin HIGH when the VIN_Bulk input supply, as well as all enabled output buck regulators and all LDO regulators, maintain the tolerance threshold as configured in the appropriate register.
The PMIC drives this pin LOW when VIN_Bulk goes below the threshold, or when any enabled output buck regulator exceeds the thresholds configured in the appropriate register, or when any LDO output regulator exceeds its configured threshold.
Read that as logic and it is an AND of every rail:
PWR_GOOD = VIN_Bulk in tolerance
AND every ENABLED buck in tolerance
AND every LDO in toleranceDERIVED, counting the terms from §1's rail list: one bulk input, three buck outputs (VDD, VDDQ, VPP) and two LDO outputs — six conditions, all of which must hold.
Three consequences, and the third is a genuine hazard.
One signal reports the health of the entire module supply. That is the point, and it is why a host can gate memory access on a single pin rather than monitoring six rails.
“Enabled” is doing real work in that sentence. A disabled buck regulator is not a term in the conjunction. So the expression's arity depends on configuration, and a rail that is off does not hold the signal low — which is correct behaviour and a thing a monitor must know.
And the conjunction makes the signal fire more often than any rail fails. §12 quantifies that.
5. The Thresholds Are Programmable, Which Changes What the Signal Means
The phrase “as configured in the appropriate register” appears twice in the verified description and it is not boilerplate.
The tolerance thresholds are register values. So PWR_GOOD does not report a fixed electrical condition — it reports whether each rail is inside a window the system programmed.
Two implications.
The same module can be strict or permissive, and the signal's meaning changes accordingly. A tight threshold produces a signal that deasserts on excursions a loose threshold would ignore. Neither is wrong, and a system comparing PWR_GOOD behaviour across two modules must know both were configured the same way before drawing a conclusion.
And the thresholds are configured through the same register interface the module exposes for everything else. Which means there is an ordering question: until the thresholds are programmed, PWR_GOOD reflects defaults rather than the system's intent. §14's block tracks configuration state for that reason, and §16's corner cases include the window before it.
6. The Hub Has a Supply the DRAM Does Not Share
§1 noted that one LDO output is specified as going “from the PMIC to the HUB.” That is a small line with a real consequence.
The module's telemetry chip is powered separately from the DRAM devices. So the hub can be alive when the devices are not — which is what makes a module interrogable before its memory is usable.
DEVICE-VERIFIED: the specification lists a serial presence detect hub with temperature sensor, 1024 byte, with its own operating-conditions section separate from both the PMIC's and the DRAM's.
That separation is the architectural point of the hub. A host needs to know what a module is — its capacity, its organisation, its timing capabilities — before it can configure and use it. A telemetry device sharing the DRAM's supply and configuration state could not answer that question at the time the question needs answering.
7. A Presence-Detect Hub With a Thermometer
DEVICE-VERIFIED, from the specification's own section list: SPD EEPROM Hub Operation, Integrated Thermal Sensor Operations, SPD Data, and SPD EEPROM Hub and Integrated Thermal Sensor Operating Conditions. The hub holds 1024 bytes and the device's operating temperature range is TOPER = 0 to 95 °C.
So the hub does two jobs that are not obviously related.
It describes the module. A 1024-byte store the host reads to learn the module's configuration. Chapter 5.6 owns what a module is; this is how a module says so.
And it measures the module's temperature, with the sensor integrated into the same part.
The pairing is not arbitrary. Both are things the host needs to know about the module as a unit rather than about any device on it, and both must be readable independently of whether the memory is working. A thermal sensor that could only be read through the memory interface would be unavailable in exactly the situation — an overheating module — where it matters most.
And it completes the localisation 4.6 §3 describes. That chapter says the relocation “makes each module responsible for its own supply quality.” A module that regulates its own power, reports its own power health on one pin, describes its own configuration and measures its own temperature is responsible for itself in four distinct senses — and §9 is about what that costs the host.
8. Localisation Cuts Both Ways
Chapter 4.6 §3 frames the relocation as “another instance of a cost being localised rather than shared,” and presents that as a benefit. It is, and it is also a new failure surface worth naming.
| Board-regulated (before) | Module-regulated (DDR5) | |
|---|---|---|
| Regulators to design | one set, on the board | one set per module |
| A regulator fault affects | every module | one module |
| Supply quality is set by | the board designer | the module vendor |
| Diagnosing a supply fault | one place to look | per-module, via PWR_GOOD |
| Conversion loss sits | on the board | on the module, next to the DRAM |
The second row is the benefit and the fifth row is the cost, and they are the same change. A fault confined to one module is a better fault; heat generated on the module is worse heat, because it is next to devices whose retention degrades with temperature — 2.7 owns that relationship.
And the third row is the one that changes how a system is validated. When the board regulated everything, a system integrator could characterise the supply once. With per-module regulation, supply quality is a property of each module the system might be built with — so it becomes a qualification question rather than a design question, and PWR_GOOD plus the hub's telemetry are the only visibility the host gets into it.
9. The Module Power Architecture
Two things the structure makes visible.
Everything below the top row is generated on the module. One arrow crosses the connector; four regulated supplies come out of it. That is §2's transport argument drawn — the long path carries the single high-voltage input and nothing else.
And the bottom row is narrow on purpose. The host sees one pin and 1024 bytes. Six rails, their thresholds, the regulators' behaviour and the conversion loss are all above a boundary the host cannot cross. §8's validation problem is that narrowness — the module is responsible for its own supply quality precisely because the host has no way to inspect it.
10. The Power-Good Aggregator
// ---------------------------------------------------------------------
// pwr_good_aggregator -- PWR_GOOD as the DEVICE-VERIFIED conjunction:
// the bulk input AND every ENABLED buck AND every LDO, each against a
// threshold held in a register.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT DOES NOT MODEL:
// - any regulator, voltage, slew rate or settling time
// - the conversion loss §2's callout names, which is real and not
// quantified anywhere in the consulted specification
// - SEQUENCING. 24.5 §10 owns rail order and the reverse-shutdown
// hazard, for a different standard. §5's callout: ordering is in
// time, aggregation is over a set, and this block does the latter
// only. There is deliberately no order state here.
//
// WHY "ENABLED" MATTERS (§4): a DISABLED buck is not a term in the
// conjunction. So the expression's arity is a runtime value, which is
// the same shape as 24.4's runtime modulus and has the same hazard --
// a fixed-arity implementation is wrong whenever a rail is off.
// ---------------------------------------------------------------------
module pwr_good_aggregator #(
parameter int N_BUCK = 3, // VDD, VDDQ, VPP (§1)
parameter int N_LDO = 2, // 1.8 V to hub, 1.0 V (§1)
parameter int N_RAILS = N_BUCK + N_LDO,
// Terms in the conjunction: the rails plus VIN_Bulk (§4).
parameter int N_TERMS = N_RAILS + 1,
parameter int MARG_W = 12,
// A COUNT of terms up to N_TERMS needs clog2(N_TERMS+1).
parameter int TERMCNT_W = $clog2(N_TERMS + 1),
parameter int CNT_W = 24
) (
input logic clk,
input logic rst_n,
// ── Bulk input status. Not a rail the PMIC makes; a precondition.
input logic vin_in_tolerance,
// ── Per-rail: enabled, in tolerance, and an abstract margin.
input logic [N_RAILS-1:0] rail_enabled,
input logic [N_RAILS-1:0] rail_in_tolerance,
input logic [MARG_W-1:0] rail_margin [N_RAILS],
// ── Thresholds are REGISTER values (§5), so configuration is a
// state this block must know about rather than assume.
input logic thresholds_programmed,
// ── The signal. Open-drain in the real part; modelled as an active
// HIGH "good" plus the drive-low condition it implies.
output logic pwr_good,
output logic driving_low,
// ── Which term broke it. The whole diagnostic value of the block.
output logic fail_vin,
output logic [N_RAILS-1:0] fail_rail,
output logic [TERMCNT_W-1:0] n_terms_active,
output logic [TERMCNT_W-1:0] n_terms_failing,
// ── Worst margin across ENABLED rails only. A disabled rail has no
// margin to report and must not drag the minimum down.
output logic [MARG_W-1:0] worst_margin,
output logic worst_margin_valid,
// ── History, because an open-drain pulse can be missed.
output logic ever_deasserted,
output logic [CNT_W-1:0] cnt_deassertions,
output logic [CNT_W-1:0] cy_deasserted,
output logic err_good_before_programmed,
output logic err_tolerance_without_enable,
output logic any_error
);
initial begin
if (N_BUCK < 1 || N_LDO < 1)
$fatal(1, "pwr_good_aggregator: need at least one buck and one LDO");
if (N_TERMS != N_RAILS + 1)
// §4: VIN_Bulk is a term. Omitting it would make the signal
// report rail health while the input was already gone.
$fatal(1, "pwr_good_aggregator: N_TERMS must include VIN_Bulk");
if (TERMCNT_W <= $clog2(N_TERMS) && N_TERMS > 1)
// A COUNT up to N_TERMS needs one more bit than an INDEX into
// it when N_TERMS is a power of two. Getting this wrong makes
// n_terms_active unable to represent "all of them".
$fatal(1, "pwr_good_aggregator: TERMCNT_W too narrow to count N_TERMS");
if (MARG_W < 2)
$fatal(1, "pwr_good_aggregator: MARG_W must be at least 2");
end
logic good_q, ever_bad, e_early, e_tol;
logic [CNT_W-1:0] n_deassert, cy_bad;
// ── The conjunction. Declared above the continuous assigns that
// read them. A disabled rail contributes NOTHING -- neither a
// pass nor a fail (§4).
logic [N_RAILS-1:0] rail_bad;
assign rail_bad = rail_enabled & ~rail_in_tolerance;
logic all_rails_ok;
assign all_rails_ok = (rail_bad == '0);
logic good_now;
assign good_now = vin_in_tolerance && all_rails_ok;
logic [TERMCNT_W-1:0] n_active, n_failing;
always_comb begin
n_active = TERMCNT_W'(1); // VIN_Bulk always counts
n_failing = vin_in_tolerance ? TERMCNT_W'(0) : TERMCNT_W'(1);
for (int i = 0; i < N_RAILS; i++) begin
if (rail_enabled[i]) begin
n_active = n_active + TERMCNT_W'(1);
if (!rail_in_tolerance[i]) n_failing = n_failing + TERMCNT_W'(1);
end
end
end
// Worst margin over ENABLED rails only.
logic [MARG_W-1:0] wm;
logic wm_v;
always_comb begin
wm = {MARG_W{1'b1}};
wm_v = 1'b0;
for (int i = 0; i < N_RAILS; i++)
if (rail_enabled[i]) begin
wm_v = 1'b1;
if (rail_margin[i] < wm) wm = rail_margin[i];
end
if (!wm_v) wm = '0;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
good_q <= 1'b0; // safe state: NOT good until proven
ever_bad <= 1'b0; n_deassert <= '0; cy_bad <= '0;
e_early <= 1'b0; e_tol <= 1'b0;
end else begin
e_early <= 1'b0; e_tol <= 1'b0;
// §5: until the thresholds are programmed, "in tolerance"
// means "inside a default window", not "inside the system's
// intent". Asserting good then is reported.
if (good_now && !thresholds_programmed) e_early <= 1'b1;
// A rail reporting in-tolerance while disabled is a status
// inconsistency: a rail that is off has no tolerance to be in.
if ((rail_in_tolerance & ~rail_enabled) != '0) e_tol <= 1'b1;
if (good_q && !good_now) begin
ever_bad <= 1'b1;
if (n_deassert != {CNT_W{1'b1}}) n_deassert <= n_deassert + CNT_W'(1);
end
if (!good_now && (cy_bad != {CNT_W{1'b1}})) cy_bad <= cy_bad + CNT_W'(1);
good_q <= good_now;
end
end
assign pwr_good = good_q && good_now;
// Open-drain: the part DRIVES low and releases high, so the drive
// condition is the one with electrical meaning.
assign driving_low = !good_now;
assign fail_vin = !vin_in_tolerance;
assign fail_rail = rail_bad;
assign n_terms_active = n_active;
assign n_terms_failing = n_failing;
assign worst_margin = wm;
assign worst_margin_valid = wm_v;
assign ever_deasserted = ever_bad;
assign cnt_deassertions = n_deassert;
assign cy_deasserted = cy_bad;
assign err_good_before_programmed = e_early;
assign err_tolerance_without_enable = e_tol;
assign any_error = e_early | e_tol;
endmoduleThree decisions in that block are §4's verified description rather than implementation taste.
A disabled rail contributes neither a pass nor a fail. rail_bad is enabled & ~in_tolerance, so a rail that is off cannot hold the signal low — which is what “all enabled output buck regulators” requires. A fixed-arity implementation that ANDed all five rails unconditionally would hold PWR_GOOD low forever on any module with a rail disabled.
driving_low is a separate output from pwr_good. The real pin is open-drain: the part drives low and releases high, and on a shared net the release does not guarantee a high. Publishing the drive condition separately is what lets a consumer model the net correctly rather than assuming a push-pull output.
And good_q comes out of reset low. A power-good signal that asserted before anything was verified would be exactly backwards — the safe direction is to withhold the assertion, and pwr_good requires both the registered and the current condition so a single good cycle cannot produce a glitch of assertion.
11. The Aggregation Hazard
§4's third consequence, quantified. A conjunction of six terms deasserts more often than any single term fails, and the factor is worth computing because it changes how the signal should be read.
DERIVED under a stated model — each term independently in tolerance with probability p:
Per-term good probability p | p⁶ (all six) | Aggregate failure | Versus one term |
|---|---|---|---|
| 0.99 | 0.941480 | 0.058520 | 5.9× |
| 0.999 | 0.994015 | 0.005985 | 6.0× |
| 0.9999 | 0.999400 | 0.000600 | 6.0× |
The aggregate signal is about six times as likely to deassert as any one rail is to fail, which is simply what a six-way AND does.
Two readings, and they pull in opposite directions.
As a gate, that is correct and desirable. If any rail is out of tolerance the memory should not be used, and a signal that catches all six is exactly right.
As a diagnostic, it is the least specific signal on the module. PWR_GOOD went low tells you one of six things happened and does not tell you which. So a design that logs only PWR_GOOD has recorded that something failed and discarded which.
12. The Transport Budget
// ---------------------------------------------------------------------
// module_transport_budget -- compares resistive transport loss for
// delivering a given power at the bulk input voltage versus at a
// rail voltage. §2's arithmetic, in abstract units.
//
// CLASSIFICATION: educational, synthesisable.
//
// ALL UNITS ARE ABSTRACT. No volts, amps, watts or ohms. Voltages are
// integer LEVELS and the result is a ratio of squares, which is the
// SHAPE of the I^2 R argument rather than a measurement.
//
// WHAT IT DOES NOT MODEL:
// - the regulator's own efficiency. §2's callout: converting on the
// module dissipates real power ON the module, and no efficiency
// figure appears in the consulted specification. This block
// compares TRANSPORT only and the omission is deliberate.
// - noise, which 4.6 §3 names alongside loss and which does not
// reduce to an I^2 R ratio at all
// - any thermal consequence (§8), which is where the conversion
// loss actually lands
//
// NO DIVISION: the comparison is published as a numerator and
// denominator pair, so the caller states its own denominator -- the
// discipline every accountant in Modules 24 and 25 follows.
// ---------------------------------------------------------------------
module module_transport_budget #(
// Abstract voltage levels, scaled by the caller. The SODIMM's
// verified 5 V / 1.1 V is 50 / 11 at a scale of 10 (§1, §3).
parameter int V_SCALE = 10,
parameter int VIN_LEVEL = 50,
parameter int N_RAILS = 5,
parameter int LVL_W = 10,
parameter int RATIO_W = 2 * LVL_W + 2,
parameter int IDX_W = (N_RAILS <= 1) ? 1 : $clog2(N_RAILS)
) (
input logic clk,
input logic rst_n,
input logic q_valid,
input logic [IDX_W-1:0] q_rail,
input logic [LVL_W-1:0] rail_level [N_RAILS],
// ── The comparison, as a PAIR. loss(at rail V) / loss(at VIN) =
// (VIN/Vrail)^2, published as numerator over denominator.
output logic [RATIO_W-1:0] loss_num, // VIN^2
output logic [RATIO_W-1:0] loss_den, // Vrail^2
output logic q_out_valid,
// Which rail benefits most: the LOWEST voltage, since the ratio
// grows as the rail falls (§2).
output logic [IDX_W-1:0] best_rail,
output logic [LVL_W-1:0] lowest_level,
// §2: the connector carries this many regulated supplies if the
// board regulates, and exactly one if the module does.
output logic [7:0] supplies_if_board,
output logic [7:0] supplies_if_module,
output logic err_rail_above_vin,
output logic err_zero_level,
output logic any_error
);
initial begin
if (VIN_LEVEL < 2)
$fatal(1, "module_transport_budget: VIN_LEVEL must be at least 2");
if (V_SCALE < 1)
$fatal(1, "module_transport_budget: V_SCALE must be positive");
if (N_RAILS < 1)
$fatal(1, "module_transport_budget: need at least one rail");
if (RATIO_W < 2 * LVL_W + 2)
// VIN^2 needs twice the level width plus headroom; too narrow
// and the ratio silently wraps, which reads as a SMALL benefit
// -- the flattering direction, and therefore the dangerous one.
$fatal(1, "module_transport_budget: RATIO_W too narrow for a square");
if (VIN_LEVEL > ((1 << LVL_W) - 1))
$fatal(1, "module_transport_budget: VIN_LEVEL exceeds LVL_W");
end
logic [RATIO_W-1:0] num_q, den_q;
logic v_q, e_above, e_zero;
logic [IDX_W-1:0] best_q;
logic [LVL_W-1:0] low_q;
// Declared above the continuous assigns that read them.
logic [LVL_W-1:0] sel_level;
assign sel_level = rail_level[q_rail];
logic [IDX_W-1:0] best_c;
logic [LVL_W-1:0] low_c;
always_comb begin
best_c = '0;
low_c = {LVL_W{1'b1}};
for (int i = 0; i < N_RAILS; i++)
if ((rail_level[i] != '0) && (rail_level[i] < low_c)) begin
low_c = rail_level[i];
best_c = IDX_W'(i);
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
num_q <= '0; den_q <= '0; v_q <= 1'b0;
best_q <= '0; low_q <= '0;
e_above <= 1'b0; e_zero <= 1'b0;
end else begin
v_q <= 1'b0; e_above <= 1'b0; e_zero <= 1'b0;
best_q <= best_c;
low_q <= low_c;
if (q_valid) begin
if (sel_level == '0) begin
e_zero <= 1'b1;
end else if ({{(LVL_W){1'b0}}, sel_level} > {{(LVL_W){1'b0}}, LVL_W'(VIN_LEVEL)}) begin
// A rail above the bulk input is not a step-down at all --
// reported rather than producing a ratio below one, which
// would read as local regulation being WORSE.
e_above <= 1'b1;
end else begin
num_q <= RATIO_W'(VIN_LEVEL) * RATIO_W'(VIN_LEVEL);
den_q <= RATIO_W'(sel_level) * RATIO_W'(sel_level);
v_q <= 1'b1;
end
end
end
end
assign loss_num = num_q;
assign loss_den = den_q;
assign q_out_valid = v_q;
assign best_rail = best_q;
assign lowest_level = low_q;
assign supplies_if_board = 8'(N_RAILS);
assign supplies_if_module = 8'd1;
assign err_rail_above_vin = e_above;
assign err_zero_level = e_zero;
assign any_error = e_above | e_zero;
endmoduleThe ratio is published as VIN² over Vrail² and never divided. A single integer ratio would need a rounding decision, and §2's figures — 20.66× and 7.72× — are not integers. The pair lets a caller compute whatever precision it wants against its own denominator, which is the discipline every accountant in this module and the last follows.
And a rail above the bulk input is reported rather than computed. It would produce a ratio below one, which reads as local regulation being worse — a true statement about an impossible configuration, and exactly the kind of number that escapes into a summary.
13. The Hub Thermal Reporter
// ---------------------------------------------------------------------
// spd_hub_reporter -- the module's own telemetry: a presence-detect
// store and a thermal threshold report, readable independently of
// whether the memory is usable.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHY INDEPENDENCE IS THE POINT (§6): the hub has its own LDO supply,
// DEVICE-VERIFIED as "1.8 V LDO output: from PMIC to HUB". So it can
// answer questions before the DRAM is configured and while the DRAM
// is unusable -- which is when the questions matter most.
//
// WHAT IT DOES NOT MODEL:
// - temperature, the sensor's physics or its accuracy
// - the real SPD data layout or ANY of its semantics. 1024 bytes is
// DEVICE-VERIFIED as the size; the contents are not modelled.
// - what a host should DO about a thermal event, which is a system
// policy and not a module behaviour
// ---------------------------------------------------------------------
module spd_hub_reporter #(
parameter int SPD_BYTES = 1024, // DEVICE-VERIFIED size
parameter int ADDR_W = $clog2(SPD_BYTES),
parameter int TEMP_W = 10, // abstract temperature units
parameter int CNT_W = 20
) (
input logic clk,
input logic rst_n,
// ── The hub's own supply. §6: separate from the DRAM's rails, so
// hub_powered can be true while dram_usable is false.
input logic hub_powered,
input logic dram_usable,
// ── Presence-detect reads.
input logic rd_valid,
input logic [ADDR_W-1:0] rd_addr,
output logic rd_ack,
output logic rd_in_range,
// ── Thermal.
input logic [TEMP_W-1:0] temp_now,
input logic [TEMP_W-1:0] thr_warn,
input logic [TEMP_W-1:0] thr_crit,
input logic thr_programmed,
output logic warn_now,
output logic crit_now,
// Sticky, because a thermal excursion a host polled past still
// happened -- the same reasoning as §11's deassertion record.
output logic ever_warned,
output logic ever_critical,
output logic [TEMP_W-1:0] peak_temp,
output logic [CNT_W-1:0] cy_above_warn,
output logic [CNT_W-1:0] cnt_warn_events,
// ── The property §6 exists to establish, published so a consumer
// can rely on it rather than infer it.
output logic readable_without_dram,
output logic err_read_while_unpowered,
output logic err_addr_out_of_range,
output logic err_thresholds_inverted,
output logic err_report_before_programmed,
output logic any_error
);
initial begin
if (SPD_BYTES != 1024)
$fatal(1, "spd_hub_reporter: SPD_BYTES is 1024 (device-verified), got %0d",
SPD_BYTES);
if (ADDR_W != $clog2(SPD_BYTES))
$fatal(1, "spd_hub_reporter: ADDR_W must address exactly SPD_BYTES");
if (TEMP_W < 2)
$fatal(1, "spd_hub_reporter: TEMP_W must be at least 2");
end
logic ack, in_rng, w_ever, c_ever;
logic [TEMP_W-1:0] pk;
logic [CNT_W-1:0] cy_warn, n_warn;
logic e_unpow, e_addr, e_inv, e_early;
logic warn_q;
// Declared above the continuous assigns that read them.
logic addr_ok;
assign addr_ok = ({{(32-ADDR_W){1'b0}}, rd_addr} < 32'(SPD_BYTES));
logic w_now, c_now;
assign w_now = thr_programmed && hub_powered && (temp_now >= thr_warn);
assign c_now = thr_programmed && hub_powered && (temp_now >= thr_crit);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ack <= 1'b0; in_rng <= 1'b0;
w_ever <= 1'b0; c_ever <= 1'b0; pk <= '0;
cy_warn <= '0; n_warn <= '0; warn_q <= 1'b0;
e_unpow <= 1'b0; e_addr <= 1'b0; e_inv <= 1'b0; e_early <= 1'b0;
end else begin
ack <= 1'b0; in_rng <= 1'b0;
e_unpow <= 1'b0; e_addr <= 1'b0; e_inv <= 1'b0; e_early <= 1'b0;
// A critical threshold below the warning one inverts the
// escalation: every warning would also be critical.
if (thr_programmed && (thr_crit < thr_warn)) e_inv <= 1'b1;
if (rd_valid) begin
if (!hub_powered) e_unpow <= 1'b1;
else if (!addr_ok) e_addr <= 1'b1;
else begin ack <= 1'b1; in_rng <= 1'b1; end
end
if (hub_powered) begin
// A temperature report before thresholds are programmed is
// a reading against defaults, not against system intent.
if (!thr_programmed && (temp_now != '0)) e_early <= 1'b1;
if (temp_now > pk) pk <= temp_now;
if (w_now) begin
w_ever <= 1'b1;
if (cy_warn != {CNT_W{1'b1}}) cy_warn <= cy_warn + CNT_W'(1);
if (!warn_q && (n_warn != {CNT_W{1'b1}})) n_warn <= n_warn + CNT_W'(1);
end
if (c_now) c_ever <= 1'b1;
warn_q <= w_now;
end
end
end
assign rd_ack = ack;
assign rd_in_range = in_rng;
assign warn_now = w_now;
assign crit_now = c_now;
assign ever_warned = w_ever;
assign ever_critical = c_ever;
assign peak_temp = pk;
assign cy_above_warn = cy_warn;
assign cnt_warn_events = n_warn;
// §6's property, as an output: the hub answers whenever it has its
// own supply, regardless of the DRAM's state.
assign readable_without_dram = hub_powered && !dram_usable;
assign err_read_while_unpowered = e_unpow;
assign err_addr_out_of_range = e_addr;
assign err_thresholds_inverted = e_inv;
assign err_report_before_programmed = e_early;
assign any_error = e_unpow | e_addr | e_inv | e_early;
endmodulereadable_without_dram is an output rather than an internal condition because it is the architectural property §6 establishes, and a consumer should be able to rely on it rather than infer it from two other signals. A hub that only answered when the DRAM was usable would be useless for the case it exists to serve.
And cnt_warn_events counts edges while cy_above_warn counts cycles. A module that crosses the warning threshold once for a long time and one that oscillates across it are different problems — the first is a steady thermal condition and the second is a load pattern — and a single counter cannot distinguish them.
14. What the Assertions Prove
// Bind unit note: P1-P6 reference pwr_good_aggregator's internals,
// P7-P10 module_transport_budget's, P11-P15 spd_hub_reporter's.
// P1 -- FORBIDDEN. PWR_GOOD is never asserted while any term fails.
// §4's conjunction, as the property the signal exists to guarantee.
property p_never_good_with_a_failing_term;
@(posedge clk) disable iff (!rst_n)
pwr_good |-> ((n_terms_failing == '0) && !fail_vin && (fail_rail == '0));
endproperty
assert property (p_never_good_with_a_failing_term);
// P2 -- FORBIDDEN. A DISABLED rail never contributes a failure. §4:
// "all ENABLED output buck regulators" -- a fixed-arity AND over all
// rails would hold the signal low forever with any rail off.
property p_disabled_rail_never_fails;
@(posedge clk) disable iff (!rst_n)
((fail_rail & ~rail_enabled) == '0);
endproperty
assert property (p_disabled_rail_never_fails);
// P3 -- the active term count is VIN plus the enabled rails, so the
// conjunction's arity tracks configuration rather than a constant.
property p_arity_tracks_enables;
@(posedge clk) disable iff (!rst_n)
(n_terms_active == (TERMCNT_W'(1) + TERMCNT_W'($countones(rail_enabled))));
endproperty
assert property (p_arity_tracks_enables);
// P4 -- the bulk input is always a term. Omitting it would report
// rail health while the input was already gone (§4).
property p_vin_is_always_a_term;
@(posedge clk) disable iff (!rst_n)
(!vin_in_tolerance |-> !pwr_good);
endproperty
assert property (p_vin_is_always_a_term);
// P5 -- the open-drain drive condition is the complement of the
// instantaneous conjunction, so a consumer can model the net.
property p_drive_low_is_not_good_now;
@(posedge clk) disable iff (!rst_n)
(driving_low == !(vin_in_tolerance && ((rail_enabled & ~rail_in_tolerance) == '0)));
endproperty
assert property (p_drive_low_is_not_good_now);
// P6 -- a deassertion is never forgotten. §11's callout: an
// open-drain pulse a host polled past still happened.
property p_deassertion_record_is_sticky;
@(posedge clk) disable iff (!rst_n)
($past(ever_deasserted, 1) |-> ever_deasserted);
endproperty
assert property (p_deassertion_record_is_sticky);
// P7 -- the published ratio is exactly VIN squared over the rail
// squared. §2's model, as an invariant rather than a comment.
property p_ratio_is_squares;
@(posedge clk) disable iff (!rst_n)
q_out_valid |-> (loss_num == (RATIO_W'(VIN_LEVEL) * RATIO_W'(VIN_LEVEL)));
endproperty
assert property (p_ratio_is_squares);
// P8 -- FORBIDDEN. A ratio is never published for a rail above the
// bulk input. It would read as local regulation being WORSE.
property p_no_ratio_above_vin;
@(posedge clk) disable iff (!rst_n)
(q_valid && (rail_level[q_rail] > LVL_W'(VIN_LEVEL))) |=> !q_out_valid;
endproperty
assert property (p_no_ratio_above_vin);
// P9 -- the numerator is never below the denominator for a published
// result, so the reported benefit is never inverted.
property p_benefit_never_inverted;
@(posedge clk) disable iff (!rst_n)
q_out_valid |-> (loss_num >= loss_den);
endproperty
assert property (p_benefit_never_inverted);
// P10 -- §2: the lowest rail benefits most, so best_rail names a
// rail whose level is the minimum non-zero level.
property p_best_rail_is_lowest;
@(posedge clk) disable iff (!rst_n)
((lowest_level != '0) |-> (rail_level[best_rail] == lowest_level));
endproperty
assert property (p_best_rail_is_lowest);
// P11 -- FORBIDDEN. The hub never acknowledges a read while
// unpowered. Answering without a supply would be a fiction.
property p_no_ack_unpowered;
@(posedge clk) disable iff (!rst_n)
(!hub_powered |-> !rd_ack);
endproperty
assert property (p_no_ack_unpowered);
// P12 -- §6's property: the hub answers with its own supply
// regardless of the DRAM's state. This is what the separate LDO buys.
property p_hub_answers_without_dram;
@(posedge clk) disable iff (!rst_n)
(rd_valid && hub_powered && !dram_usable &&
({{(32-ADDR_W){1'b0}}, rd_addr} < 32'(SPD_BYTES))) |=> rd_ack;
endproperty
assert property (p_hub_answers_without_dram);
// P13 -- FORBIDDEN. An out-of-range address is never acknowledged.
property p_no_ack_out_of_range;
@(posedge clk) disable iff (!rst_n)
(rd_valid && ({{(32-ADDR_W){1'b0}}, rd_addr} >= 32'(SPD_BYTES))) |=> !rd_ack;
endproperty
assert property (p_no_ack_out_of_range);
// P14 -- critical implies warning, so the escalation is ordered. An
// inverted threshold pair is reported rather than silently making
// every warning critical.
property p_critical_implies_warning;
@(posedge clk) disable iff (!rst_n)
((thr_crit >= thr_warn) && crit_now) |-> warn_now;
endproperty
assert property (p_critical_implies_warning);
// P15 -- the peak is monotone and the sticky flags never clear. A
// thermal excursion that evaporated from the record would let a host
// conclude nothing happened.
property p_thermal_record_is_monotone;
@(posedge clk) disable iff (!rst_n)
((peak_temp >= $past(peak_temp, 1)) &&
($past(ever_warned,1) |-> ever_warned) &&
($past(ever_critical,1) |-> ever_critical));
endproperty
assert property (p_thermal_record_is_monotone);
// ── Cover.
// The conjunction good, and failing by EACH term separately -- a
// property forbidding something is vacuous until it is attempted.
cover property (@(posedge clk) disable iff (!rst_n) pwr_good);
cover property (@(posedge clk) disable iff (!rst_n) fail_vin);
cover property (@(posedge clk) disable iff (!rst_n) $countones(fail_rail) == 1);
cover property (@(posedge clk) disable iff (!rst_n) $countones(fail_rail) > 1);
// A rail DISABLED while out of tolerance -- P2 is vacuous without it,
// and it is the case a fixed-arity AND gets wrong.
cover property (@(posedge clk) disable iff (!rst_n)
((rail_enabled & ~rail_in_tolerance) == '0) &&
((~rail_enabled & ~rail_in_tolerance) != '0) && pwr_good);
// Good asserted before thresholds were programmed — §5's window.
cover property (@(posedge clk) disable iff (!rst_n) err_good_before_programmed);
// Both extremes of the transport comparison.
cover property (@(posedge clk) disable iff (!rst_n)
q_out_valid && (lowest_level == rail_level[best_rail]));
cover property (@(posedge clk) disable iff (!rst_n) err_rail_above_vin);
// THE PROPERTY §6 EXISTS FOR: the hub read while the DRAM is unusable.
cover property (@(posedge clk) disable iff (!rst_n) readable_without_dram && rd_ack);
cover property (@(posedge clk) disable iff (!rst_n) err_read_while_unpowered);
cover property (@(posedge clk) disable iff (!rst_n) err_addr_out_of_range);
// Thermal: warning, critical, and an inverted threshold pair.
cover property (@(posedge clk) disable iff (!rst_n) warn_now && !crit_now);
cover property (@(posedge clk) disable iff (!rst_n) crit_now);
cover property (@(posedge clk) disable iff (!rst_n) err_thresholds_inverted);
// Oscillation across the warning threshold — the case a single
// counter cannot distinguish from a steady excursion (§13).
cover property (@(posedge clk) disable iff (!rst_n)
warn_now ##1 !warn_now ##1 warn_now);15. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
| Every term in tolerance | pwr_good; driving_low low | §4's conjunction satisfied |
VIN_Bulk out of tolerance | Not good, regardless of rails | P4 — the input is always a term |
| One enabled buck out of tolerance | Not good; fail_rail names it | P1; the per-term output is the diagnosis |
| A disabled rail out of tolerance | Still good | P2 — a rail that is off has no tolerance to be in |
| All five rails disabled | Good if VIN_Bulk is good | The arity collapses to one term — P3 |
| A fixed AND over all five rails | Never good with any rail off | The bug P2 forbids |
| Rail reports in tolerance while disabled | err_tolerance_without_enable | A status inconsistency, not a fault |
| Good asserted before thresholds programmed | err_good_before_programmed | §5 — the window where defaults, not intent, apply |
| Brief deassertion a host polls past | ever_deasserted sticky; count rises | §11's callout — open-drain pulses get missed |
| Reset | Not good until proven | The safe direction |
Rail level above VIN_LEVEL | Reported; no ratio published | P8 — would read as regulation being worse |
| Rail level of zero | err_zero_level; no ratio | A zero denominator |
RATIO_W too narrow for a square | $fatal at elaboration | A wrapped ratio reads as a small benefit |
| Hub read while unpowered | No acknowledge; reported | P11 — answering without a supply is a fiction |
| Hub read while DRAM unusable | Acknowledged | P12 — §6's whole purpose |
| Address ≥ 1024 | No acknowledge; reported | P13 |
thr_crit below thr_warn | err_thresholds_inverted | Every warning would also be critical |
| Temperature reported before thresholds set | err_report_before_programmed | A reading against defaults |
| Oscillation across the warning threshold | Event count rises; cycle count low | §13 — a load pattern, not a steady excursion |
Rows four through six are the group worth studying together. A disabled rail out of tolerance must not hold PWR_GOOD low, and the natural implementation — AND every rail — gets it exactly wrong on any module with a rail switched off.
16. DV
// Independent conjunction reference. Evaluates PWR_GOOD by walking an
// explicit term LIST rather than by bitwise masking, so agreement
// with the DUT is evidence and not a restatement of the same
// expression. SIMULATION-ONLY.
class pwr_good_ref;
typedef struct { bit enabled; bit in_tol; int margin; } term_t;
term_t rails[$];
bit vin_ok;
function void build(int n);
rails.delete();
for (int i = 0; i < n; i++) begin
term_t t; t.enabled = 0; t.in_tol = 0; t.margin = 0;
rails.push_back(t);
end
endfunction
function bit good();
if (!vin_ok) return 0;
foreach (rails[i])
if (rails[i].enabled && !rails[i].in_tol) return 0; // enabled only
return 1;
endfunction
function int active_terms();
int n = 1; // VIN always counts
foreach (rails[i]) if (rails[i].enabled) n++;
return n;
endfunction
function int worst_margin();
int w = -1;
foreach (rails[i])
if (rails[i].enabled && (w < 0 || rails[i].margin < w)) w = rails[i].margin;
return w; // -1 when none enabled
endfunction
// §11's model, for comparing an observed deassertion rate against
// what a six-term conjunction predicts.
function real predicted_failure(real p, int terms);
return 1.0 - (p ** terms);
endfunction
endclass| Check | What it establishes |
|---|---|
| Sweep all 2⁵ enable patterns × all 2⁵ tolerance patterns | The conjunction against an independent term walk |
Confirm n_terms_active matches active_terms() throughout | P3 across every configuration |
| Disable a rail and drive it out of tolerance | P2 and its cover — the case a fixed AND fails |
| Replace the DUT's mask with an unconditional AND over all rails | P2 fires; every other property still passes |
Drop VIN_Bulk with every rail good | P4 |
Assert good before thresholds_programmed | §15's row eight |
| Single-cycle deassertion, then good again | ever_deasserted sticky; count rises by one |
Compare observed deassertion rate against predicted_failure | §11's 6× factor, measured rather than assumed |
Sweep rail levels 1…VIN_LEVEL; check loss_num/loss_den | P7, P9; reproduce §2's 20.66× and 7.72× as pairs |
Drive a rail level above VIN_LEVEL | P8 — no ratio published |
Narrow RATIO_W by one bit | Elaboration fails rather than wrapping |
Read the hub with dram_usable low throughout | P12 and its cover — §6's purpose |
Read the hub with hub_powered low | P11 |
| Address 1023 and 1024 | P13 at the boundary |
Program thr_crit below thr_warn | P14's guard and the inverted-pair cover |
Hold the temperature just above thr_warn for many cycles | Cycle count rises, event count stays at one |
Oscillate around thr_warn | Event count rises, cycle count stays low |
The fourth check produces the report worth publishing, because the bug it finds is the one a careful engineer writes on purpose:
THE CONJUNCTION THAT LOOKED MORE CAREFUL
PWR_GOOD implemented as the obvious safe thing:
good = vin_ok && (rail_in_tolerance == 5'b11111);
i.e. ALL five rails must be in tolerance, enabled or not.
module configuration: the 1.0 V LDO is unused on this design
and is DISABLED. Its status bit reads 0.
result: PWR_GOOD never asserts. Not once. The host never
releases memory, and the module appears dead on
arrival.
assertions : P1 never good with a failing term PASS
P3 arity tracks enables PASS
P4 vin is always a term PASS
P2 disabled rail never fails FAIL
diagnosis : the implementation is STRICTER than the
specification, and strictness is the wrong direction here.
§4's verified wording is "all ENABLED output buck regulators",
and a disabled regulator has no tolerance to be in -- its
status bit is not a fault, it is meaningless.
what makes it instructive : this is not a careless bug. It is
what you write if you reason "a power-good signal should be
conservative", which is usually correct and is wrong here.
The specification's one word -- "enabled" -- is the whole
difference, and only P2 encodes it.
and it fails LOUDLY, unlike most bugs in these two modules: the
module simply never comes up. That makes it the easiest
failure in Module 25 to find and the easiest to introduce.17. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
Module never comes up; PWR_GOOD never asserts | A disabled rail ANDed into the conjunction — §16 | P2; check rail_enabled against the status mask |
PWR_GOOD asserts then drops intermittently | One of six terms marginal — §11 | fail_rail and fail_vin, not the aggregate |
| Deassertions far more frequent than any rail's fault rate | Expected — §11 | Six terms; roughly 6× a single rail's rate |
| Deassertion suspected but never observed | Open-drain pulse polled past | ever_deasserted, cnt_deassertions |
PWR_GOOD behaviour differs between two identical modules | Thresholds programmed differently — §5 | Compare the configured windows before concluding |
| Good asserted during boot, then withdrawn | Asserted against defaults before programming | err_good_before_programmed |
| Supply looks fine at the board, module misbehaves | Regulation is on the module now — §8 | PWR_GOOD and the hub, not the board rails |
| Module hotter than the previous generation | Conversion loss moved onto the module — §2's callout | Peak temperature from the hub; §8's last row |
| Cannot read module configuration at all | Hub unpowered, or read attempted off its supply | err_read_while_unpowered; the hub's LDO |
| Configuration readable but memory unusable | Normal and by design — §6 | readable_without_dram is the intended state |
| Thermal warnings that never escalate | thr_crit below thr_warn — inverted pair | err_thresholds_inverted |
| Thermal event count high, time-above-threshold low | Oscillation, not a steady excursion — §13 | cnt_warn_events against cy_above_warn |
| Transport benefit reported as less than one | A rail level above the bulk input | err_rail_above_vin; P8 should have blocked it |
Row three is the entry this chapter exists to add. “PWR_GOOD deasserts more often than our rail fault rate predicts” is not a fault — it is a six-term conjunction behaving as specified, and an engineer who reasons from a single rail's reliability will conclude the hardware is broken.
Row ten is the one that reads as a failure and is not. A module whose configuration is readable while its memory is unusable is working exactly as designed, and that is the entire reason the hub has its own supply.
18. Misconceptions
“A DDR5 module receives its supplies from the board.” §1. It receives one bulk supply and generates five.
“VPP is an optional extra rail.” §1. It is the array's pump voltage — the high rail needed to drive a wordline — and at 1.8 V it is the highest device rail.
“VDD and VDDQ are the same rail because they are the same voltage.” §1. Separate PMIC outputs at the same voltage, kept apart for noise isolation.
“Local regulation is a 20× power saving.” §2's callout. It is roughly 20× less resistive transport loss on the 1.1 V rails, under a stated I² R model — and it moves the conversion loss onto the module, where no efficiency figure is available.
“DDR5 modules take 12 V.” §3. The named SODIMM specification says 5 V, and this chapter does not claim a figure for other module forms because it did not retrieve one.
“PWR_GOOD means the rails are up.” §4. It means the bulk input and every enabled regulator are inside register-programmed windows — six conditions, not one.
“PWR_GOOD should AND all the rails.” §16. Only the enabled ones. ANDing a disabled rail holds the signal low forever, and the module never comes up.
“A PWR_GOOD deassertion tells you what failed.” §11. It tells you one of six things failed. Without per-term outputs, which one is discarded.
“Deassertions are as rare as a rail fault.” §11. About six times as common, because that is what a six-way AND does.
“PWR_GOOD means the same thing on every module.” §5. The thresholds are register values, so two modules configured differently report differently on identical electrical behaviour.
“The presence-detect hub is a small EEPROM.” §7. It is a 1024-byte store with an integrated thermal sensor, on its own supply.
“If the memory is unusable the module tells you nothing.” §6. The hub has its own LDO precisely so it can answer when the DRAM cannot.
“Moving regulation onto the module is purely a win.” §8. A fault is confined to one module, and the conversion heat now sits next to devices whose retention degrades with temperature.
19. Interview Reasoning
What does a DDR5 module's PMIC do? Takes one bulk input — 5 V on the named SODIMM — and generates the device supplies locally: VDD and VDDQ at 1.1 V, VPP at 1.8 V, plus two low-dropout outputs, one of which feeds the presence-detect hub.
Why regulate on the module rather than the board? Resistive loss goes as the square of the current, so delivering the same power at 5 V instead of 1.1 V costs about a twentieth of the loss in the delivery path. It also puts regulation close to the load and reduces the connector to one regulated supply.
What does that argument leave out? The regulator's own efficiency. Conversion loss moves onto the module, next to the DRAM, and the consulted specification does not publish an efficiency figure — so the transport saving is quantified and the total is not.
What exactly does PWR_GOOD assert? That the bulk input and every enabled buck and LDO regulator are within thresholds held in registers. It is a conjunction of six terms on a fully populated module.
Why does the word “enabled” matter? A disabled regulator is not a term. An implementation that ANDs all rails unconditionally holds the signal low forever on any module with a rail switched off, and the module never comes up.
If PWR_GOOD deasserts more often than your rail fault rate predicts, what is wrong? Probably nothing. Six independent terms fail about six times as often as one, so the aggregate rate is expected to exceed any single rail's.
How would you make PWR_GOOD diagnostically useful? Publish the per-term status alongside the conjunction, and record deassertions stickily — the pin is open-drain and a brief pulse can be polled past entirely.
Why does the presence-detect hub have its own supply? So it can describe the module and report its temperature when the DRAM is not usable — which is when a host most needs to know what the module is and how hot it is.
What did moving regulation onto the module cost? Supply quality became a per-module property set by the module vendor rather than a board-level design characteristic, and the conversion heat now sits next to the array.
A module reports its configuration fine but its memory does not work. Is the hub lying? No — that is the designed behaviour. The hub runs from its own low-dropout supply and answers independently of the DRAM's state.
20. Exercises
-
Recompute §2's loss table for a 12 V bulk input against the same rails, then state precisely what would have to be retrieved before publishing the result as a DDR5 fact.
-
§2's callout says conversion loss is unquantified. Derive the regulator efficiency at which local regulation stops being a net win for the 1.1 V rail, stating every assumption you must add about the delivery path's resistance.
-
Using §11's model, compute the per-term reliability needed for a six-term conjunction to deassert no more often than once per 10⁹ cycles, and compare it against the requirement for a single-term signal.
-
Implement §16's over-strict conjunction and find the shortest stimulus that distinguishes it from the correct one. Explain why P1, P3 and P4 all pass on the broken version.
-
§12's block publishes the loss comparison as a pair. Compute the exact ratios for all five rails in §1 as reduced fractions, and say which one a single integer would misrepresent worst.
-
§13 separates
cnt_warn_eventsfromcy_above_warn. Construct two temperature traces with identical peak temperature and identical time above the warning threshold but different event counts, and say which represents the more serious condition. -
The hub is 1024 bytes and has its own supply. Design the minimum information a host would need from it to configure a module it has never seen, and identify which of that must be readable before any DRAM rail is up.
-
§8 says supply quality became a per-module property. Specify the qualification test a system integrator would need, using only signals this chapter establishes are visible to the host.
21. Where This Goes
A DDR5 module's power architecture is now concrete. One 5 V bulk input across the connector; VDD and VDDQ at 1.1 V, VPP at 1.8 V and two low-dropout outputs generated locally; a power-good signal that is the conjunction of the input and every enabled regulator against register-programmed thresholds; and a 1024-byte presence-detect hub with an integrated thermal sensor running from its own supply.
Four results carry forward. The transport argument is large and one-sided: roughly 20× less resistive loss on the 1.1 V rails, and five regulated supplies across the connector reduced to one. The conjunction's arity is a runtime value, because a disabled rail is not a term — and the natural, more-conservative implementation fails in the loudest possible way. The aggregate signal is about six times as likely to deassert as any rail is to fail, which makes it an excellent gate and the least specific diagnostic on the module. And the hub's separate supply is what makes a module interrogable when its memory is not, which is the case the telemetry exists to serve.
Two things left open, both recorded. The regulator's own efficiency is not published in the consulted specification, so §2's transport saving cannot be turned into a net figure — and the conversion loss it omits lands thermally next to the array. And the bulk input voltage for module forms other than the named SODIMM did not retrieve, so §3 declines to state one rather than repeating a figure it cannot cite.
What all three chapters so far have taken for granted is that the data arriving from the device is the data that was stored. Chapter 4.6 §3 establishes that DDR5 no longer takes that for granted either — it moved error correction inside the device, added a mechanism against a disturbance phenomenon that worsens with density, and taught the receiver to compensate for the channel using its own recent decisions. That chapter also names the awkward consequence: on-die ECC “improves the part and degrades the host's visibility into it.”
Chapter 25.4 takes up all of it — on-die ECC as a bounded-fault mechanism with transparency and scrub, refresh management with a bounded configuration whose duration equation can be checked against its own published table, decision feedback equalisation, and the question none of the earlier chapters could reach: how four integrity mechanisms operating at different layers interact, when one corrects inside the array, one checks the link and reports on a dedicated pin, and a system has all of them at once.
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
HBM Overview
HBM reaches hundreds of GB/s with a per-pin rate lower than DDR5's. It wins on width, not speed — and getting that width required changing the packaging, which adds a fourth design layer to array physics, device architecture and the interface.
- 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.
