DDR · Module 24
Low-Power Features
A named device's own state diagram defines the states, and several transitions between them are illegal. Per-bank refresh draws identical current to all-bank refresh on every rail — it buys availability, not energy.
Chapter 24.1 §3 established that at a 2% duty cycle nearly half a mobile memory's average power is spent idle, and below 1% it is two thirds. Chapter 4.7 §4 established why the LPDDR family attacks that cost from three directions — per-bank refresh, temperature-adaptive self refresh, and refreshing only the part of the array that holds live data.
Neither chapter says what the states are. That is this chapter's subject, and the states are not a matter of opinion: a real device publishes its own state diagram, and the set below is read from one.
The chapter also reports a measurement that contradicts something most engineers assume, including me before I looked it up. On a named LPDDR5X part, per-bank refresh draws exactly the same current as all-bank refresh on every supply rail. §8 works through what that means and what the mechanism buys instead.
1. The States Are the Device's, Not a Textbook's
Low-power states are often taught as a generic hierarchy — active, then something shallower, then something deeper. Real devices do not have a generic hierarchy; they have a specific set with specific names and specific rules about which transitions are permitted.
So rather than describe a hierarchy, here is the set a named LPDDR4 part publishes in its own bus-interface state diagram. DEVICE-VERIFIED from a Samsung 16Gb LPDDR4 datasheet.
| State | Reached by | What it is |
|---|---|---|
| Power On | Supply applied | Before reset completes |
| Reset | RESET_n low, then high | Initialisation |
| Idle | Reset complete, or precharge | Banks closed, clock running, ready for commands |
| Idle Power Down | CKE low from Idle | Idle with the clock input gated |
| Activating | ACT from Idle | A row is opening |
| Bank Active | Activation complete | A row is open |
| Active Power Down | CKE low from Bank Active | A row open, clock input gated |
| Precharging | PRE / PREA | Closing a row |
| Per Bank Refresh | REF from Idle | One bank being refreshed |
| All Bank Refresh | REF from Idle | Every bank being refreshed |
| Self Refresh | SRE from Idle | Device refreshes itself; exits on SRX |
| SR Power Down | From Self Refresh | Self refresh with further gating |
Plus two training states — Command Bus Training and MPC Based Training — reachable from Idle, which Module 21 would recognise as this family's equivalent machinery.
Two observations before the rules.
There are two power-down states, not one, and they differ by whether a row is open. Idle Power Down and Active Power Down are separate states because the device's obligations differ — an open row has constraints a closed one does not.
Self refresh is not a power-down state. It is a distinct branch with its own entry and exit commands, SRE and SRX, and it has a further low-power state beneath it. Conflating “power down” with “self refresh” is the most common error about this diagram, and §4 explains why they are different in kind.
2. Two Commands, Two Different Refreshes
The diagram shows Per Bank Refresh and All Bank Refresh as separate states, both entered by a REF command from Idle. The distinction is what the device does and when it is finished.
The datasheet's own state descriptions are precise about the difference, and the precision is the point. DEVICE-VERIFIED, paraphrased:
Per-bank refresh begins when a per-bank REF is registered and ends when tRFCpb is met. Once met, that bank is in an idle state.
All-bank refresh begins when an all-bank REF is registered and ends when tRFCab is met. Once met, the device is in an all-banks-idle state.
| Per bank | All bank | |
|---|---|---|
| Timing parameter | tRFCpb | tRFCab |
| What becomes idle | that bank | the device |
| Other banks during it | available | unavailable |
The middle row is the mechanism. Per-bank refresh leaves the device partially usable; all-bank refresh does not. That is an availability property, and §8 shows it is the only property that differs on the device measured there.
One more DEVICE-VERIFIED detail, from the same datasheet's current-measurement conditions: the per-bank refresh test specifies tRC = tREFI / 8. Eight per-bank refreshes are issued in the interval one all-bank refresh would occupy — which tells you the bank count the per-bank scheme is designed around, without the datasheet having to state it separately.
3. Some Transitions Are Illegal
This is where a state diagram earns its keep, and it is the part a generic hierarchy cannot express.
Reading the device's diagram, the permitted transitions form a specific graph. What matters for a controller is the complement: the transitions that are not in it. Several are worth naming because each corresponds to a plausible mistake.
| Attempted transition | Why it is illegal |
|---|---|
Bank Active → Self Refresh | Self refresh is entered from Idle. A row must be closed first. |
Bank Active → Per Bank Refresh | Refresh is issued from Idle; an open row must be precharged. |
Idle Power Down → Bank Active | Power-down must be exited before a command is accepted. |
Self Refresh → Bank Active | SRX returns the device to Idle, not to an active row. |
Self Refresh → Idle Power Down | Different branches; the diagram provides no such edge. |
Reset → Bank Active | Initialisation completes into Idle. |
The common shape of all six is that a state must be left the way it was entered, through a defined exit, rather than abandoned sideways into another state.
Two of them deserve more than a table row.
An open row blocks both refresh and self refresh. That is not arbitrary — a refresh operates on the array, and a row held open in a sense amounts holds part of the array in a state the refresh would disturb. So the controller must precharge before refreshing, and a refresh scheduler that does not track whether banks are open will attempt an illegal command.
Power-down exit is a step, not a side effect. Raising CKE does not make a command land; it returns the device to the state it left, after which commands are accepted again. Chapter 6.2 owns the qualification contract that makes this precise, and its debugging section — a command that appears to have been issued and had no effect — is exactly the failure a controller produces when it treats exit as instantaneous.
4. Power Down and Self Refresh Are Different in Kind
§1 noted these are commonly conflated. The distinction is worth making precisely, because it decides which one a controller should use and the answer depends on something outside the memory.
| Power down | Self refresh | |
|---|---|---|
| Entered by | CKE low | SRE command |
| Exited by | CKE high | SRX command |
| Who refreshes | the controller, still | the device, itself |
| Controller obligation | must keep issuing refresh on time | none while in the state |
| Depth | shallower | deeper, with a further state below |
The third row is the whole difference. In power down, the device has gated its clock input but the refresh obligation still belongs to the controller — it must exit, refresh, and re-enter, on schedule, for as long as it wants to stay there. In self refresh, the device takes the obligation over.
Two consequences follow, and they point in opposite directions.
Self refresh is the only option for a long idle period, because a controller that must wake every refresh interval to service the array is not really leaving. For a phone in a pocket for an hour, self refresh is the state; power down is not.
But power down is the right choice for a short gap, because its exit is cheaper and the controller was going to be awake anyway. Entering self refresh for a gap shorter than its exit cost loses.
So the choice is a function of the expected idle duration, which the memory cannot know and the controller must estimate — the same shape as 24.1 §3's duty cycle being a system property. §5 makes the trade quantitative.
5. A Deeper State Costs More to Leave
The trade in §4 is an exchange: a state that turns more things off saves more while occupied and costs more to exit. Chapter 4.7 §7 owns that argument and builds a controller around it; this section supplies the arithmetic that decides a single instance of the choice.
For a state with a per-tick saving s relative to staying active, an exit cost e, and an idle duration d ticks:
entering is worth it when s x d > e
the break-even duration is d* = e / sDERIVED, and it is a one-line result with a useful shape: the break-even duration is the exit cost divided by the saving rate. A state that saves twice as much per tick pays back in half the time; a state that costs twice as much to leave needs twice as long to be worth entering.
Two consequences, both of which recur:
A deeper state is not better — it is better for longer idles. Two states with different depths have different break-even points, and the right choice depends entirely on d, which is a prediction.
And predicting d wrong is asymmetric. Entering a deep state for a short idle pays e and recovers little. Staying shallow through a long idle forgoes savings that would have accumulated. Which error is worse depends on the distribution of idle durations — and §14's corner cases include the case where a device oscillates because its predictor is wrong in both directions alternately.
6. The Break-Even Selector
§5's relation decides one instance of the choice, and it is worth building because the arithmetic has a property the formula hides: the two states cross over at a specific duration, and below it the deeper state is not merely less good — it is worse than not entering at all.
Work it through with abstract units. Take exit costs of 2 and 16 ticks for the shallow and deep states, and saving rates of 3 and 10 units per tick. ILLUSTRATIVE figures, chosen for legibility; every derived number below recomputed.
Net saving from entering a state for d ticks is s x d - e:
Idle duration d | Power down 3d - 2 | Self refresh 10d - 16 | Better choice |
|---|---|---|---|
| 1 | +1 | -6 | Power down — self refresh loses |
| 2 | +4 | +4 | tie — the crossover |
| 3 | +7 | +14 | Self refresh |
| 5 | +13 | +34 | Self refresh |
| 10 | +28 | +84 | Self refresh |
| 40 | +118 | +384 | Self refresh |
DERIVED, and the crossover is exact rather than approximate:
3d - 2 = 10d - 16
14 = 7d
d = 2 the crossover, exactlyThree readings, and the first is the one the formula alone does not give you.
At d = 1 the deep state has a negative net saving. It costs 16 to leave and saves 10, so entering it for a single tick spends 6 units to save nothing — worse than staying active. A selector that only asked “which state saves more per tick?” would pick self refresh here and lose.
The crossover is not either state's own break-even. Power down pays back at 2/3 of a tick and self refresh at 1.6 ticks, but the duration at which self refresh becomes the better of the two is 2 — later than its own break-even. Between 1.6 and 2 ticks, self refresh is worth entering and power down is worth more.
And above the crossover the deep state wins by a growing margin. At d = 40 it saves 384 against 118, a factor of 3.3. The trade is not close once the idle is long — which is why §4's conclusion holds: for a long idle there is no real choice, and all the difficulty is in the short cases near the crossover.
// ---------------------------------------------------------------------
// lp_state_selector -- given a predicted idle duration and the cost
// and saving of two states, choose between them, or neither.
//
// CLASSIFICATION: educational, synthesisable.
//
// ALL UNITS ARE ABSTRACT. No energy, no current, no voltage, no real
// exit latency. §5's callout: datasheets publish exit LATENCY and
// steady-state CURRENT, which cannot be combined into an energy
// without information they do not give.
//
// WHAT IT DOES NOT MODEL:
// - the prediction. d_pred is an INPUT. §4: the idle duration is a
// system property the memory cannot know, and guessing it here
// would invent the hardest part of the problem.
// - legality. §10's machine owns whether the transition is even
// permitted, and this block can recommend a state the machine
// will refuse. That separation is deliberate.
// - what actually happens if the prediction is wrong, beyond
// reporting the regret (§13's oscillation case).
//
// NO DIVISION. The break-even comparison is done by cross-multiplying
// (s x d vs e), the same no-divide discipline as 24.1 §10.
// ---------------------------------------------------------------------
module lp_state_selector #(
parameter int RATE_W = 12, // saving per tick
parameter int COST_W = 16, // cost to leave the state
parameter int DUR_W = 16, // predicted idle duration, ticks
parameter int NET_W = RATE_W + DUR_W + 2 // signed headroom
) (
input logic clk,
input logic rst_n,
input logic eval,
input logic [DUR_W-1:0] d_pred,
// Shallow state (power down) and deep state (self refresh).
input logic [RATE_W-1:0] s_shallow,
input logic [COST_W-1:0] e_shallow,
input logic [RATE_W-1:0] s_deep,
input logic [COST_W-1:0] e_deep,
// ── Verdict. Three outcomes, not two -- "neither" is a real answer
// and §6's d=1 row is why.
output logic pick_shallow,
output logic pick_deep,
output logic pick_none,
// Net savings, SIGNED, so a state that loses reports a negative
// figure rather than a clamped zero. §6: at d=1 the deep state
// spends 6 units to save nothing, and that must be visible.
output logic signed [NET_W-1:0] net_shallow,
output logic signed [NET_W-1:0] net_deep,
output logic signed [NET_W-1:0] net_chosen,
// The regret of the choice not taken -- always >= 0 by construction.
output logic signed [NET_W-1:0] regret,
// §6's second reading: a state can be past its own break-even and
// still be the worse of the two. Reported separately because a
// policy that conflates them picks wrongly in that window.
output logic deep_past_own_breakeven,
output logic deep_beats_shallow,
output logic err_deep_cheaper_to_exit,
output logic err_no_saving_either_state
);
initial begin
if (NET_W <= RATE_W + DUR_W)
$fatal(1, "lp_state_selector: NET_W needs sign and carry headroom");
if (COST_W > NET_W - 1)
$fatal(1, "lp_state_selector: COST_W exceeds signed net range");
end
logic signed [NET_W-1:0] n_sh, n_dp;
logic v_sh, v_dp, v_no, bad_cost, bad_zero;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_sh <= '0; n_dp <= '0;
v_sh <= 1'b0; v_dp <= 1'b0; v_no <= 1'b1;
bad_cost <= 1'b0; bad_zero <= 1'b0;
end else if (eval) begin
begin : decide
logic signed [NET_W-1:0] a, b;
// s x d - e, by multiplication only. Zero-extend the unsigned
// operands into the signed width before the subtract, so the
// result can legitimately go negative.
a = NET_W'($signed({1'b0, s_shallow}) * $signed({1'b0, d_pred}))
- NET_W'($signed({1'b0, e_shallow}));
b = NET_W'($signed({1'b0, s_deep}) * $signed({1'b0, d_pred}))
- NET_W'($signed({1'b0, e_deep}));
n_sh <= a;
n_dp <= b;
// Three-way. A state is only picked if its net saving is
// POSITIVE -- "neither" wins when both lose.
if ((a <= 0) && (b <= 0)) begin
v_sh <= 1'b0; v_dp <= 1'b0; v_no <= 1'b1;
end else if (b > a) begin
v_sh <= 1'b0; v_dp <= 1'b1; v_no <= 1'b0;
end else begin
// Ties go to the SHALLOW state. At the crossover both save
// the same, and the shallow state carries less risk if the
// prediction was optimistic -- §5's asymmetry.
v_sh <= 1'b1; v_dp <= 1'b0; v_no <= 1'b0;
end
bad_zero <= (a <= 0) && (b <= 0);
end
// §5's trade, checked at runtime rather than at elaboration
// because here the costs are inputs rather than parameters.
bad_cost <= (e_deep <= e_shallow);
end
end
assign pick_shallow = v_sh;
assign pick_deep = v_dp;
assign pick_none = v_no;
assign net_shallow = n_sh;
assign net_deep = n_dp;
assign net_chosen = v_dp ? n_dp : (v_sh ? n_sh : '0);
// Regret of the alternative. Zero when nothing was entered, since
// there was nothing better to have done.
assign regret = v_dp ? ((n_dp > n_sh) ? (n_dp - n_sh) : '0)
: v_sh ? ((n_sh > n_dp) ? (n_sh - n_dp) : '0)
: '0;
assign deep_past_own_breakeven = (n_dp > 0);
assign deep_beats_shallow = (n_dp > n_sh);
assign err_deep_cheaper_to_exit = bad_cost;
assign err_no_saving_either_state = bad_zero;
endmoduleTwo decisions in that block are §6's readings rather than implementation taste.
The net savings are signed and a losing state reports a negative figure. Clamping at zero would hide the d = 1 row, where the deep state is not simply unhelpful but actively costly — and that is the case a naive rate-comparing policy gets wrong.
deep_past_own_breakeven and deep_beats_shallow are separate outputs. §6's second reading is that between 1.6 and 2 ticks the deep state is past its own break-even and still the worse choice. A policy that treats those as the same condition enters the deep state in that window and loses — so the block reports both and lets the caller see the gap.
7. Temperature Is the Expensive Variable
Chapter 2.7 owns retention against temperature and builds an interval selector; 4.7 §4 owns why a temperature-adaptive self refresh is attractive. What neither has is a number for how much temperature actually costs.
A named LPDDR5X part publishes its self-refresh current at two temperatures. DEVICE-VERIFIED, Micron Y4BM family:
| Rail | 25 °C | 95 °C | Ratio |
|---|---|---|---|
VDD1 | 0.25 mA | 3.70 mA | 14.8× |
VDD2H | 0.45 mA | 12.00 mA | 26.7× |
| Sum of the two | 0.70 mA | 15.70 mA | 22.4× |
All ratios DERIVED by division and recomputed.
A device idling hot draws more than twenty times the self-refresh current of the same device idling cool. That is the quantified case for temperature-compensated self refresh, and it is a far larger factor than most of the architectural savings elsewhere in this module.
Two readings, and the second is the one that matters for a system.
The mechanism's value scales with how hot the device actually gets. On a device that stays cool, temperature compensation saves little because there was little to save. On one that runs hot — in a pocket, in sunlight, next to a busy application processor — it is the dominant idle-power lever.
And it interacts with 24.1 §4's feedback loop. A hotter device refreshes more, which dissipates more, which makes it hotter. Temperature-compensated refresh does not break that loop, but it changes its gain — and the 22.4× figure is the measure of how much gain is on the table.
8. What Per-Bank Refresh Actually Buys
Here is the measurement that corrected my own assumption, and it is worth presenting as the surprise it was.
The natural expectation is that per-bank refresh saves energy: it refreshes one bank rather than eight, so it should draw less. The same LPDDR5X datasheet publishes both currents. DEVICE-VERIFIED, at two speed grades in both x8 and x16 modes:
| Rail | IDD5AB (all bank) | IDD5PB (per bank) | Equal? |
|---|---|---|---|
VDD1 | 2.5 mA | 2.5 mA | yes |
VDD2H | 23.5 / 24.0 mA | 23.5 / 24.0 mA | yes |
VDD2L | 0.2 mA | 0.2 mA | yes |
VDDQ | 0.6 mA | 0.6 mA | yes |
Every rail, at every grade, identical. Checked rail by rail rather than eyeballed.
The reason, on reflection, is obvious: these are average currents over an interval in which the same amount of array gets refreshed either way. Per-bank refresh does not refresh less; it refreshes the same total in smaller pieces. The energy to refresh a bank is the energy to refresh a bank, and doing eight of them individually costs what doing eight together costs.
So per-bank refresh buys exactly one thing: availability. §2's middle row is not one benefit among several — it is the benefit. While a per-bank refresh proceeds, the other banks can serve requests; during an all-bank refresh, nothing can.
9. Partial-Array Self Refresh Is a Mask
The third mechanism 4.7 §4 names, and the one with the most interesting failure mode. That chapter owns the argument that it is a software contract whose violation causes data loss rather than a performance regression. This section owns the mechanism.
Both generations support it. DEVICE-VERIFIED: the LPDDR4 part lists PASR as a supported special function, and the LPDDR5X part lists partial-array self refresh (PASR) and partial-array auto refresh (PAAR) with segment mask.
Note that LPDDR5 has two of them, which is a genuine generational difference:
| Mechanism | Applies to | Generation |
|---|---|---|
| PASR | Self refresh — the device's own refreshing | LPDDR4 and LPDDR5 |
| PAAR | Auto refresh — controller-issued refresh | LPDDR5 (on the named part) |
PASR covers the idle case and PAAR covers the active case. A device in self refresh with PASR maintains only the masked-in region; a device being refreshed by its controller with PAAR does the same. LPDDR4's support, on the part consulted, is listed for PASR only — so on that device the saving applies while idle and not while the controller is driving refresh.
The mechanism itself is a segment mask: the array is divided into regions, and a register says which regions are maintained. Everything outside is not refreshed and its contents decay.
Three properties of the mask matter, and §11's second block implements all three.
It is coarse. Regions are segments, not addresses. A single live byte in a segment requires the whole segment to be maintained.
It is not self-checking. The device does not know which regions hold live data and cannot refuse a bad mask. Nothing in the hardware detects that the operating system abandoned a region it was still using.
And the consequence is gradual. An abandoned region does not become unreadable at once. Its contents decay over an unpredictable interval, so reads return correct data for a while and then progressively do not — which, as 4.7 §4's callout says, looks like arbitrary memory corruption rather than like a refresh problem.
10. The State and Refresh Architecture
Two things the structure makes visible that the prose cannot.
Bank open? feeds the refresh and self-refresh states and nothing else. It is not an input to the power-down states, because those can be entered with a row open — that is exactly why there are two of them. It is an input to refresh and self refresh, because §3 established that an open row makes both illegal. The asymmetry in the diagram is the legality rule.
The Entry/exit cost node is also the one §6 now puts arithmetic behind. It appears among the mechanisms rather than among the inputs because it is not something the device supplies — it is a comparison the controller performs, using a prediction no part of this diagram can produce.
And all three mechanisms converge on one output. §9's callout asked which of 24.1's three quantities PASR serves; the diagram answers it for all three mechanisms at once. None of them improves energy per bit or peak bandwidth. They reduce the cost of doing nothing, which is the quantity the duty cycle makes dominant.
11. The Low-Power State Machine
// ---------------------------------------------------------------------
// lp_state_machine -- the LPDDR interface state set read from a named
// device's own bus-interface state diagram, with the illegal
// transitions of §3 explicitly forbidden.
//
// CLASSIFICATION: educational, synthesisable.
//
// RELATION TO 4.7 §7: that chapter's dram_power_state_ctrl owns the
// POLICY question -- is entering a deeper state worth its exit cost.
// This block owns the LEGALITY question -- is the transition even
// permitted. Both are needed and they are different.
//
// WHAT IT DOES NOT MODEL:
// - the device, any timing parameter (tRFCpb/tRFCab/exit latency),
// any current or energy, the array, or refresh itself
// - the POLICY of when to enter a state (4.7 §7; §5's break-even)
// - what the device actually does if an illegal command arrives,
// which is not defined to be a clean rejection (§3's callout)
//
// THE STATE SET IS THE DEVICE'S. It is not a generic hierarchy, and
// the two power-down states are separate because their obligations
// differ (§1).
// ---------------------------------------------------------------------
module lp_state_machine #(
// Exit latency per deep state, in abstract ticks. Published by
// datasheets as a LATENCY (not an energy), so that is what this
// takes -- §5's callout on why no energy figure is invented.
parameter int EXIT_PD = 2,
parameter int EXIT_SR = 16,
parameter int LAT_W = (EXIT_SR <= 1) ? 1 : $clog2(EXIT_SR + 1),
parameter int CNT_W = 20
) (
input logic clk,
input logic rst_n,
// ── Device reset, which the diagram makes a state rather than an
// input condition.
input logic dev_reset_n,
// ── The commands and signals that drive transitions.
input logic cke, // low requests power down
input logic cmd_act,
input logic cmd_pre,
input logic cmd_ref,
input logic ref_all_bank, // scope of cmd_ref (§2)
input logic cmd_sre, // enter self refresh
input logic cmd_srx, // exit self refresh
input logic cmd_rdwr,
// Set while any bank holds an open row. §3: this is what makes
// refresh and self refresh illegal.
input logic any_bank_open,
// Timing completions, supplied by whoever owns the parameters.
input logic rfc_pb_done,
input logic rfc_ab_done,
// ── State, as the device names it.
output logic [3:0] state,
output logic in_low_power,
output logic refresh_obligation_is_ours,
// ── Exit accounting. §5: a deeper state costs more to leave.
output logic [LAT_W-1:0] exit_countdown,
output logic exiting,
output logic [CNT_W-1:0] cy_in_pd,
output logic [CNT_W-1:0] cy_in_sr,
output logic [CNT_W-1:0] cnt_entries_pd,
output logic [CNT_W-1:0] cnt_entries_sr,
// ── §3's forbidden set, each reported separately so a debug session
// learns WHICH rule was broken rather than that one was.
output logic err_refresh_with_bank_open,
output logic err_sre_with_bank_open,
output logic err_cmd_during_power_down,
output logic err_cmd_during_self_refresh,
output logic err_srx_without_sre,
output logic any_illegal
);
localparam logic [3:0] S_RESET = 4'd0;
localparam logic [3:0] S_IDLE = 4'd1;
localparam logic [3:0] S_IDLE_PD = 4'd2;
localparam logic [3:0] S_ACTING = 4'd3;
localparam logic [3:0] S_ACTIVE = 4'd4;
localparam logic [3:0] S_ACT_PD = 4'd5;
localparam logic [3:0] S_PRE = 4'd6;
localparam logic [3:0] S_REF_PB = 4'd7;
localparam logic [3:0] S_REF_AB = 4'd8;
localparam logic [3:0] S_SREF = 4'd9;
localparam logic [3:0] S_SR_PD = 4'd10;
initial begin
if (EXIT_PD < 1 || EXIT_SR < 1)
$fatal(1, "lp_state_machine: exit latencies must be at least 1");
if (EXIT_SR <= EXIT_PD)
// §5: a deeper state costs MORE to leave. A parameterisation
// claiming otherwise inverts the trade the machine exists to
// represent, and every conclusion drawn from it would be wrong.
$fatal(1, "lp_state_machine: EXIT_SR (%0d) must exceed EXIT_PD (%0d)",
EXIT_SR, EXIT_PD);
end
logic [3:0] st;
logic [LAT_W-1:0] exit_cnt;
logic [CNT_W-1:0] pd_cy, sr_cy, pd_n, sr_n;
logic e_ref_open, e_sre_open, e_cmd_pd, e_cmd_sr, e_srx;
// Any command at all, for the "commands are not accepted here"
// checks. Declared above the assigns that read it.
logic any_cmd;
assign any_cmd = cmd_act | cmd_pre | cmd_ref | cmd_sre | cmd_rdwr;
logic is_pd, is_sr;
assign is_pd = (st == S_IDLE_PD) || (st == S_ACT_PD);
assign is_sr = (st == S_SREF) || (st == S_SR_PD);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st <= S_RESET; exit_cnt <= '0;
pd_cy <= '0; sr_cy <= '0; pd_n <= '0; sr_n <= '0;
e_ref_open <= 1'b0; e_sre_open <= 1'b0;
e_cmd_pd <= 1'b0; e_cmd_sr <= 1'b0; e_srx <= 1'b0;
end else begin
e_ref_open <= 1'b0; e_sre_open <= 1'b0;
e_cmd_pd <= 1'b0; e_cmd_sr <= 1'b0; e_srx <= 1'b0;
if (!dev_reset_n) begin
st <= S_RESET;
end else begin
// ── Occupancy accounting, charged before any transition so a
// state's final tick is counted in that state.
if (is_pd && (pd_cy != {CNT_W{1'b1}})) pd_cy <= pd_cy + CNT_W'(1);
if (is_sr && (sr_cy != {CNT_W{1'b1}})) sr_cy <= sr_cy + CNT_W'(1);
// ── Exit latency. A deep state is left over several ticks, and
// commands are not accepted during them.
if (exit_cnt != '0) begin
exit_cnt <= exit_cnt - LAT_W'(1);
end else begin
unique case (st)
S_RESET : if (dev_reset_n) st <= S_IDLE;
S_IDLE : begin
// §3: refresh and self refresh require closed banks. The
// check is on any_bank_open rather than on the state,
// because a bank can be open while the interface state
// is Idle in a multi-bank device.
if (cmd_ref && any_bank_open) e_ref_open <= 1'b1;
else if (cmd_ref) st <= ref_all_bank ? S_REF_AB : S_REF_PB;
else if (cmd_sre && any_bank_open) e_sre_open <= 1'b1;
else if (cmd_sre) begin
st <= S_SREF;
if (sr_n != {CNT_W{1'b1}}) sr_n <= sr_n + CNT_W'(1);
end
else if (cmd_act) st <= S_ACTING;
else if (!cke) begin
st <= S_IDLE_PD;
if (pd_n != {CNT_W{1'b1}}) pd_n <= pd_n + CNT_W'(1);
end
else if (cmd_srx) e_srx <= 1'b1;
end
S_ACTING : st <= S_ACTIVE;
S_ACTIVE : begin
// §3: refresh and self refresh from an active state are
// illegal. Reported, and the state does NOT change --
// pretending the transition happened would hide the bug.
if (cmd_ref) e_ref_open <= 1'b1;
else if (cmd_sre) e_sre_open <= 1'b1;
else if (cmd_pre) st <= S_PRE;
else if (!cke) begin
st <= S_ACT_PD;
if (pd_n != {CNT_W{1'b1}}) pd_n <= pd_n + CNT_W'(1);
end
end
S_PRE : st <= S_IDLE;
// §3: a state is left the way it was entered. Raising CKE
// starts an exit that takes EXIT_PD ticks, and commands
// arriving during those ticks are not accepted.
S_IDLE_PD : begin
if (any_cmd) e_cmd_pd <= 1'b1;
if (cke) begin st <= S_IDLE; exit_cnt <= LAT_W'(EXIT_PD); end
end
S_ACT_PD : begin
if (any_cmd) e_cmd_pd <= 1'b1;
if (cke) begin st <= S_ACTIVE; exit_cnt <= LAT_W'(EXIT_PD); end
end
S_REF_PB : if (rfc_pb_done) st <= S_IDLE;
S_REF_AB : if (rfc_ab_done) st <= S_IDLE;
S_SREF : begin
// Only SRX leaves self refresh, and it returns to IDLE
// rather than to an active row (§3).
if (any_cmd) e_cmd_sr <= 1'b1;
if (cmd_srx) begin st <= S_IDLE; exit_cnt <= LAT_W'(EXIT_SR); end
else if (!cke) st <= S_SR_PD;
end
S_SR_PD : begin
if (any_cmd) e_cmd_sr <= 1'b1;
if (cke) st <= S_SREF;
end
default : st <= S_RESET;
endcase
end
end
end
end
assign state = st;
assign in_low_power = is_pd || is_sr;
// §4's third row: in power down the obligation is still the
// controller's; in self refresh it is not.
assign refresh_obligation_is_ours = !is_sr;
assign exit_countdown = exit_cnt;
assign exiting = (exit_cnt != '0);
assign cy_in_pd = pd_cy;
assign cy_in_sr = sr_cy;
assign cnt_entries_pd = pd_n;
assign cnt_entries_sr = sr_n;
assign err_refresh_with_bank_open = e_ref_open;
assign err_sre_with_bank_open = e_sre_open;
assign err_cmd_during_power_down = e_cmd_pd;
assign err_cmd_during_self_refresh = e_cmd_sr;
assign err_srx_without_sre = e_srx;
assign any_illegal = e_ref_open | e_sre_open | e_cmd_pd | e_cmd_sr | e_srx;
endmoduleThree decisions in that block are §3's argument rather than implementation detail.
An illegal transition is reported and the state does not change. Not silently ignored, and not performed anyway. Advancing the state would make the model diverge from the device; ignoring it silently would hide a controller bug. Reporting and holding does neither.
The five error outputs are separate rather than one flag. A debug session needs to know which rule was broken — refresh with a bank open is a scheduler problem, a command during self refresh is a sequencing problem, and SRX without SRE is a state-tracking problem. One flag would collapse three different investigations into one.
And the elaboration guard requires EXIT_SR > EXIT_PD. §5 established that a deeper state costs more to leave. A parameterisation claiming otherwise does not describe a shallower deep state — it inverts the trade the machine exists to represent, so every break-even conclusion drawn from it would point the wrong way.
12. The Partial-Array Mask
// ---------------------------------------------------------------------
// pasr_segment_mask -- which array segments are maintained, and
// detection of accesses to ones that are not.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT DOES NOT MODEL:
// - retention time, temperature, or decay. It CANNOT say when an
// abandoned segment's contents become wrong (§9: gradual and
// unpredictable). A block predicting that would be inventing the
// property that makes the mechanism dangerous.
// - the software contract itself. 4.7 §4 owns the argument that
// correctness here depends on the operating system keeping a
// promise, and no hardware can verify it.
//
// WHAT IT ADDS: the device cannot refuse a bad mask (§9), so the
// CONTROLLER can at least NOTICE an access to an abandoned segment.
// That detection does not make the mechanism safe -- it makes a
// violation visible instead of silent, which is the difference
// between a diagnosable failure and arbitrary corruption.
// ---------------------------------------------------------------------
module pasr_segment_mask #(
parameter int SEGMENTS = 8,
parameter int ADDR_W = 32,
// Address bits that select the segment. The segment granularity is
// COARSE (§9): one live byte in a segment requires the whole
// segment, and this parameter is where that coarseness lives.
parameter int SEG_LSB = 26,
parameter int SEG_W = (SEGMENTS <= 1) ? 1 : $clog2(SEGMENTS),
// A COUNT of segments up to SEGMENTS needs clog2(SEGMENTS+1).
parameter int SEGC_W = $clog2(SEGMENTS + 1),
parameter int CNT_W = 20
) (
input logic clk,
input logic rst_n,
// ── Mask programming. Bit set = segment is MAINTAINED.
input logic mask_write,
input logic [SEGMENTS-1:0] mask_in,
// ── Accesses, for the detection of §12's header.
input logic acc_valid,
input logic [ADDR_W-1:0] acc_addr,
// ── Whether the device is in a state where the mask applies. PASR
// governs SELF refresh; PAAR governs controller-issued refresh.
// Both are inputs because which exists is generation-specific
// (§9's table) and this block does not decide it.
input logic in_self_refresh,
input logic paar_enabled,
// ── State.
output logic [SEGMENTS-1:0] mask,
output logic [SEGC_W-1:0] n_maintained,
output logic [SEGC_W-1:0] n_abandoned,
// Fraction maintained, as a PAIR. No division: the caller states
// its denominator, as every accountant in this module does.
output logic [SEGC_W-1:0] maintained_num,
output logic [SEGC_W-1:0] maintained_den,
// ── Detection. The whole point of the block.
output logic acc_to_abandoned,
output logic [SEG_W-1:0] abandoned_seg,
output logic [CNT_W-1:0] cnt_abandoned_acc,
// Sticky: once an abandoned segment has been read, the data
// returned from it is untrustworthy FOREVER after, because §9 says
// decay is progressive rather than instantaneous.
output logic trust_lost,
output logic err_mask_all_zero,
output logic err_mask_changed_in_sr
);
initial begin
if (SEGMENTS < 2)
$fatal(1, "pasr_segment_mask: SEGMENTS must be at least 2");
if (SEG_LSB + SEG_W > ADDR_W)
$fatal(1, "pasr_segment_mask: segment field exceeds ADDR_W");
end
logic [SEGMENTS-1:0] mask_q;
logic [CNT_W-1:0] n_bad;
logic lost, bad_zero, changed_sr, hit_bad;
logic [SEG_W-1:0] bad_seg;
// Declared above the assigns that read it.
logic [SEG_W-1:0] seg_of_acc;
assign seg_of_acc = acc_addr[SEG_LSB +: SEG_W];
logic [SEGC_W-1:0] n_keep;
always_comb begin
n_keep = '0;
for (int s = 0; s < SEGMENTS; s++)
if (mask_q[s]) n_keep = n_keep + SEGC_W'(1);
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// SAFE STATE: everything maintained. A reset that abandoned
// segments would lose data before software had said anything.
mask_q <= {SEGMENTS{1'b1}};
n_bad <= '0;
lost <= 1'b0;
bad_zero <= 1'b0;
changed_sr <= 1'b0;
hit_bad <= 1'b0;
bad_seg <= '0;
end else begin
hit_bad <= 1'b0;
changed_sr <= 1'b0;
if (mask_write) begin
mask_q <= mask_in;
// An all-zero mask maintains nothing. Legal to program and
// never intended: it abandons the entire array.
bad_zero <= (mask_in == '0);
// Changing the mask while the device is self-refreshing means
// segments change maintenance status mid-interval, so what was
// retained is no longer knowable.
if (in_self_refresh) changed_sr <= 1'b1;
end
if (acc_valid && !mask_q[seg_of_acc]) begin
hit_bad <= 1'b1;
bad_seg <= seg_of_acc;
lost <= 1'b1; // sticky, per §12's header
if (n_bad != {CNT_W{1'b1}}) n_bad <= n_bad + CNT_W'(1);
end
end
end
assign mask = mask_q;
assign n_maintained = n_keep;
assign n_abandoned = SEGC_W'(SEGMENTS) - n_keep;
assign maintained_num = n_keep;
assign maintained_den = SEGC_W'(SEGMENTS);
assign acc_to_abandoned = hit_bad;
assign abandoned_seg = bad_seg;
assign cnt_abandoned_acc = n_bad;
assign trust_lost = lost;
assign err_mask_all_zero = bad_zero;
// Reported whenever the mask changes in self refresh, regardless of
// whether PAAR is also enabled -- the hazard is the mid-interval
// change, not which mechanism was active.
assign err_mask_changed_in_sr = changed_sr;
endmoduletrust_lost is sticky and that is deliberate. §9 established that an abandoned segment's contents decay progressively, so a read that happened to return plausible data does not mean the data was correct, and a later read of the same location may differ. Once an abandoned segment has been accessed, no subsequent read from that region can be trusted, and a flag that cleared would imply recovery that did not occur.
The reset state maintains everything. A mask that came up with segments abandoned would lose data before software had said anything about which regions were live — which inverts the contract 4.7 §4 describes, where software must consolidate before abandoning.
13. What the Assertions Prove
// Bind unit note: P1-P7 reference lp_state_machine's internal state
// and P8-P11 pasr_segment_mask's; both are written as though bound
// into their module, so clk and rst_n are visible directly.
// P1 -- FORBIDDEN TRANSITION. Refresh is never entered with a bank
// open. §3, and §15 names this property class the highest-value one
// in this module.
property p_no_refresh_with_bank_open;
@(posedge clk) disable iff (!rst_n)
(any_bank_open && cmd_ref) |=> ((state != 4'd7) && (state != 4'd8));
endproperty
assert property (p_no_refresh_with_bank_open);
// P2 -- FORBIDDEN TRANSITION. Self refresh is never entered from an
// active state or with a bank open.
property p_no_self_refresh_from_active;
@(posedge clk) disable iff (!rst_n)
((state == 4'd4) && cmd_sre) |=> (state != 4'd9);
endproperty
assert property (p_no_self_refresh_from_active);
// P3 -- FORBIDDEN TRANSITION. Self refresh exits to Idle, never
// directly to an active row.
property p_srx_exits_to_idle;
@(posedge clk) disable iff (!rst_n)
((state == 4'd9) && cmd_srx) |=> ((state == 4'd1) || (state == 4'd9));
endproperty
assert property (p_srx_exits_to_idle);
// P4 -- FORBIDDEN TRANSITION. A power-down state is never left
// directly into an activation; the exit is a step (§3).
property p_no_activate_from_power_down;
@(posedge clk) disable iff (!rst_n)
(((state == 4'd2) || (state == 4'd5)) && cmd_act) |=> (state != 4'd3);
endproperty
assert property (p_no_activate_from_power_down);
// P5 -- an illegal request is reported AND the state holds. Reporting
// without holding would let the model diverge from the device;
// holding without reporting would hide the controller's bug (§11).
property p_illegal_reported_and_held;
@(posedge clk) disable iff (!rst_n)
(err_refresh_with_bank_open || err_sre_with_bank_open)
|-> (state == $past(state, 1));
endproperty
assert property (p_illegal_reported_and_held);
// P6 -- §4's third row, as an invariant. The refresh obligation is the
// controller's in every state except self refresh.
property p_obligation_matches_state;
@(posedge clk) disable iff (!rst_n)
refresh_obligation_is_ours == !((state == 4'd9) || (state == 4'd10));
endproperty
assert property (p_obligation_matches_state);
// P7 -- no command is accepted while an exit is in progress. §5: a
// deeper state is left over several ticks, and this is what makes the
// exit cost real rather than notional.
property p_no_transition_while_exiting;
@(posedge clk) disable iff (!rst_n)
exiting |=> (state == $past(state, 1));
endproperty
assert property (p_no_transition_while_exiting);
// P8 -- the maintained and abandoned counts partition the segments.
property p_segments_partition;
@(posedge clk) disable iff (!rst_n)
((n_maintained + n_abandoned) == SEGC_W'(SEGMENTS));
endproperty
assert property (p_segments_partition);
// P9 -- an access to an unmaintained segment is always detected. The
// block's entire purpose: a violation must be visible rather than
// silent (§12's header).
property p_abandoned_access_detected;
@(posedge clk) disable iff (!rst_n)
(acc_valid && !mask[acc_addr[SEG_LSB +: SEG_W]]) |=> acc_to_abandoned;
endproperty
assert property (p_abandoned_access_detected);
// P10 -- trust, once lost, is never regained. §12: decay is
// progressive, so a plausible read proves nothing.
property p_trust_loss_is_sticky;
@(posedge clk) disable iff (!rst_n)
$past(trust_lost, 1) |-> trust_lost;
endproperty
assert property (p_trust_loss_is_sticky);
// P11 -- the mask comes out of reset maintaining everything. A reset
// that abandoned segments would lose data before software spoke.
property p_reset_maintains_all;
@(posedge clk)
$rose(rst_n) |-> (mask == {SEGMENTS{1'b1}});
endproperty
assert property (p_reset_maintains_all);
// P12 -- the three verdicts are mutually exclusive and one always
// holds. "Neither" is a real answer (§6's d=1 row), so the verdict is
// a genuine three-way partition rather than two flags.
property p_verdict_is_a_partition;
@(posedge clk) disable iff (!rst_n)
($onehot({pick_shallow, pick_deep, pick_none}));
endproperty
assert property (p_verdict_is_a_partition);
// P13 -- a state is never entered at a loss. The whole point of §6:
// at short durations the deep state SPENDS to save nothing, and the
// selector must decline rather than pick the better loser.
property p_never_enter_at_a_loss;
@(posedge clk) disable iff (!rst_n)
(pick_shallow || pick_deep) |-> (net_chosen > 0);
endproperty
assert property (p_never_enter_at_a_loss);
// P14 -- the chosen state is the better of the two whenever either
// is worth entering. Rules out a selector that declines a profitable
// choice or takes the worse one.
property p_choice_is_maximal;
@(posedge clk) disable iff (!rst_n)
pick_deep |-> (net_deep >= net_shallow) and
pick_shallow |-> (net_shallow >= net_deep);
endproperty
assert property (p_choice_is_maximal);
// P15 -- §6's second reading, as a property. Being past one's own
// break-even does NOT imply being the better choice, so the two
// outputs must be allowed to disagree -- and when they do, the
// shallow state is the one picked.
property p_breakeven_does_not_imply_better;
@(posedge clk) disable iff (!rst_n)
(deep_past_own_breakeven && !deep_beats_shallow) |-> !pick_deep;
endproperty
assert property (p_breakeven_does_not_imply_better);
// P16 -- regret is never negative. It measures the choice not taken,
// and a negative regret would mean the selector knew it chose badly.
property p_regret_non_negative;
@(posedge clk) disable iff (!rst_n) (regret >= 0);
endproperty
assert property (p_regret_non_negative);
// ── Cover.
cover property (@(posedge clk) disable iff (!rst_n) state == 4'd9); // self refresh
cover property (@(posedge clk) disable iff (!rst_n) state == 4'd10); // SR power down
cover property (@(posedge clk) disable iff (!rst_n) state == 4'd2); // idle PD
cover property (@(posedge clk) disable iff (!rst_n) state == 4'd5); // active PD
// Both refresh scopes exercised -- §2's distinction.
cover property (@(posedge clk) disable iff (!rst_n) state == 4'd7);
cover property (@(posedge clk) disable iff (!rst_n) state == 4'd8);
// Each forbidden transition ATTEMPTED. A suite that never attempts one
// has not tested the property that forbids it.
cover property (@(posedge clk) disable iff (!rst_n) err_refresh_with_bank_open);
cover property (@(posedge clk) disable iff (!rst_n) err_sre_with_bank_open);
cover property (@(posedge clk) disable iff (!rst_n) err_cmd_during_self_refresh);
// A partial mask actually in force, and an access to an abandoned
// segment actually detected.
cover property (@(posedge clk) disable iff (!rst_n)
(n_abandoned != '0) && (n_maintained != '0));
cover property (@(posedge clk) disable iff (!rst_n) acc_to_abandoned);
// All three selector verdicts reached. "None" is the one a suite is
// likeliest to miss, and §6's d=1 row is exactly when it happens.
cover property (@(posedge clk) disable iff (!rst_n) pick_shallow);
cover property (@(posedge clk) disable iff (!rst_n) pick_deep);
cover property (@(posedge clk) disable iff (!rst_n) pick_none);
// The window §6 names: past the deep state's own break-even and still
// the worse choice. A suite that never enters it has not tested P15.
cover property (@(posedge clk) disable iff (!rst_n)
deep_past_own_breakeven && !deep_beats_shallow);
// The deep state evaluated at an outright loss -- the negative net.
cover property (@(posedge clk) disable iff (!rst_n) net_deep < 0);P1 through P4 are the class §15 identifies as the highest-value here, and they are worth distinguishing from the obvious alternative. Asserting that the legal transitions happen is weaker than asserting the illegal ones cannot. The first holds for a machine that also permits things it should not; only the second constrains the complement.
The three “forbidden transition attempted” covers matter for the same reason. A property forbidding something is vacuous on a testbench that never attempts it, so the covers make the properties' non-vacuity a checked fact rather than an assumption.
14. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
REF with a bank open | Reported; state holds | §3's first rule — P1 and P5 |
SRE from Bank Active | Reported; state holds | Self refresh is entered from Idle only |
| Command during power down | err_cmd_during_power_down | Exit is a step, not a side effect — 6.2 |
SRX without having entered self refresh | err_srx_without_sre | A state-tracking error in the controller |
CKE low from Idle vs from Active | Two different states entered | §1 — their obligations differ |
CKE low while in self refresh | Enters SR Power Down | A further state below self refresh, per the diagram |
| Exit in progress, command arrives | No transition; P7 holds | The exit cost is real ticks |
EXIT_SR <= EXIT_PD | $fatal at elaboration | Inverts §5's trade; every break-even conclusion would reverse |
| Mask programmed all-zero | err_mask_all_zero; mask applied | Legal to program, never intended — abandons everything |
| Mask changed during self refresh | err_mask_changed_in_sr | What was retained mid-interval is no longer knowable |
| Access to an abandoned segment | Detected; trust_lost sticky | Decay is progressive — a plausible read proves nothing |
| One live byte in an abandoned segment | Undetectable by hardware | §9 — the mask is coarse and not self-checking |
| Reset with a partial mask programmed | Mask returns to all-maintained | P11 — the safe direction |
d_pred short enough that both states lose | pick_none; both nets reported | §6 — “neither” is a real answer, P13 |
d_pred exactly at the crossover | Tie goes to the shallow state | Less exposure if the prediction was optimistic — §5's asymmetry |
| Deep state past its own break-even but behind the shallow one | pick_shallow; both flags reported | §6's second reading — P15 |
e_deep <= e_shallow supplied at runtime | err_deep_cheaper_to_exit | Same inversion §10 catches at elaboration, here an input |
| Prediction alternately too long and too short | Selector oscillates; regret accumulates | §5's asymmetry realised — the predictor, not the selector, is at fault |
Row twelve is the honest limit and it is worth stating as a corner case rather than burying it. Hardware cannot detect that software abandoned a segment it was still using until something reads it — and by then the contents have been decaying for an unknown interval. The detection in §12 catches the access, not the mistake, and those are separated in time by however long the region sat unread.
15. DV — Drive the Illegal Set Deliberately
The testbench's main job here is unusual: most of its value is in attempting things that must not work. A suite that only drives legal sequences leaves P1 through P4 vacuous.
// Independent legality reference. Holds the permitted transitions as
// an explicit adjacency table read from the device's state diagram,
// rather than as a case statement -- a different representation, so
// agreement is evidence rather than a restatement.
// SIMULATION-ONLY.
class lp_legality_ref;
// legal[from][to] = 1 if the diagram has that edge.
bit legal [11][11];
function void build();
foreach (legal[i,j]) legal[i][j] = 0;
legal[0][1] = 1; // RESET -> IDLE
legal[1][2] = 1; legal[1][3] = 1; // IDLE -> IDLE_PD, ACTING
legal[1][7] = 1; legal[1][8] = 1; // IDLE -> REF_PB, REF_AB
legal[1][9] = 1; // IDLE -> SREF
legal[3][4] = 1; // ACTING -> ACTIVE
legal[4][5] = 1; legal[4][6] = 1; // ACTIVE -> ACT_PD, PRE
legal[6][1] = 1; // PRE -> IDLE
legal[2][1] = 1; // IDLE_PD-> IDLE
legal[5][4] = 1; // ACT_PD -> ACTIVE
legal[7][1] = 1; legal[8][1] = 1; // REF_* -> IDLE
legal[9][1] = 1; legal[9][10] = 1; // SREF -> IDLE, SR_PD
legal[10][9] = 1; // SR_PD -> SREF
endfunction
function bit permitted(int f, int t);
return (f == t) || legal[f][t];
endfunction
endclass| Check | What it establishes |
|---|---|
Every observed transition is permitted() by the table | Two representations of one diagram agree |
Drive REF with any_bank_open; expect the error and no state change | P1 and P5, driven rather than assumed |
Drive SRE from Bank Active; same | P2 |
Drive cmd_act from both power-down states; expect no activation | P4 |
Drive SRX from Idle; expect err_srx_without_sre | A state-tracking error is visible |
| Enter each of the four low-power states at least once | The four state covers |
| Enter both refresh states; confirm each exits on its own completion | §2's two scopes are distinct |
Hold cke low from self refresh; expect SR Power Down | The state below self refresh |
Sweep EXIT_SR; confirm no transition during the countdown | P7 over the parameter range |
Mask: abandon a segment, access it, confirm sticky trust_lost | P9 and P10 |
| Mask: change the mask during self refresh; expect the error | §14's row ten |
| Reset with a partial mask; confirm all-maintained on release | P11 |
Sweep d_pred across §6's table; compare against the arithmetic in a reference | The three-way verdict at every duration |
Drive d_pred = 1 with §6's rates; expect pick_none and net_deep < 0 | P13 and the negative-net cover |
| Drive the crossover duration exactly; confirm the tie resolves shallow | §13's second new row |
| Drive the window between the deep break-even and the crossover | P15 non-vacuous |
Supply e_deep <= e_shallow; expect the error and no silent inversion | The runtime form of §10's guard |
The second and third checks produce the report worth publishing, because they demonstrate a failure mode that a legal-only testbench would never see:
THE LEGAL-ONLY TESTBENCH THAT PROVES NOTHING
A suite that drives only permitted sequences.
result: every assertion PASSES.
P1 no refresh with bank open PASS (never attempted)
P2 no self refresh from active PASS (never attempted)
P3 srx exits to idle PASS
P4 no activate from power down PASS (never attempted)
coverage: err_refresh_with_bank_open NEVER HIT
err_sre_with_bank_open NEVER HIT
err_cmd_during_self_refresh NEVER HIT
diagnosis : P1, P2 and P4 are VACUOUS. Their antecedents never
became true, so they held trivially and constrained nothing.
The design could permit every illegal transition and this suite
would still be green.
what makes it dangerous : the pass is not a weak signal, it is a
MISLEADING one. Three properties reported success while
verifying nothing, and the only evidence of the problem is in
the coverage report rather than in the results.
the fix : the three "forbidden transition attempted" covers of
§13 are the guard. A suite that does not hit them has not
exercised the property class §15 calls the most valuable here,
and the covers turn that from a judgement into a measurement.That is the right lesson for a chapter built on forbidden transitions. A property that forbids something is only as strong as the testbench's willingness to attempt it, and the covers exist to make the attempt auditable.
16. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
| A command had no effect | Issued during power down or its exit | err_cmd_during_power_down; 6.2 §9 owns this failure |
| Refresh rejected or device misbehaves | Bank open when REF was issued | err_refresh_with_bank_open; precharge first |
| Device never enters self refresh | A bank is still open | err_sre_with_bank_open; the scheduler must close rows |
| Idle power higher than expected | The device is in power down, not self refresh | Check which state; §4 — the obligation is still yours |
| Frequent wakeups, poor battery life | Entering a deep state for short idles — §5 | cnt_entries_sr against cy_in_sr; short average occupancy |
| Deep state entered rarely | Predictor too conservative | Same two counters, opposite reading |
| Per-bank refresh gave no power saving | Expected — §8 | It buys availability, not energy |
| Per-bank refresh gave no availability gain | The scheduler is not using the free banks | That is Module 23's territory |
| Idle current much worse when the device is warm | Temperature — §7 | Up to a 22.4× spread on a named part |
| Sporadic memory corruption after a power event | An abandoned PASR segment was read | trust_lost, cnt_abandoned_acc; §9 |
| Corruption appears gradually rather than at once | Decay is progressive — §9 | Characteristic of PASR misuse, not of a logic bug |
| Deep state entered for very short idles; battery worse than before | Selector comparing rates, not net savings — §6 | net_deep negative while the state was entered; P13 would have caught it |
| Device enters and leaves a deep state repeatedly | Prediction wrong in both directions alternately | regret accumulating with short cy_in_sr per entry |
| Deep state never chosen despite long idles | d_pred clamped or the deep cost overstated | Compare net_deep against net_shallow directly |
| Assertions all pass and the design is wrong | A legal-only testbench — §15 | Check the forbidden-transition covers, not the results |
Row seven is the one this chapter exists to put in a debugging table. “Per-bank refresh gave no power saving” is not a bug report — it is the expected behaviour on the device measured in §8, and an engineer who does not know that will spend time looking for a fault that is not there.
Row eleven is the signature worth memorising. Logic bugs tend to fail deterministically; decay fails gradually, so corruption that worsens over minutes after an idle period points at retention rather than at logic.
17. Misconceptions
“Low-power states are a generic hierarchy.” §1. A real device publishes a specific set with specific names, two distinct power-down states, and a self-refresh branch with its own sub-state.
“Power down and self refresh are the same idea at different depths.” §4. They differ in who refreshes: in power down the obligation is still the controller's; in self refresh the device takes it over.
“You can enter self refresh whenever you like.” §3. Not with a bank open. Self refresh is entered from Idle, so rows must be closed first.
“Raising CKE makes the next command land.” §3. Exit is a step that takes time, and commands during it are not accepted — 6.2's qualification contract.
“A deeper state is better.” §5. It is better for longer idles. The break-even duration is the exit cost divided by the saving rate, and entering for a shorter idle loses.
“Per-bank refresh saves refresh power.” §8. On the named LPDDR5X part, IDD5PB equals IDD5AB on every rail. It buys availability.
“Per-bank refresh refreshes less of the array.” §8. It refreshes the same total in smaller pieces. The energy to refresh a bank does not change by doing it alone.
“Self-refresh current is a single number.” §7. On a named part it spans more than a factor of twenty between 25 °C and 95 °C — and even that ratio compares a typical with a worst case.
“PASR saves energy per bit.” §9's callout. It reduces the cost of doing nothing. Bits that move cost what they cost.
“Hardware will stop you abandoning a region you still need.” §9. The device cannot refuse a bad mask. §12's block can notice an access afterwards, which is detection, not prevention.
“An abandoned region fails cleanly.” §9 and §14's row eleven. It decays progressively, returning correct data for a while and then not — which looks like arbitrary corruption.
“Pick whichever state saves more per tick.” §6. At short durations the deeper state has a negative net saving — it spends to save nothing — and a rate comparison picks it anyway.
“Once a state is past its break-even it is the state to enter.” §6. Past its own break-even it is worth entering; it is not necessarily the better of the two, and there is a window where both are true of the shallow state and only the first of the deep one.
“All assertions passing means the state machine is right.” §15. A legal-only testbench leaves the forbidden-transition properties vacuous, and three of them can pass while constraining nothing.
18. Interview Reasoning
Name the low-power states a real LPDDR4 device defines. From a named device's own state diagram: two power-down states — idle and active, differing by whether a row is open — plus a self-refresh state with a further power-down beneath it, alongside the operating and refresh states.
What is the difference between power down and self refresh? Who refreshes. In power down the controller still owns the refresh obligation and must exit, refresh and re-enter on schedule. In self refresh the device takes over, which is why it is the only viable state for a long idle.
Which transitions are illegal, and what do they have in common? Refresh or self-refresh entry with a bank open, activation directly out of power down, and self-refresh exit straight to an active row. All of them try to leave a state sideways rather than through its defined exit.
Why assert the forbidden transitions rather than the permitted ones? Because asserting that legal transitions happen also holds for a machine that permits illegal ones too. Only forbidding the complement constrains it — and the forbidden properties are vacuous unless the testbench attempts them.
When is a deeper low-power state worth entering? When the saving rate times the idle duration exceeds the exit cost. The break-even duration is exit cost over saving rate, and the idle duration is a prediction the memory cannot make.
What does per-bank refresh buy? Availability. On the named LPDDR5X part its current is identical to all-bank refresh on every supply rail, so it does not save energy — it converts a device-wide stall into a partial one.
Why would anyone expect it to save energy, and why doesn't it? Because it refreshes one bank rather than eight. But it refreshes the same total array in smaller pieces over the same interval, and the energy per bank does not change by doing banks separately.
How much does temperature cost in self refresh? On a named LPDDR5X part, the sum of the two active rails goes from 0.70 mA at 25 °C to 15.70 mA at 95 °C — a factor of 22.4. That figure compares a typical with a worst case, so it is an upper bound on the spread.
A deep state saves more per tick than a shallow one. Is it always the better choice for an idle period? No. The net saving is rate times duration minus exit cost, and at short durations the deep state's net is negative — entering it costs more than staying active. The duration at which it becomes the better of the two is later than its own break-even point.
What makes partial-array self refresh dangerous rather than merely limited? Its correctness depends on software keeping a promise hardware cannot verify, and violating it loses data gradually rather than failing cleanly — so it presents as arbitrary corruption rather than as a refresh problem.
19. Exercises
-
Using §5's break-even relation, compute the minimum idle duration that justifies self refresh over power down given the block's default exit latencies, stating every assumption you must add to make the comparison well posed. Which assumption is the one the datasheets do not supply?
-
§8 shows
IDD5PBequal toIDD5ABon every rail. Construct the argument for why a per-bank scheme might still reduce peak current, then state precisely what a datasheet would have to publish for the argument to be checkable. -
§7's 22.4× compares a typical figure with a worst-case one. Derive the range the true typical-to-typical ratio must lie in, given only that a worst case is at least as large as a typical.
-
Remove the
EXIT_SR > EXIT_PDelaboration guard and set the two equal. Which of §5's conclusions reverses, and which property, if any, catches the inversion? Propose the property that would. -
Drive a legal-only sequence against §11's machine and report which of P1 through P11 become vacuous. Then write the minimal stimulus that makes all four forbidden-transition properties non-vacuous.
-
§12's
trust_lostis sticky and never clears. Specify what a system would have to do for clearing it to be honest, and say which part of that is outside the hardware's reach. -
The segment mask is coarse. For a mask of 8 segments over a 4 GB array, compute the granularity and describe the worst-case waste when a single live page sits in an otherwise-dead segment.
-
Using §6's illustrative rates, find the duration at which the deep state's net saving reaches twice the shallow state's, then show that no such duration exists if the deep state's rate is lowered to 4 units per tick. What does the second result say about when a deeper state is worth designing?
-
§6's selector resolves a tie toward the shallow state. Construct the idle-duration distribution for which the opposite tie-break yields more total saving, and say what the selector would need to know to justify it.
-
LPDDR5 adds PAAR alongside PASR while the named LPDDR4 part lists PASR only. Work out which of 24.1 §2's three quantities PAAR serves that PASR does not, and construct the workload where the difference is largest.
20. Where This Goes
The idle side of mobile memory is now concrete. The states are a named device's own, two of them differ only by whether a row is open, self refresh is a separate branch rather than a deeper power-down, and several plausible transitions between them are illegal in a way a generic hierarchy cannot express.
Four results carry forward. A deeper state is better for longer idles and worse for short ones, with a break-even duration that is exit cost over saving rate. Below that duration the deeper state is not merely less good but actively costly — §6's worked case spends six units to save nothing — and the duration at which it becomes the better of two states is later than its own break-even, which leaves a window where a rate-comparing policy picks wrongly. Temperature is the largest single idle-power lever on the device measured — up to a 22.4× spread, bounded by the typical-versus-worst-case caveat. And per-bank refresh does not save energy: it buys availability, which is a performance property rather than a power one.
One structural point is worth carrying too. The three blocks here answer three different questions about the same decision — is the transition permitted (§10), is it worth making (§6), and what does the mechanism cost in correctness (§11) — and none of them can answer another's. A controller needs all three, and conflating any two produces a design that is either illegal, uneconomic, or unsafe.
One question is left genuinely open, and it is recorded rather than glossed. §8 established that average refresh currents are identical between the two scopes, and noted that peak current might still differ — which matters for supply design. The datasheets consulted do not publish a peak-current comparison, so this module cannot say. It is a real gap, not a rhetorical one.
What every section here has assumed is the command interface: that a REF, an SRE or an ACT simply arrives. On this family it does not simply arrive. Chapter 4.7 §5 established that LPDDR4 organises a device as two independent narrow channels and why the requester mix makes that attractive. What neither that chapter nor this one has said is what a command on such a channel actually looks like — and it is not what any DDR chapter in this curriculum would lead you to expect.
Chapter 24.3 takes it up: a six-bit command and address bus, commands spread across one, two or four clock cycles, sampled on one edge only, and two channels that must be genuinely independent on a single die. The decoder for that is a different object from anything in Module 7, and building it is where the family's pin-count discipline becomes visible.
Continue learning
Related tutorials
- Related topic
LPDDR Evolution
LPDDR is not low-power DDR. It is the same reasoning run against a different constraint set — no socket, a fixed battery, no airflow — which produces low-swing signalling, split command and data clocks, and power states expensive enough to reshape the controller.
- 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.
- Related topic
Mobile-Memory Mission
A phone asks three different things of its memory and they trade against each other. At a 2% duty cycle nearly half the average power is spent idle, and below 1% it is two thirds.
- Related topic
Mobile-Power Management
LPDDR4X lowers two rails and deletes a third. Two of four LPDDR5X rails go unused in self refresh — so gating them saves nothing in the memory, and a bad shutdown order survives ten thousand clean power cycles.
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.
