DDR · Module 4
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.
Chapters 4.1 to 4.6 followed one chain, and every link optimised the same objective under the same constraints: bandwidth per channel, for a device in a socket on a board, drawing from a large supply, cooled by moving air, expected to be replaceable and interchangeable between vendors.
This chapter changes the constraints and re-runs the reasoning.
A memory device in a battery-powered product is soldered down, shares a package or a substrate with the processor, has no airflow worth the name, and draws from a fixed quantity of stored energy. Under those constraints the objective is not bandwidth per channel. It is bandwidth per joule, with an absolute ceiling on power set by what the package can shed as heat.
Same reasoning, different answers. And the most interesting thing about the result is that the constraint that makes the problem harder is the same one that makes the solution possible — which is §1's subject and the key to the whole chapter.
1. The Constraint That Cuts Both Ways
Start with the property that looks purely like a disadvantage: the device is not replaceable. It is soldered, often stacked with or beside the processor. Capacity is fixed at manufacture. There is no upgrade path, no second vendor after the fact, no socket.
That costs real things. It also removes the reason the DDR chain's electrical problems were hard.
Go back through what made DDR2 and DDR3 difficult. A socket is a discontinuity. A replaceable module means an unknown population. Several devices on a shared bus means several loads. A bus that must work with one module or four means a topology that works across configurations. Every one of those problems exists because the memory is replaceable, and 4.3 §3's reflections, 4.4 §2's stubs and fly-by routing, and the whole apparatus of termination and calibration are the responses.
A soldered, point-to-point, millimetres-long connection has almost none of that. Few loads, no connector, short traces, a known and fixed configuration.
And a channel that is electrically easy can be driven with a much smaller signal swing.
The top row is the benefit chain and the bottom row is what it cost. Both edges leaving the row-0 nodes downward are real prices, and they are prices a phone is willing to pay and a server generally is not — which is the whole of why two lineages exist.
2. Low-Swing Signalling, and an Instructive Inversion
LPDDR4 adopts a signalling scheme built for a short, lightly loaded channel: low-voltage swing terminated logic. Two properties matter here.
The swing is drastically smaller. The standard specifies swing values in the region of 0.37 to 0.44 V — under half what the preceding LPDDR generation used. By the square law that is well under a quarter of the switching energy per transition.
And the termination is to ground rather than to a supply. Current flows through the termination when the line is driven high, so a line held low dissipates no termination current.
3. Splitting the Clocks — the Command Path Stops Paying for the Data Path
LPDDR5 makes a structural change whose reasoning is the most elegant in this chapter.
Recall the rate hierarchy Chapter 4.2 §8 identified. A DDR device runs at three different rates internally: the data path fastest, the command path slower, the array slower still. In the DDR chain, the command path's clock and the data path's timing are derived from the same clock. DDR1 added a strobe for the data, but the strobe's relationship to the clock still mattered, and DDR3's write leveling exists precisely because of that relationship.
The question LPDDR5 asks is why the command path should be clocked anywhere near the data rate at all.
It does not need to be. One command produces a burst of many beats, so the command path's required rate is lower than the data path's by roughly the burst length. Running it fast costs energy — clock distribution, receiver activity, switching on the command pins — for no benefit.
So LPDDR5 separates them. The command clock, CK, carries commands. A separate data clock, WCK, times the data. WCK is forwarded from the controller to the device and is used to time write data and — on reads — to generate the read strobe the device returns. In the published standard the command clock's maximum rate is a small fraction of the data clock's, which is exactly the asymmetry the burst length implies.
Three consequences are worth naming.
The command path's energy is decoupled from the data rate. Raising the data rate no longer drags the command path's clocking energy up with it — which, under a bandwidth-per-joule objective, is a first-order win rather than a refinement.
The data path's timing reference is fully source-forwarded. WCK comes from the controller with the data, and the device uses it to time what it sends back. This is DDR1's source-synchronous idea taken to its conclusion: the data path no longer shares a timing base with the command path at all, so the relationship between them stops being a constraint on the data path's rate.
And the controller gains another domain to manage. Two clocks with independent rates mean two timing domains, a defined relationship between them for commands that reference data, and more state to train and maintain. Module 24 owns that machinery — the point here is that it is the cost of the decoupling, and it lands on the controller, exactly as every generation's cost in this module has.
4. Refresh as a Managed, Partially Software-Visible Cost
Chapter 2.3 established that refresh is mandatory and consumes both time and energy. Under a bandwidth-per-joule objective, refresh energy is not an overhead to be tolerated — it is a line item to be attacked, and the LPDDR family attacks it from three directions.
Per-bank refresh. Refreshing one bank at a time leaves the others available, converting a device-wide stall into a partial one. DDR5 arrived at the same idea with same-bank refresh; the mobile family reached it earlier because the payoff was larger under its objective.
Self-refresh that adapts to temperature. Chapter 2.3 §4 established that retention time falls as temperature rises, so a fixed refresh interval must be set for the worst case. A device that senses its own temperature can refresh less often when it is cool — which, for a device idling in a pocket, is most of the time. This is the calibration pattern again: a quantity that had to be designed conservatively becomes measured and adapted.
Refreshing only the part of the array that holds live data. A device can be told to maintain only a portion of its array, leaving the rest unrefreshed and its contents lost. Refresh energy then scales with how much memory actually holds something.
5. More Channels, Each Narrower
LPDDR4 organises a device as two independent 16-bit channels rather than one wider channel, each with its own command path and bank hierarchy.
That is structurally the move DDR5 made, and the arithmetic of 4.6 §1 applies unchanged — but the motivation is different, and the difference is instructive.
DDR5 split its channel to preserve granularity while doubling the burst length. The split was the price of a longer burst.
LPDDR splits because a narrow channel matches its requesters and its energy budget. A phone's memory traffic comes from many small, largely independent consumers — application cores, graphics, camera, display, various accelerators — each wanting modest amounts of data. A narrow channel's smaller granularity wastes less on a small access, and several independent narrow channels serve several independent requesters concurrently, which is Chapter 1.8 §7's memory-level parallelism matched to the shape of the demand.
So the same structural feature is a cost in one lineage and a benefit in the other, decided entirely by the requesters. That is worth sitting with, because it means "is a narrow channel good" has no context-free answer — and neither, therefore, does "how much granularity is too much".
6. What Actually Diverged, Layer by Layer
| Property | Mainstream DDR chain | LPDDR family | Layer |
|---|---|---|---|
| Objective | bandwidth per channel | bandwidth per joule, power-capped | — |
| Replaceable | yes, socketed modules | no, soldered / co-packaged | System |
| Channel environment | connector, several loads, unknown population | short, point-to-point, fixed | Board / package |
| Signal swing | constrained by the channel | drastically lower | Interface |
| Termination reference | to the supply (DDR4 POD12) | to ground (LVSTL) | Interface |
| Energetically free state | high | low | Consequence |
| Command vs data clocking | one clock base | separate CK and WCK (LPDDR5) | Interface |
| Channel organisation | wide, split late (DDR5) | narrow, several, split early | Device architecture |
| Refresh | scheduled by the controller | + temperature-adaptive, + partial-array | Device + software |
| Low-power states | present | deep, and central to the design | Device + controller |
| Array | unchanged | unchanged | none |
The last row is the punchline of the whole module. Two lineages, eleven generations between them, entirely different signalling, clocking, channel organisation and power behaviour — and the same 1T1C cell with the same destructive read and the same mandatory refresh underneath all of it.
7. RTL — Power States and the Cost of Leaving Them
Problem
A device with deep low-power states needs a controller that decides when to enter them and pays the cost of leaving them. The decision is a real trade: a deeper state saves more energy per idle cycle and costs more cycles to exit, so entering too eagerly on bursty traffic can cost more than it saves.
The block must make that trade measurable rather than assumed, which means accounting for both sides: the energy saved while idle and the cycles stalled on exit.
Classification
SYNTHESIZABLE RTL, with abstract relative energy weights. The state machine, the idle timers, the exit countdown and the counters are real controller logic.
The energy weights are abstract relative units and are not power. They are not watts, not milliwatts, not normalised to anything measurable, and not any device's numbers. They exist so the trade can be computed at all, and using them as power figures would be a straightforward error. Real low-power state energy and exit latency are per-device, per-generation, per-condition quantities belonging to Module 24 and to the device's own specification.
And one deliberate abstraction worth flagging. The deep state's exit cost is modelled as a cycle count. In reality, exiting a deep state may require re-establishing the measured and calibrated state Chapter 4.4 §3 described — which is a procedure, not a delay. Collapsing a training procedure into a countdown is honest only because it is stated; the model captures that leaving is expensive and captures nothing about why.
Interface
req_valid presents pending traffic. req_served says it may proceed. state reports the current state and exit_stall reports a request blocked by an exit in progress. energy_acc and stall_acc are the two sides of the trade.
How to simulate it
vlog dram_power_state_ctrl.sv tb_dram_power_state_ctrl.sv then vsim -c tb_dram_power_state_ctrl -do "run -all"; VCS vcs -sverilog dram_power_state_ctrl.sv tb_dram_power_state_ctrl.sv && ./simv; Xcelium xrun -sv dram_power_state_ctrl.sv tb_dram_power_state_ctrl.sv.
// ─────────────────────────────────────────────────────────────────────────
// DRAM POWER STATE CONTROLLER.
// Classification: SYNTHESIZABLE RTL, WITH ABSTRACT RELATIVE ENERGY WEIGHTS.
//
// Decides when to enter progressively deeper low-power states and pays the
// cost of leaving them. Accounts for BOTH sides of the trade so it can be
// computed rather than assumed:
// energy_acc -- abstract units accumulated per cycle by state
// stall_acc -- cycles a pending request waited on an exit
//
// THE WEIGHTS ARE NOT POWER. Not watts, not normalised to anything
// measurable, not any device's numbers. They exist so the trade has units
// at all. Real state energies and exit latencies are per-device,
// per-generation and per-condition: Module 24 and the device specification.
//
// DELIBERATE ABSTRACTION: the deep state's exit is a CYCLE COUNT. In
// reality, leaving a deep state can require re-establishing calibrated and
// trained state (Chapter 4.4) -- a procedure, not a delay. This model
// captures THAT LEAVING IS EXPENSIVE and nothing about why.
//
// WHAT THIS DOES NOT MODEL: voltage, current, charge, clock gating,
// regulator behaviour, thermal effects, retention, or any analog property
// of a power state. None of that is representable in RTL.
// ─────────────────────────────────────────────────────────────────────────
module dram_power_state_ctrl #(
// Idle cycles in ACTIVE/IDLE before entering the shallow low-power state.
parameter int IDLE_TO_PD = 3,
// Further idle cycles in the shallow state before going deep.
parameter int PD_TO_DEEP = 8,
// Exit costs, in cycles. DEEP must cost at least as much as PD, or the
// hierarchy is inverted -- checked at elaboration.
parameter int PD_EXIT_CYC = 3,
parameter int DEEP_EXIT_CYC = 12,
// Abstract relative energy per cycle. See the header: NOT power.
parameter int W_ACTIVE = 8,
parameter int W_IDLE = 4,
parameter int W_PD = 1,
parameter int W_DEEP = 0,
parameter int ACC_W = 24,
parameter int IDL_W = $clog2((IDLE_TO_PD > PD_TO_DEEP ? IDLE_TO_PD
: PD_TO_DEEP) + 1),
parameter int EXT_W = $clog2(DEEP_EXIT_CYC + 1)
) (
input logic clk,
input logic rst_n,
// Traffic is pending. Held high until served.
input logic req_valid,
// The request may proceed this cycle.
output logic req_served,
// 0 ACTIVE, 1 IDLE, 2 PD, 3 DEEP, 4 EXIT.
output logic [2:0] state,
// A request is pending and cannot be served because the device is in or
// leaving a low-power state. This is the LATENCY side of the trade.
output logic exit_stall,
output logic [ACC_W-1:0] energy_acc,
output logic [ACC_W-1:0] stall_acc
);
typedef enum logic [2:0] {
S_ACTIVE = 3'd0,
S_IDLE = 3'd1,
S_PD = 3'd2,
S_DEEP = 3'd3,
S_EXIT = 3'd4
} state_e;
state_e st_q;
// ── COMPILE-TIME legality. An inverted hierarchy -- a deeper state
// cheaper to leave, or costlier to occupy -- is rejected rather than
// silently simulated, because every conclusion drawn from such a run
// would be about a device that cannot exist.
if (IDLE_TO_PD < 1) begin : g_idle_min
initial $fatal(1, "dram_power_state_ctrl: IDLE_TO_PD must be >= 1");
end
if (PD_TO_DEEP < 1) begin : g_deep_min
initial $fatal(1, "dram_power_state_ctrl: PD_TO_DEEP must be >= 1");
end
if (PD_EXIT_CYC < 1) begin : g_pdx_min
initial $fatal(1, "dram_power_state_ctrl: PD_EXIT_CYC must be >= 1");
end
if (DEEP_EXIT_CYC < PD_EXIT_CYC) begin : g_exit_order
initial $fatal(1, "dram_power_state_ctrl: DEEP_EXIT_CYC must be >= PD_EXIT_CYC");
end
if ((W_ACTIVE < W_IDLE) || (W_IDLE < W_PD) || (W_PD < W_DEEP)) begin : g_weight_order
initial $fatal(1, "dram_power_state_ctrl: energy weights must be non-increasing with depth");
end
logic [IDL_W-1:0] idle_cnt;
logic [EXT_W-1:0] exit_cnt;
assign state = st_q;
// A request is served only in the two states where the device is
// immediately usable. IDLE is "clocked but doing nothing", so it serves
// without penalty -- which is exactly why IDLE_TO_PD exists as a
// separate, tunable threshold rather than dropping straight to PD.
assign req_served = req_valid && ((st_q == S_ACTIVE) || (st_q == S_IDLE));
assign exit_stall = req_valid && ((st_q == S_PD) || (st_q == S_DEEP)
|| (st_q == S_EXIT));
// ── Energy weight of the current state. S_EXIT is charged at the ACTIVE
// rate: leaving a low-power state is not itself low-power, and a model
// that charged it at the sleeping rate would make deep states look
// unconditionally free.
logic [ACC_W-1:0] w_now;
always_comb begin
unique case (st_q)
S_ACTIVE: w_now = ACC_W'(W_ACTIVE);
S_IDLE: w_now = ACC_W'(W_IDLE);
S_PD: w_now = ACC_W'(W_PD);
S_DEEP: w_now = ACC_W'(W_DEEP);
default: w_now = ACC_W'(W_ACTIVE);
endcase
end
// Saturating accumulation, computed one bit wide to expose the carry.
logic [ACC_W:0] e_sum, s_sum;
always_comb begin
e_sum = {1'b0, energy_acc} + {1'b0, w_now};
s_sum = {1'b0, stall_acc} + (ACC_W+1)'(1);
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st_q <= S_ACTIVE;
idle_cnt <= '0;
exit_cnt <= '0;
energy_acc <= '0;
stall_acc <= '0;
end else begin
energy_acc <= e_sum[ACC_W] ? {ACC_W{1'b1}} : e_sum[ACC_W-1:0];
if (exit_stall) begin
stall_acc <= s_sum[ACC_W] ? {ACC_W{1'b1}} : s_sum[ACC_W-1:0];
end
unique case (st_q)
S_ACTIVE: begin
if (!req_valid) begin
st_q <= S_IDLE;
idle_cnt <= '0;
end
end
S_IDLE: begin
if (req_valid) begin
// Served without penalty, so return to ACTIVE with no cost.
st_q <= S_ACTIVE;
end else if (idle_cnt == IDL_W'(IDLE_TO_PD - 1)) begin
st_q <= S_PD;
idle_cnt <= '0;
end else begin
idle_cnt <= idle_cnt + 1'b1;
end
end
S_PD: begin
if (req_valid) begin
st_q <= S_EXIT;
exit_cnt <= EXT_W'(PD_EXIT_CYC);
end else if (idle_cnt == IDL_W'(PD_TO_DEEP - 1)) begin
st_q <= S_DEEP;
idle_cnt <= '0;
end else begin
idle_cnt <= idle_cnt + 1'b1;
end
end
S_DEEP: begin
if (req_valid) begin
st_q <= S_EXIT;
exit_cnt <= EXT_W'(DEEP_EXIT_CYC);
end
end
S_EXIT: begin
// The exit runs to completion regardless of whether the request
// is withdrawn. A partially exited device is not in a defined
// state, and abandoning an exit part-way is how a controller
// ends up talking to a device that is neither asleep nor awake.
if (exit_cnt <= EXT_W'(1)) begin
st_q <= S_ACTIVE;
exit_cnt <= '0;
end else begin
exit_cnt <= exit_cnt - 1'b1;
end
end
default: st_q <= S_ACTIVE;
endcase
end
end
endmoduleCombinational logic
req_served and exit_stall partition the request's fate by state, and the partition is the trade made observable. w_now charges the exit at the active rate, which is the modelling decision that keeps deep states from appearing unconditionally free.
Sequential logic
A five-state machine with a shared idle counter — reused across S_IDLE and S_PD because only one is ever counting — and an exit countdown loaded with the departing state's cost. The S_EXIT behaviour is the design decision worth noticing: the exit completes even if the request is withdrawn, because abandoning it part-way leaves the device in no defined state.
Cycle trace
IDLE_TO_PD = 3, PD_EXIT_CYC = 3, weights 8 / 4 / 1 / 0:
| Cycle | req_valid | state | req_served | exit_stall | energy this cycle | energy_acc after |
|---|---|---|---|---|---|---|
| 0 | 1 | ACTIVE | 1 | 0 | 8 | 8 |
| 1 | 0 | ACTIVE | 0 | 0 | 8 | 16 |
| 2 | 0 | IDLE | 0 | 0 | 4 | 20 |
| 3 | 0 | IDLE | 0 | 0 | 4 | 24 |
| 4 | 0 | IDLE | 0 | 0 | 4 | 28 |
| 5 | 1 | PD | 0 | 1 | 1 | 29 |
| 6 | 1 | EXIT | 0 | 1 | 8 | 37 |
| 7 | 1 | EXIT | 0 | 1 | 8 | 45 |
| 8 | 1 | EXIT | 0 | 1 | 8 | 53 |
| 9 | 1 | ACTIVE | 1 | 0 | 8 | 61 |
Now do the arithmetic the block exists to make possible. Staying in ACTIVE for all ten cycles would cost 10 × 8 = 80 units and stall nothing. The run above costs 61 units and 4 stall cycles.
So the trade is 19 energy units for 4 cycles of added latency — and whether that is a good deal is not a hardware question. It depends entirely on what a cycle is worth in the product, which is why the threshold is a parameter and why a controller that hard-codes it is wrong for most of the systems it ends up in.
Now change one parameter and watch it invert. Set IDLE_TO_PD = 1 and the device enters PD after a single idle cycle: it spends 3 cycles at 1 unit instead of 3 at 4 units, saving 9 more — and pays exactly the same 3-cycle exit, because the exit cost does not depend on how long it slept. Enter eagerly enough, on traffic bursty enough, and the exit cost dominates entirely: a device that sleeps for two cycles and takes three to wake has spent more than it saved on both axes.
That non-monotonic relationship between entry aggressiveness and total cost is the real content of low-power design, and it is the reason this block accounts for both sides rather than reporting energy alone.
Simulation
A directed test should sweep IDLE_TO_PD against a fixed bursty stimulus and plot energy_acc and stall_acc together, confirming the trade curve and that neither is monotonic in isolation; confirm req_served only in ACTIVE and IDLE; confirm the deep state is reached only after the full idle sequence; confirm an exit completes even when the request is withdrawn mid-exit; and confirm the elaboration checks fire on an inverted hierarchy.
Sweeping the parameter is the test that matters, because a single-point run tells you what one threshold costs and nothing about whether it was the right one.
Synthesis
A five-state machine, two counters, a small multiplexer and two saturating adders. Trivially small. In a real controller this logic sits alongside the scheduler and shares the request visibility, because the entry decision wants to know not just that the device is idle but whether traffic is expected — which is a predictor this block does not have.
Corner cases
IDLE_TO_PD == 1 enters after a single idle cycle, which is the eager extreme the trace above analyses. DEEP_EXIT_CYC == PD_EXIT_CYC makes the deep state free to reach, which is unrealistic and permitted so the elaboration boundary is testable. Weights all equal makes every state cost the same and should show zero energy benefit and non-zero stall cost — a configuration that is strictly worse than never sleeping, and a good sanity check that the accounting is wired correctly. A request withdrawn mid-exit still completes the exit.
Verification
What DV must prove: req_served asserted only in ACTIVE and IDLE; every state reachable and the deep state reachable only via the shallow one; exit duration exactly the departing state's cost; exits completing regardless of the request; energy accumulating at the state's weight every cycle with the exit charged at the active rate; stalls counted on exactly the cycles a request was pending and unserved; saturation rather than wrap; and the two accumulators moving in opposite directions as the entry threshold is swept, which is the property that confirms the trade is being modelled at all.
Debugging
If energy looks implausibly low, check whether S_EXIT is charged at the active rate — charging it at the sleeping rate makes every deep state look free and is the single most common error in this kind of model. If the deep state is never reached, check that idle_cnt is cleared on the S_IDLE to S_PD transition; a counter that carries over reaches the deep threshold immediately or never. If stalls are undercounted, check that exit_stall includes S_PD and S_DEEP and not only S_EXIT — the cycle on which the request arrives at a sleeping device is already a stalled cycle. If a request is served during an exit, check that req_served excludes S_EXIT.
Limitations
The weights are not power, as the header states at length. No voltage, current, charge, clock gating, regulator behaviour, thermal effect or retention. No prediction: entry is a fixed idle threshold, where a real controller wants to know whether traffic is expected, and that predictor is where most of the engineering value lies. No re-training on exit, collapsed into a cycle count. No partial-array behaviour, no temperature adaptation, and no self-refresh distinction — all of §4's mechanisms are absent.
8. Three Assertions Worth Writing
// VERIFICATION-ONLY, inside dram_power_state_ctrl.
// P1 -- SAFETY. A request is served only when the device is actually
// usable. Serving in a low-power or exiting state would mean talking to a
// device that cannot answer, which on real hardware is not a performance
// bug.
property p_served_only_when_usable;
@(posedge clk) disable iff (!rst_n)
req_served |-> ((st_q == S_ACTIVE) || (st_q == S_IDLE));
endproperty
assert property (p_served_only_when_usable);
// P2 -- an exit, once begun, runs to completion. The device must not be
// left neither asleep nor awake, and a withdrawn request must not abort it.
//
// The form that comes naturally is:
//
// (st_q == S_EXIT) |-> (st_q == S_EXIT);
//
// which is a TAUTOLOGY -- antecedent and consequent describe the same
// signal, so it holds on every design including a broken one. That shape is
// worth learning to recognise: a property whose two halves talk about the
// same state is usually saying nothing. The useful version tracks the
// COUNTDOWN instead, asserting that the state leaves S_EXIT only once the
// count has actually run out.
property p_exit_runs_out;
@(posedge clk) disable iff (!rst_n)
($past(st_q) == S_EXIT) && (st_q != S_EXIT)
|-> ($past(exit_cnt) <= EXT_W'(1));
endproperty
assert property (p_exit_runs_out);
// P3 -- THE TRADE PROPERTY, and the one that earns its place. Every cycle
// with a pending, unserved request is counted as a stall. Without it, a
// model could report an excellent energy figure while silently
// under-counting the latency it cost -- which would make every conclusion
// drawn from the model wrong in the FAVOURABLE direction.
property p_stall_counted;
@(posedge clk) disable iff (!rst_n)
(req_valid && !req_served && (stall_acc != {ACC_W{1'b1}}))
|=> (stall_acc == ($past(stall_acc) + ACC_W'(1)));
endproperty
assert property (p_stall_counted);
// P4 -- the deep state is reachable only through the shallow one. Protects
// the hierarchy: a transition straight from IDLE to DEEP would skip the
// shallow state's accounting and mis-report both sides of the trade.
property p_deep_via_pd;
@(posedge clk) disable iff (!rst_n)
$rose(st_q == S_DEEP) |-> ($past(st_q) == S_PD);
endproperty
assert property (p_deep_via_pd);P3 is the assertion this chapter is about. A power model whose energy accounting is right and whose stall accounting is quietly low reports a favourable trade that does not exist — and it reports it confidently, with numbers. An instrument that errs in the direction its user is hoping for is worse than no instrument, because it converts an open question into a wrong answer nobody will re-examine.
That generalises well beyond power: whenever a model computes a trade-off, assert both sides with equal rigour. The side that makes your design look good is the side you are least likely to scrutinise, and therefore the side that most needs a property.
P2 is shown twice deliberately. The first form is the one that comes naturally and it is nearly vacuous — it restates the state it is guarding. A property whose antecedent and consequent both describe the same signal is usually not saying anything, and recognising that shape is worth more than the fixed version. The second form tracks the countdown, which is the actual invariant.
What none of them prove. Nothing about power, energy, voltage, current, thermal behaviour or retention — §7's header says why. Nothing about whether exiting a real deep state re-establishes trained state correctly, which is a procedure this model collapses to a countdown. And nothing whatsoever about §4's partial-array refresh, whose correctness condition is a claim about operating-system behaviour and therefore lies outside any assertion on any hardware block — the boundary §4's callout insisted a verification plan must state explicitly.
9. The Trade in Cycles
dram_power_state_ctrl — entering power-down, then paying to leave
10 cyclesWatch the slope of energy_acc. It rises by 8 per cycle while active, by 4 while idle, by 1 in power-down — and then by 8 again for the whole exit. The flat part is the benefit and the steep part after it is the bill.
And watch stall_acc start climbing at cycle 5, not cycle 6. The cycle on which the request arrives at a sleeping device is already a lost cycle; the exit has not even begun. That off-by-one is the exit_stall debugging note from §7, and it is the difference between a model that reports the trade honestly and one that flatters it by 25% on this run.
The totals: 61 units and 4 stall cycles, against 80 units and 0 stalls for never sleeping. Whether 19 units are worth 4 cycles is a product question, not a hardware one — and the only reason it can be asked at all is that both sides were counted.
What the figure does not claim. The weights are abstract relative units, not power. The exit cost is a cycle count standing in for what may be a re-calibration procedure. And nothing here models temperature, retention or partial-array behaviour.
10. Common Misconceptions
"LPDDR is DDR with the power turned down." Wrong mental model: one architecture with a low-power configuration. Engineering action: expecting LPDDR generations to correspond to DDR generations of the same number; expecting comparable signalling, clocking or channel organisation; sizing an LPDDR interface by analogy with a DDR one. Observable failure / bad conclusion: wrong expectations across the board — swing, termination polarity, clock structure, channel width, and which logic level is energetically free are all different. And a complete inability to explain why LPDDR can use a swing DDR cannot. Correct model: two parallel lineages, numbered independently, running the same engineering reasoning against different constraint sets. LPDDR's objective is bandwidth per joule under a hard power ceiling; the DDR chain's is bandwidth per channel with replaceability and multi-vendor interchange. Prevention: ask what the memory is attached to. A socket and an unknown population force one set of answers; a soldered, point-to-point, known connection permits a different set.
"Being soldered down is the price LPDDR pays for low power." Wrong mental model: fixed installation is a cost accepted in exchange for efficiency. Engineering action: proposing LPDDR-class swing on a socketed bus; assuming a low-swing interface could be made replaceable with sufficient engineering. Observable failure / bad conclusion: a design whose signalling the channel cannot support, discovered late and expensively. And a misreading of the whole family as a philosophy rather than as a consequence. Correct model: being soldered down is the precondition, not the price. Switching energy scales with the square of the swing, so swing is the largest energy lever available — and the only reason the DDR chain cannot pull it hard is that its channel must survive connectors, stubs, unknown populations and multiple loads. A short, lightly loaded, fixed channel is what permits the low swing. Prevention: ask what the channel will support before asking what the design would like.
"The line held high always costs more energy." Wrong mental model: one logic level is inherently the expensive one. Engineering action: applying an inversion or encoding scheme with the polarity carried over from another interface family; analysing energy per level by habit rather than from the termination arrangement. Observable failure / bad conclusion: an inversion mechanism that increases energy instead of reducing it, on an interface whose free state is the opposite of the one assumed — with no functional symptom at all. Correct model: which level is free follows from where the termination goes. DDR4's POD12 terminates to the supply, so current flows when the line is driven low and the high state is free. LPDDR4's LVSTL terminates to ground, so current flows when the line is driven high and the low state is free. Opposite answers, both correct for their interface. Prevention: find the termination reference. Current flows toward it, so the free state is the one that matches it.
"Deeper power states are always better for energy." Wrong mental model: sleeping more saves more. Engineering action: setting entry thresholds aggressively; modelling state energy without modelling exit cost; treating a low idle-power figure as the whole story. Observable failure / bad conclusion: a system that enters deep states on short idle gaps, pays a fixed exit cost every time, and ends up spending more energy and delivering worse latency than never sleeping. And, worse, a power model that reports a benefit because it accounted for only one side. Correct model: the exit cost does not depend on how long the device slept. So the benefit scales with idle duration while the cost is fixed, which makes total cost non-monotonic in entry aggressiveness. A device that sleeps for two cycles and takes twelve to wake has lost on both axes. Prevention: compute both sides for the actual traffic pattern. If a model reports only energy, it cannot answer the question being asked of it.
"Partial-array refresh is a device power feature." Wrong mental model: it is configuration the controller applies. Engineering action: enabling it without an operating-system contract about which regions hold live data; verifying it within the memory subsystem boundary. Observable failure / bad conclusion: data loss that presents as arbitrary memory corruption, because an abandoned region's contents decay gradually and unpredictably rather than disappearing at once — so the failure looks nothing like a refresh problem and is traced to the wrong subsystem. Correct model: refresh energy scales with how much array is maintained, but only software knows which parts hold live data. The mechanism's correctness depends on the OS consolidating live data into the maintained region and never reading what it abandoned. It is a hardware-software contract, and its correctness condition lies outside the hardware boundary. Prevention: ask who knows which memory is live. If the answer is software, the verification plan must say so explicitly, because no hardware regression can establish the property.
11. Debugging — Battery Life Is Worse Than the Power Model Predicted
Symptom. A mobile system's measured energy consumption on a realistic workload exceeds what the memory power model predicted, by a wide margin. The system is functionally correct and performance is acceptable.
A model-versus-measurement gap is a different kind of problem from a failure, and the first discipline is to suspect the model as readily as the system — because a model is a design under test, and one that errs favourably will not be questioned on its own.
Mechanism 1 — the model counted state energy and not exit energy. Inspect: whether the model charges the exit period at the active rate or at the sleeping rate. Expected evidence: a model in which entering a deep state is unconditionally beneficial. Discriminator: re-run the model charging exits at the active rate and see whether the gap closes. This is free, it is §7's most common modelling error, and a model with this defect predicts that aggressive entry is always right — which is a recognisable signature.
Mechanism 2 — the device is entering and leaving states far more often than modelled. Inspect: state-transition counts against the model's assumption, and the actual idle-gap distribution of the workload. Expected evidence: many short idle gaps rather than the few long ones the model assumed. Discriminator: the idle-gap histogram, not its mean. This is the important one: a workload whose mean idle gap is long can consist almost entirely of short gaps with a few very long ones, and the entry threshold interacts with the distribution rather than with the average. A model driven by an average will be wrong in a direction that depends on the shape.
Mechanism 3 — refresh energy is higher than modelled because the device is hot. Inspect: die temperature under the workload and whether the refresh rate is being increased accordingly. Expected evidence: refresh energy scaling with temperature. Discriminator: does the gap track temperature? Chapter 2.3 §4 established that retention falls as temperature rises, so a hot device refreshes more and spends more — and a model using a room-temperature refresh rate under-predicts systematically in exactly the conditions that matter.
Mechanism 4 — the interface is transferring far more bytes than the workload wants. Inspect: useful bytes over transferred bytes, exactly 4.4 §6's counters. Expected evidence: a large discard fraction on small scattered accesses. Discriminator: is the energy going into wanted bytes? Granularity waste is an energy problem before it is a bandwidth problem on a battery-powered device: every discarded byte was driven across the interface at full cost. A model that assumes all transferred bytes are useful under-predicts by the reciprocal of the useful fraction, which can be a factor of several.
Mechanism 5 — the low-power states are not being entered at all. Inspect: measured time-in-state against the model's assumption. Expected evidence: the device essentially never leaving the active or idle states. Discriminator: time-in-state, read directly. Causes include a controller that never releases the device, a periodic background activity that resets every idle timer, or thresholds configured far longer than any real idle gap. A single low-rate periodic activity can hold an entire memory subsystem awake, and that is a system-integration fault rather than a memory one.
Discrimination, cheapest first. Read measured time-in-state and transition counts, which resolves mechanisms 2 and 5 immediately and is pure telemetry. Then read useful-over-transferred bytes. Then re-run the model with exits charged correctly. Then check temperature.
The reasoning lesson. The measurement and the model disagree, and the model is the cheaper thing to check — yet the instinct is to instrument the hardware first. On an energy problem specifically, the model is unusually likely to be the culprit, for a reason worth stating plainly: power models are built from datasheet state figures, which describe steady states, while real systems spend much of their time in transitions between them. A model composed of steady states and driven by average idle gaps will systematically under-predict a bursty workload, and it will do so in the flattering direction. Distribution, not average; transitions, not just states.
12. Interview Reasoning
"Why can LPDDR use a much lower signal swing than DDR?" Because its channel is electrically easy, and that is a consequence of not being replaceable. LPDDR is soldered or co-packaged, so the connection is short, point-to-point, lightly loaded and of a known fixed configuration — none of the connectors, stubs, unknown populations or multiple loads that make a socketed DDR bus hard. A channel like that supports a much smaller swing, and since switching energy scales with the square of the swing, that is the largest energy lever available on an interface. So being fixed in place is not the price LPDDR pays for low power; it is the precondition that makes the low power reachable.
"Why does LPDDR5 separate the command clock from the data clock?" Because the command path does not need the data path's rate, and clocking it that fast costs energy for nothing. One command produces a burst of many beats, so the required command rate is lower than the data rate by roughly the burst length. Separating them means raising the data rate no longer drags the command path's clocking energy up with it — a first-order win under a bandwidth-per-joule objective. It also completes the source-synchronous idea DDR1 started: the forwarded data clock times the data in both directions, so the data path no longer shares a timing base with the command path and the relationship between them stops constraining the data rate. The cost is another timing domain for the controller to train and maintain.
"Which logic level costs more energy on a terminated interface?" It depends on where the termination goes, and current flows toward the termination reference. DDR4's POD12 terminates to the supply, so current flows when the driver pulls the line low and the high state is free. LPDDR4's LVSTL terminates to ground, so current flows when the driver pulls the line high and the low state is free. They are opposite and both correct for their interface. That matters practically, because any encoding that reduces energy by biasing the data toward one level has to bias toward whichever level that interface makes free — so the same mechanism has opposite polarity on the two families, and getting it backwards raises energy with no functional symptom.
"Is a deeper power state always better for energy?" No, because the exit cost does not depend on how long the device slept. The benefit scales with idle duration and the cost is fixed, so total cost is non-monotonic in how aggressively you enter: a device that sleeps for two cycles and takes twelve to wake has spent more energy and delivered worse latency than one that never slept. The right threshold depends on the workload's idle-gap distribution, not its average — a workload whose mean gap is long can be almost entirely short gaps with a few long ones. And a power model that accounts for state energy but not exit energy will report that aggressive entry is always correct, which is a recognisable signature of a broken model.
"Partial-array self-refresh saves energy. What makes it risky?" Its correctness condition lives in software. Refresh energy scales with how much of the array is maintained, but only the operating system knows which regions hold live data, so the mechanism depends on the OS consolidating live data into the maintained region and never reading what it abandoned. Break that contract and the failure is data loss, not a performance regression — and it presents as arbitrary memory corruption, because an abandoned region decays gradually and unpredictably rather than vanishing at once, so it gets traced to the wrong subsystem. It is also the clearest case of a hardware mechanism that cannot be validated in isolation: no device or controller regression establishes the property, because the property is a claim about software, and a verification plan has to say that explicitly.
13. Engineering Check
An educational low-power model: relative energy per cycle of 8 active, 4 idle, 1 in power-down, with a fixed 3-cycle exit charged at the active rate. Abstract relative units, not power, and not any device's figures.
1. A 10-cycle idle gap, entering power-down after 3 idle cycles. Energy, versus staying active? 3 cycles idle at 4 = 12, then 7 cycles in power-down at 1 = 7, total 19. Staying active: 10 × 8 = 80. Saved 61 units, plus a 3-cycle exit at 8 = 24 charged when the next request arrives, so net 37 spent against 80.
2. A 4-cycle idle gap, same threshold. 3 idle at 4 = 12, 1 cycle in power-down at 1 = 1, total 13, plus the 3-cycle exit at 24 = 37. Staying active for those 4 cycles: 32. Sleeping cost 5 units more than not sleeping — the exit alone outweighed everything saved.
3. What idle gap breaks even? Let the gap be N cycles with N > 3. Sleeping costs 12 + (N-3) + 24 = N + 33; staying active costs 8N. Break-even at 8N = N + 33, so N ≈ 4.7 — about 5 cycles. Below that, sleeping loses. The threshold is not a preference; it is computable from the weights and the exit cost.
4. The exit cost rises to 12 cycles for a deeper state saving 1 unit per cycle more. New break-even? Sleeping costs 12 + 0×(N-3) + 96 = 108 for the deep state's zero weight; active costs 8N. Break-even at N = 13.5, so about 14 cycles. A deeper state moves the break-even point far out — it is better for long gaps and much worse for short ones, which is exactly why a hierarchy of states exists rather than one deep state.
5. A workload's idle gaps average 8 cycles: half are 1 cycle and half are 15. Which state should be entered? Neither answer follows from the average. The 1-cycle gaps lose on any state; the 15-cycle gaps win on the deep one. The correct policy is entered per gap, which requires the threshold hierarchy of question 4 — enter the shallow state after a few idle cycles and the deep one only after many more. This is why §11's second mechanism insists on the distribution rather than the mean: an average of 8 would have suggested the deep state, and it would have been wrong half the time.
6. The same workload, but a periodic background activity touches memory every 2 cycles. What now? No idle gap ever exceeds 2 cycles, so no state is ever worth entering and none is ever reached. The entire low-power design is inert, with nothing malfunctioning. A single low-rate periodic activity can hold a whole memory subsystem awake — §11's fifth mechanism, and the reason time-in-state is the first telemetry to read on any energy investigation.
14. Summary
LPDDR is not low-power DDR. It is the same engineering reasoning run against a different constraint set: soldered or co-packaged, no socket, no meaningful airflow, and a fixed energy budget — which makes the objective bandwidth per joule under a hard power ceiling rather than bandwidth per channel.
The constraint that makes the problem harder is the one that makes the answer possible. Being fixed in place means the channel is short, point-to-point and lightly loaded — none of the connectors, stubs, unknown populations or multiple loads that make a socketed bus hard. A channel like that supports a much smaller signal swing, and since switching energy scales with the square of the swing, that is the largest energy lever an interface has. Being soldered down is the precondition, not the price.
LPDDR4's signalling terminates to ground, so current flows when a line is driven high and the low state is energetically free — the inverse of DDR4's POD12, which terminates to the supply. Which logic level is free is a design variable that follows from where the termination goes, not a fact about digital circuits, and any energy-reducing encoding must match its interface's polarity.
LPDDR5 separates the command clock from the data clock. A command produces many beats, so the command path's required rate is lower than the data path's by roughly the burst length, and clocking it fast costs energy for nothing. Separating them decouples command-path energy from the data rate, and completes DDR1's source-synchronous idea — the data path no longer shares a timing base with the command path at all.
Refresh is attacked as a line item: per-bank refresh to localise the stall, temperature-adaptive self-refresh because retention improves when cool, and partial-array refresh so energy scales with how much memory holds live data. The last is a hardware-software contract whose correctness depends on the operating system, whose failure mode is data loss presenting as arbitrary corruption, and which therefore cannot be validated inside the hardware boundary at all.
And the channels are narrow and several — structurally DDR5's split, arrived at earlier and for the opposite reason: not to afford a longer burst, but because a phone's many small independent requesters are better served by smaller granularity and more concurrency. The same feature is a cost in one lineage and a benefit in the other, decided by the requesters.
The array is unchanged. Two lineages, eleven generations between them, and the same 1T1C cell underneath all of it.
15. What Comes Next
Both lineages so far move data through a package's external connections, and both have spent every available lever on making those connections faster or more numerous.
Chapter 4.8 confronts a requirement neither can reach. An accelerator wanting hundreds of gigabytes per second cannot get there by raising the per-pin rate, because energy per bit rises with rate — the 4.2 and §2 argument running the wrong way — and cannot get there by adding pins, because a package has a perimeter and a perimeter has a limit.
So the bottleneck stops being electrical and becomes geometric, and the response is the only one left: change the packaging so that a device can have a vastly wider interface at a lower per-pin rate. Stack the DRAM dies vertically, connect them with through-silicon vias, and place the stack on an interposer beside the processor. A thousand-bit interface at a modest per-pin rate, which is a different point on the width-versus-rate curve than anything in this module so far — and the chapter that closes it, because it shows the curve had a dimension the first seven chapters never used.
Return to DDR5 for the channel split this chapter's narrow channels parallel, DDR4 for the termination polarity §2 inverts, or The Refresh Requirement for the temperature dependence §4's adaptive refresh exploits. Module 24 is the LPDDR architecture in full. The full path is on the DDR tutorials index.
Continue learning
Related tutorials
- Related topic
DDR vs LPDDR
The access contract is identical and all thirteen obligations are shared, so no structural argument separates these two. The deciding quantity is energy under the actual duty cycle and idle-duration distribution, not power and not bandwidth.
- 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
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.
- 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.
