DDR · Module 15
Refresh Commands
A refresh command's scope decides what it retires. Device-wide scope means one obligation and one counter; narrower scope means one obligation per resource, a counter each, and a pointer deciding which is next.
Chapter 7.5 already owns the REF command. It established that refresh requests maintenance rather than a transfer, that its precondition spans banks, that the device is unavailable for a period afterwards, and it built ref_precondition_check to enforce both.
That chapter answered “may this command be issued, and what does it block?” This one answers a question it had no reason to ask:
What does one refresh command actually retire — and what must the controller therefore count?
Chapter 15.1 established refresh as a service-rate obligation and left “a service event” deliberately abstract. Making it concrete turns out to depend entirely on the command's scope, and the answer changes the controller's state in a way that has nothing to do with timing.
1. What One Command Retires
Start with the question, because it is easy to slide past.
Chapter 15.1 §5 defined compliance as an average interval between service events. A refresh command is a service event. But how much service does one command deliver?
The honest answer is: exactly one unit of one obligation — and how many obligations there are depends on the command's scope.
scope = the whole device
one obligation for the device
one command retires one unit of it
→ the controller needs ONE counter
scope = one bank within each group
one obligation PER BANK
one command retires one unit for the banks it names
→ the controller needs ONE COUNTER PER BANK
plus a decision about which is next2. What Chapter 7.5 Settled
Briefly, because it is prerequisite rather than content.
Chapter 7.5 §1 established that REF requests maintenance and moves no data. §2 established that its precondition spans banks — the command requires the affected resources to be in a particular state before it may be issued. §3 established that a narrower-scope refresh weakens that precondition, because fewer resources need to be prepared. And §4's ref_precondition_check enforces both the precondition and the occupancy.
This chapter assumes all of that and adds nothing to it. The precondition is why refresh needs preparation, which Chapter 15.3 owns. The occupancy is why refresh costs availability, which Chapter 15.5 owns.
What neither chapter asked is what §1 just asked: the counting.
3. Device-Wide Scope — One Obligation
The simplest and, for DDR4, the only case.
A refresh command whose scope is the whole device advances the device's internal row pointer once, across everything. There is exactly one obligation, so the controller's state is minimal:
one interval counter — when is service due?
one service ledger — how much is owed or credited?
no selection decision — there is only one thing to serviceThat is Chapter 2.3 §4's refresh_deadline_tracker almost exactly, and Chapter 15.1 §6's refresh_rate_obligation measures its average. For a device-wide scope, the existing blocks are sufficient and this chapter has no new RTL to justify.
4. Narrower Scope — N Obligations
Now the case that forces new state.
Suppose a refresh command's scope is one bank within each bank group rather than the whole device. Module 16 owns what a bank group is; all this chapter needs is that such a scope is narrower than the device and coarser than a single bank.
Three things change at once.
The precondition weakens. Only the named banks must be prepared; the rest may stay active. Chapter 7.5 §3 established this, and it is the performance motivation for the narrower scope — Chapter 15.5 quantifies it.
The obligation multiplies. Each bank now has its own service requirement and its own rate to meet. One command retires one unit for the banks it names and nothing for the others.
A selection decision appears. With N obligations and one command per opportunity, something must choose. The natural choice is round-robin, and the natural bug is to let traffic pressure bias it — servicing the idle banks repeatedly because they are convenient, while a busy bank's obligation ages.
5. The Generation Question — And a Correction
§12 of this module's governing brief is explicit that per-bank refresh must not be taught as universally available, and that a feature must not be attributed exclusively to one generation if another has a related form. Both cautions apply here, and one of them applies to this chapter's own registry description.
Now the generation table, with each row's evidence stated exactly.
| Generation | Refresh scopes | Evidence |
|---|---|---|
| DDR4 | device-wide only | VERIFIED — §3's citation; no bank-scoped refresh in the datasheet |
| DDR5 | all-bank plus same-bank | REPORTED, not retrieved — see below |
| LPDDR | includes a per-bank form | DEFERRED — Module 24 owns it; not verified here |
6. The Service Event, Per Scope
Chapter 15.1 §5 fixed the convention: a service event is dated by the cycle the refresh command is accepted. That stands. What §1 adds is which obligation the event credits.
refresh accepted on cycle S with scope covering resource set R
for every resource r in R:
service(r) occurs at cycle S
next service for r is DUE at S + I
for every resource NOT in R:
nothing happensThe second clause is the one that gets dropped. A controller that credits every bank on every refresh command — because that was correct for device-wide scope — will believe all banks are current while only some were serviced. That is not a timing bug and no timing checker will see it; it is a bookkeeping bug, and its symptom is Chapter 15.1 §10's silent corruption localised to particular banks.
7. RTL — A Scope-Aware Service Ledger
Collision check. Chapter 2.3's refresh_deadline_tracker holds one interval and one owed count. Chapter 7.5's ref_precondition_check owns the precondition and occupancy. Chapter 15.1's refresh_rate_obligation measures one average over a window. Chapter 13.3's deadline_scoreboard holds abstract obligations.
All four assume a single obligation. refresh_deadline_tracker has one counter; refresh_rate_obligation has one window and one service count; deadline_scoreboard's slots are abstract but its reduction is a maximum over obligations on one candidate, not a per-resource ledger. Nothing in the corpus holds one refresh obligation per resource with a scope mask deciding which are retired.
That is the new responsibility, and §4 established why it matters: the per-resource view is the only one that can detect the neglected-bank failure.
The engineering problem. Hold one service obligation per resource. On an accepted refresh, retire one unit for exactly the resources its scope covers and leave the rest untouched. Report the worst resource — the one furthest behind — because that is the one that determines compliance.
Classification: controller-side service accounting over a resource set. Counts cycles and events per resource; compares counts. No physical modelling.
What it does not model. Charge, leakage, retention, rows or cells — Chapter 15.1 §6's boundary, unchanged. It also does not model the precondition (Chapter 7.5), the occupancy (Chapter 15.5), or any selection policy (Module 17) — it reports which resource is worst and chooses nothing.
// ─────────────────────────────────────────────────────────────────────
// refresh_scope_ledger
//
// CLASSIFICATION
// Controller-side service accounting over a RESOURCE SET. Counts
// cycles and service events per resource and compares counts.
//
// WHAT IT MODELS
// §6's per-scope service rule:
// one obligation per resource
// an accepted refresh with scope mask M retires one unit for
// every resource in M and NOTHING for the others
// the WORST resource determines compliance, not the total
//
// WHAT IT DOES NOT MODEL
// Charge, leakage, retention, rows or cells -- Chapter 2.2 owns the
// physics and a controller cannot observe any of it. Nor the bank
// precondition (Chapter 7.5's ref_precondition_check), nor the
// occupancy (Chapter 15.5), nor any selection POLICY (Module 17).
// It reports which resource is furthest behind; it chooses nothing.
//
// RELATIONSHIP TO EXISTING RTL
// Chapter 2.3's refresh_deadline_tracker and Chapter 15.1's
// refresh_rate_obligation both assume ONE obligation and are the
// right blocks for a device-wide scope (§3). This block is needed
// only when the scope is narrower than the device (§4), and it
// generalises both: with RESOURCES == 1 and an all-ones mask it
// degenerates to the single-obligation case, which §10's fifth
// test uses as a sanity check.
//
// SCOPE IS AN INPUT, NOT A PARAMETER
// scope_mask is a runtime input because a generation may offer
// BOTH an all-bank and a narrower command (§5's DDR5 row). The
// same ledger must handle a wide command and a narrow one in
// consecutive cycles, so the scope travels with the event.
//
// CONVENTION (Chapter 15.1 §5, extended per §6)
// A service event is dated by the cycle the refresh command is
// ACCEPTED. For resource r covered by the scope, that resets r's
// age; for r not covered, r's age continues to advance.
//
// SIMULTANEITY
// service and age-tick on the same cycle -> the covered resources'
// ages are RESET, not incremented. A reset-then-increment
// ordering would leave a serviced resource showing age 1, which
// is harmless numerically and wrong semantically -- the age is
// "cycles since service" and service just happened.
// an all-zero scope mask -> a refresh that retires nothing.
// REPORTED on empty_scope rather than silently accepted,
// because it is almost certainly a mask-wiring bug and it
// consumes an opportunity without delivering service.
// reset -> all ages zero. This is deliberately OPTIMISTIC and is
// discussed in the reset note below.
// ─────────────────────────────────────────────────────────────────────
module refresh_scope_ledger #(
// Number of independently-refreshed resources. 1 = device-wide
// scope (§3); greater than 1 = a narrower scope (§4).
parameter int RESOURCES = 4,
// Age at which a resource is considered DUE for service, in cycles.
// EDUCATIONAL sizes expected in simulation -- Chapter 15.1 §7
// explains why a real interval must not be simulated directly.
parameter int INTERVAL = 16,
// Age at which a resource is LATE. Chapter 15.3 owns where this
// comes from; here it is supplied so the ledger can report urgency.
parameter int DEADLINE = 24,
parameter int RES_W = (RESOURCES <= 1) ? 1 : $clog2(RESOURCES),
// Age counter width. Sized for DEADLINE plus headroom so a resource
// that goes past its deadline still reports a truthful age rather
// than wrapping -- see the saturation note.
parameter int AGE_W = (DEADLINE <= 1) ? 1 : $clog2(DEADLINE + 1) + 1
) (
input logic clk,
input logic rst_n,
// ── An accepted refresh command and the resources its scope covers.
input logic service_accepted,
input logic [RESOURCES-1:0] scope_mask,
// ── Per-resource age, in cycles since that resource was serviced.
// Saturating, so a badly-neglected resource reports a truthful
// "at least this old" rather than wrapping to a small number.
output logic [AGE_W-1:0] age [RESOURCES],
output logic [RESOURCES-1:0] res_due,
output logic [RESOURCES-1:0] res_late,
// ── THE compliance-relevant outputs: the WORST resource, not the
// total. §4's argument is that an aggregate count cannot detect
// a neglected resource.
output logic [RES_W-1:0] worst_resource,
output logic [AGE_W-1:0] worst_age,
output logic any_due,
output logic any_late,
// ── A refresh that covered nothing. Almost certainly a mask bug,
// and it wastes a service opportunity. Reported, never absorbed.
output logic empty_scope
);
// ── Elaboration guards.
if (RESOURCES < 1) begin : g_res
initial $fatal(1, "refresh_scope_ledger: RESOURCES must be >= 1");
end
if (INTERVAL < 1) begin : g_int
initial $fatal(1, "refresh_scope_ledger: INTERVAL must be >= 1");
end
// A deadline at or before the due point would make "due" and "late"
// the same event, collapsing Chapter 15.1 §5's distinction. Refused
// at elaboration rather than producing a ledger whose urgency
// signals are indistinguishable.
if (DEADLINE < INTERVAL) begin : g_order
initial $fatal(1, "refresh_scope_ledger: DEADLINE must be >= INTERVAL");
end
localparam int AGE_MAX = (1 << AGE_W) - 1;
logic [AGE_W-1:0] age_q [RESOURCES];
// ── Combinational view.
int unsigned best_idx;
logic [AGE_W-1:0] best_age;
always_comb begin
empty_scope = service_accepted && (scope_mask == '0);
for (int unsigned r = 0; r < RESOURCES; r++) begin
age[r] = age_q[r];
res_due[r] = (age_q[r] >= AGE_W'(INTERVAL));
res_late[r] = (age_q[r] >= AGE_W'(DEADLINE));
end
any_due = (res_due != '0);
any_late = (res_late != '0);
// ── Worst-resource reduction. Scanned so the LOWEST index wins a
// tie, giving a reproducible report run to run.
best_idx = 0;
best_age = age_q[0];
for (int unsigned r = 1; r < RESOURCES; r++) begin
if (age_q[r] > best_age) begin
best_age = age_q[r];
best_idx = r;
end
end
worst_resource = RES_W'(best_idx);
worst_age = best_age;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int unsigned r = 0; r < RESOURCES; r++) begin
age_q[r] <= '0;
end
end else begin
for (int unsigned r = 0; r < RESOURCES; r++) begin
if (service_accepted && scope_mask[r]) begin
// §6's first clause: covered resources are serviced. Reset
// to zero, NOT to one -- the age is cycles since service and
// service is happening now. Simultaneity rule in the header.
age_q[r] <= '0;
end else if (age_q[r] != AGE_W'(AGE_MAX)) begin
// §6's second clause: uncovered resources keep ageing.
// Saturating, so neglect is reported truthfully instead of
// wrapping to a small and reassuring number.
age_q[r] <= age_q[r] + 1'b1;
end
end
end
end
endmoduleInterface contract. scope_mask travels with the service event rather than being a parameter, because §5's DDR5 row offers both a wide and a narrow command and one ledger must handle either. worst_resource and worst_age are the compliance-relevant outputs; a consumer that sums the ages or counts the commands is computing the quantity §4 showed cannot detect the failure.
Parameter contract. RESOURCES == 1 with an all-ones mask degenerates to the single-obligation case, which is the sanity check §10 uses. DEADLINE must be at least INTERVAL, enforced at elaboration — a deadline at or before the due point would collapse Chapter 15.1 §5's due-is-not-late distinction into one event, and the urgency outputs would become indistinguishable.
Why the age saturates. AGE_W is sized one bit above DEADLINE and the counter stops at its maximum. A wrapping age counter is actively dangerous here: a resource neglected for twice its deadline would report a small age and appear healthy, which is the opposite of what a monitor is for. Saturating means the report reads “at least this old”, which is truthful and actionable.
Reset behaviour, and an honest note about it. Reset sets every age to zero, i.e. it asserts that every resource was just serviced. That is optimistic and not physically justified — a device coming out of reset has rows of unknown age, and the specification's own initialisation sequence is what establishes a known state. The block takes the optimistic position because a controller cannot know otherwise, and because the alternative — asserting every resource is immediately late — would force a burst of refresh at every reset. The right resolution is that initialisation is a separate obligation the device sequence handles, and this block is not it. Flagging the assumption is better than hiding it.
Corner cases. All-zero scope_mask on an accepted refresh: nothing is retired, empty_scope fires, and the opportunity is consumed — almost certainly a mask-wiring bug. All-ones mask: behaves as a device-wide refresh regardless of RESOURCES, which is how a generation offering both commands uses one ledger. Service and age-tick coinciding: covered resources reset, per the simultaneity rule. A resource at AGE_MAX: stops counting and keeps reporting.
Synthesis implications. RESOURCES age counters of AGE_W bits, two comparator arrays, and a RESOURCES-way maximum tree. For 4 resources and 6-bit ages that is 24 flops — the multiplication §1 warned about is real but cheap, which is worth saying because the cost argument against per-resource state is usually wrong.
Failure modes. Crediting every resource on every refresh — §6's dropped second clause, and the headline failure: all resources appear current while only some were serviced. Summing or averaging the ages instead of taking the maximum: hides exactly the neglected resource the block exists to find. A wrapping age counter: makes severe neglect look healthy. Resetting a serviced resource's age to 1 rather than 0: harmless numerically, wrong semantically, and it makes the res_due boundary off by one against §6's convention.
8. Scope and Ages, in Cycles
refresh_scope_ledger — a narrow scope neglects resource 3
10 cyclesThree observations, each a §9 property.
Three refresh commands in ten cycles is a healthy aggregate rate, and resource 3 still went late. That is §4's failure exactly, and it is why an aggregate count is the wrong measurement.
age[0] resets three times and age[3] never does. The masks 0011, 0101, 0011 cover resource 0 every time and resource 3 never — a pointer that never advanced past the low indices, which is the natural bug when the selection decision is added carelessly.
worst res reads 3 from cycle 2 onward, long before anything is due. That is the output a controller can act on: it identifies the resource to service next while there is still time, as opposed to any_late, which arrives when the damage is already done.
9. Four Assertions Worth Writing
// ── P1. THE scope rule, positive half: a covered resource's age
// resets. Catches a mask that is read but not applied.
generate
for (genvar gr = 0; gr < RESOURCES; gr++) begin : g_covered
property p_covered_resource_resets;
@(posedge clk) disable iff (!rst_n)
($past(service_accepted, 1) && $past(scope_mask[gr], 1))
|-> (age[gr] == '0);
endproperty
a_covered_resource_resets: assert property (p_covered_resource_resets);
end
endgenerate
// ── P2. THE scope rule, negative half -- and the one that matters.
// An UNCOVERED resource must keep ageing. Catches §7's headline
// failure: crediting every resource on every refresh, which makes
// all resources look current while only some were serviced.
// This is the property a device-wide design does not need and a
// narrow-scope design cannot do without.
generate
for (genvar gs = 0; gs < RESOURCES; gs++) begin : g_uncovered
property p_uncovered_resource_ages;
@(posedge clk) disable iff (!rst_n)
( $past(service_accepted, 1) && !$past(scope_mask[gs], 1)
&& ($past(age[gs], 1) != AGE_W'(AGE_MAX)) )
|-> (age[gs] == ($past(age[gs], 1) + AGE_W'(1)));
endproperty
a_uncovered_resource_ages: assert property (p_uncovered_resource_ages);
end
endgenerate
// ── P3. Compliance is the WORST resource, not the total. Written as
// the two halves of "is the maximum": it equals some resource's
// age, and no resource exceeds it.
// Catches a reduction that sums or averages -- which is exactly the
// measurement §4 showed cannot detect a neglected resource.
generate
for (genvar gt = 0; gt < RESOURCES; gt++) begin : g_worst_dominates
property p_no_resource_exceeds_worst;
@(posedge clk) disable iff (!rst_n)
(age[gt] <= worst_age);
endproperty
a_no_resource_exceeds_worst: assert property (p_no_resource_exceeds_worst);
end
endgenerate
property p_worst_is_a_real_resource;
@(posedge clk) disable iff (!rst_n)
(worst_age == age[worst_resource]);
endproperty
a_worst_is_a_real_resource: assert property (p_worst_is_a_real_resource);
// ── P4. The age saturates rather than wrapping. Catches the failure
// that makes severe neglect look healthy -- a wrapped age reports a
// small number for a resource that has been ignored for twice its
// deadline, which is worse than no monitor at all.
generate
for (genvar gu = 0; gu < RESOURCES; gu++) begin : g_saturate
property p_age_saturates;
@(posedge clk) disable iff (!rst_n)
($past(age[gu], 1) == AGE_W'(AGE_MAX)) |->
( (age[gu] == AGE_W'(AGE_MAX))
|| ($past(service_accepted, 1) && $past(scope_mask[gu], 1)) );
endproperty
a_age_saturates: assert property (p_age_saturates);
end
endgenerate
// ── C1. A resource actually goes LATE somewhere in the run, and an
// uncovered-while-serviced case actually occurs. Without the second,
// P2 is vacuous -- and P2 is the property this chapter exists for.
c_some_resource_late: cover property (@(posedge clk) disable iff (!rst_n)
any_late);
generate
for (genvar gv = 0; gv < RESOURCES; gv++) begin : g_cover_uncovered
c_uncovered_during_service:
cover property (@(posedge clk) disable iff (!rst_n)
(service_accepted && !scope_mask[gv]));
end
endgenerateWhat these prove. That covered resources reset and uncovered resources keep ageing — the two halves of §6's rule; that compliance is reported as the maximum in both directions; and that the age saturates rather than wrapping.
What these do not prove. That INTERVAL and DEADLINE are the right values — parameters, and Chapter 15.1 §8's limitation applies unchanged: a ledger configured with an interval twice too long certifies a design that refreshes half as often as required, with every property passing. That the scope mask is correct — if the mask does not match what the command actually covers in the device, every property passes and the ledger is confidently wrong about reality; that is a decode question against the generation's command definition. And nothing about the precondition or the occupancy, which Chapter 7.5 and Chapter 15.5 own and which §3 of Chapter 15.1 established fail independently.
Vacuity. P2's antecedent requires a service event that does not cover the resource in question — which never occurs for a device-wide scope with an all-ones mask. So on a DDR4 configuration P2 is vacuous by construction, and that is fine and worth knowing: the property exists for the narrow-scope case. C1's second cover is what tells you whether it was exercised.
10. DV — Reconstructing Service Per Resource
Invert the representation. The ledger holds per-resource ages that count up and reset. A checker should hold per-resource last-service timestamps and compute age by subtraction on demand — Chapter 13.4 §8's discipline, and here it has a specific payoff: a checker holding timestamps cannot reproduce a reset-versus-increment ordering bug, because it never increments anything.
Decode the scope independently. This is the chapter-specific verification obligation. The ledger is told which resources a command covered. A checker must derive that from the command encoding and the generation's definition, not from the design's scope_mask — because §9's second limitation is that a wrong mask passes every property. If the design's notion of scope and the device's disagree, only an independent decode surfaces it.
Check per resource, never in aggregate. A checker that counts refresh commands and compares against an expected total is measuring the quantity §4 proved insufficient. The check is on the maximum age, per resource, every cycle.
REFRESH SERVICE VIOLATION
generation : DDR5 (same-bank scope) [see 15.2 §5]
resource : bank 3
last service : cycle 1204
observed cycle : 1462
age : 258 cycles
due at age : 200
late at age : 250
aggregate rate : COMPLIANT — 9 REF in 1800 cycles
per-resource rate : VIOLATED — bank 3 served 0 times in 258 cycles
scope masks seen : 0011, 0101, 0011, 0011, 0101, 0011, 0011, 0101, 0011
root question : why did the selection pointer never reach bank 3?The two lines to steal are the paired rate verdicts and the mask history. Showing aggregate rate: COMPLIANT beside per-resource rate: VIOLATED pre-empts the first objection a designer will raise — but we issued plenty of refresh — and answers it in the same frame. And the mask history makes the cause visible without a waveform: masks that never set bit 3 are a selection bug, not a rate bug.
The root question is the right last line. Chapter 15.3 §10 develops the general form of this: the failure did not begin when the age crossed the deadline, it began when the selection policy first skipped the resource.
11. Debugging
Symptom. Corruption localised to particular banks, with refresh commands being issued at a healthy rate.
| Candidate mechanism | Evidence | Discriminator |
|---|---|---|
| Selection pointer never reaches some resources | Mask history never sets certain bits | The decisive check: log the scope masks and OR them together over a window. Any bit that stays clear names a neglected resource. Costs nothing and settles it. |
| Every resource credited on every refresh | All ages look current; corruption anyway | §9's P2. Check whether an uncovered resource's age ever increments during a service cycle. |
| Scope mask does not match the command | Ages plausible; corruption in resources the design believes it served | Decode the command independently against the generation's definition. §10. |
| Aggregate monitoring only | Monitor compliant; corruption localised | Whether the monitor has per-resource state at all. An aggregate monitor cannot see this class of failure. |
| Ages wrapping | A severely neglected resource reports a small age | §9's P4. Check AGE_W against DEADLINE. |
| Wrong generation assumed | Design issues narrow-scope commands to a device that only has device-wide scope | §5's table. A DDR4 part has one scope; a controller issuing bank-scoped refresh to it is issuing something else. |
| Interval or deadline misconfigured | Uniform rather than localised corruption | Chapter 15.1 §10 — and localisation is the discriminator. |
The discriminator that defines this chapter is whether the corruption is localised. Chapter 15.1 §10's failures are uniform — a wrong interval under-refreshes everything equally. A scope or selection bug is localised, because some resources are served and others are not. One question — is the corruption spread evenly or concentrated? — routes the investigation to the right chapter.
The second discriminator is the OR of the scope masks. It is a one-line instrumentation, it requires no simulation rerun if the masks are already logged, and a clear bit is a proof rather than a hypothesis.
Responsible layer. If every resource's age stays within its deadline and corruption persists, this chapter's material is exonerated and the question moves to the interval value (Chapter 15.4) or the device itself.
12. Common Misconceptions
“All refresh commands affect every bank.” Tempting because it is true for DDR4 — §3 verified it — and DDR4 is where most people learn refresh. Why it is wrong: it is a property of one generation's command set, not of refresh. §5's table shows narrower scopes existing elsewhere. Consequence: a controller ported to a generation with narrower scope keeps one counter and believes all banks are current while only some were serviced — §4's silent, localised data loss. Replacement model: scope is a property of the command, and the bookkeeping follows from it. Debugging clue: corruption concentrated in particular banks rather than spread evenly.
“Per-bank refresh exists identically in every DDR generation.” Tempting because the phrase circulates freely and sounds like a general DRAM feature. Why it is wrong: §3 verified DDR4 has no bank-scoped refresh at all; §5 shows DDR5's mechanism is same-bank with a different scope, and that per-bank is an LPDDR term this curriculum assigns to Module 24. Three generations, three different answers. Consequence: a design or a datasheet reading that assumes a command exists when it does not. Replacement model: verify the scope per generation before designing bookkeeping for it. Debugging clue: a controller issuing a refresh variant the part does not define.
“Same-bank and per-bank refresh are two names for one thing.” Tempting because both are narrower than device-wide and the names sound interchangeable. Why it is wrong: the scopes differ — one bank within each bank group is not the same set as one bank in the device — and they belong to different generations with different preconditions. §5 is explicit, and the registry description that prompted the correction is a live example of the conflation. Consequence: a scope mask that covers the wrong set, which §9's second limitation shows passes every property. Replacement model: name the scope by the resource set it covers, not by a label. Debugging clue: ages that look plausible while corruption appears in resources the design believed it served.
“If enough refresh commands were issued, refresh is correct.” Tempting because the aggregate count is the easy measurement and it is genuinely necessary. Why it is wrong: §4 — necessary and not sufficient. An adequate total distributed unevenly starves a resource, and §8's trace shows three commands in ten cycles with a resource going late. Consequence: a monitor that reports compliance while data is lost. Replacement model: the worst resource determines compliance. Debugging clue: aggregate compliant, corruption localised.
“A narrower scope is strictly better.” Tempting because it weakens the precondition, which Chapter 7.5 §3 established and which is a real availability benefit. Why it is wrong: it multiplies the controller's state, adds a selection decision that did not exist, and — per §5's reported DDR5 figures — may be available only in a mode that raises the service frequency, so the availability arithmetic is not obviously favourable. Chapter 15.5 computes it. Consequence: a design choice made on an intuition about preconditions without the aggregate cost. Replacement model: narrower scope trades bookkeeping and frequency for a weaker precondition. Debugging clue: availability no better after adopting the narrower command.
“Reset means every resource has just been refreshed.” Tempting because §7's block does exactly that, and it has to pick something. Why it is wrong: a device coming out of reset has rows of unknown age; the optimistic assumption is a convenience, not a fact, and §7's reset note says so. Consequence: a design that treats post-reset as a clean slate and skips the initialisation the device sequence requires. Replacement model: initialisation is a separate obligation, owned by the device's own sequence, not by this ledger. Debugging clue: corruption only in the first retention window after reset.
13. Interview Reasoning
“What is the architectural difference between device-wide and narrower-scope refresh?” The precondition and the bookkeeping, and the bookkeeping is the half people miss. A device-wide command requires the whole device prepared and retires one unit of one obligation, so the controller needs one counter and makes no choices. A narrower command requires only the named resources prepared — the availability benefit — and retires one unit per named resource, so the controller needs a counter per resource plus a selection decision that did not previously exist. Same interval, same physics, multiplied state.
“Which parts of that answer are generation-specific?” Almost all of the specifics. DDR4 has device-wide refresh only — verifiable in the datasheet, whose refresh period is defined across all bank groups and whose only per-bank language concerns post-package repair. DDR5 adds a same-bank variant alongside all-bank. LPDDR has a per-bank form, which is a different name for a different scope and belongs to a different module. The structural reasoning about bookkeeping is generation-neutral; every scope claim needs a generation attached.
“A controller issues plenty of refresh commands and a bank still loses data. How?” Uneven distribution. With per-resource obligations, the aggregate count can be exactly right while the selection pointer favours some resources and starves another. The measurement that catches it is the worst resource's age, never the total — and the cheapest instrumentation is to OR the scope masks over a window, because any bit that stays clear names a resource nobody served. The symptom that distinguishes it from a wrong interval is that the corruption is localised rather than uniform.
“Why should a checker decode the refresh scope independently?” Because the ledger is told which resources a command covered, and no property of the ledger can tell whether that is what the device actually did. If the design's scope mask and the generation's command definition disagree, the ages are self-consistent and confidently wrong about reality. Decoding from the command encoding is the only way the disagreement surfaces.
“Is a narrower refresh scope always better?” No, and the reasons are worth separating. It genuinely weakens the precondition, which frees banks that would otherwise have to be idle. Against that: the controller's refresh state multiplies, a selection decision appears with its own failure mode, and — at least as reported for DDR5 — the narrower command may be available only in a mode that refreshes more often, so the total availability cost can move the wrong way. The precondition benefit is local and the frequency cost is global, so the arithmetic has to be done rather than assumed.
“What does a refresh command retire?” One unit of one obligation, for each resource its scope covers, and nothing for the resources it does not. The second clause is the one that gets dropped when code is ported from a device-wide design, and dropping it makes every resource look current while only some were serviced — a bookkeeping bug that no timing checker can see, because nothing about it is a timing violation.
14. Engineering Exercises
1. Count the state. A controller manages a device with 16 independently-refreshed resources, a due age requiring 12 bits and a deadline needing one more. Give the register cost of per-resource ages, and compare with the single-counter device-wide case. Then say whether cost is a good argument against per-resource state.
Worked: 16 resources × 13 bits = 208 flops, against 13 for the single case. The comparison is the point: 208 flops is negligible in any real controller, so the cost argument against per-resource tracking is weak, and the real reasons designs omit it are inertia from a device-wide predecessor and not having noticed that the obligation multiplied.
2. Find the neglected resource. Over one window a controller issues refresh with these scope masks: 0011, 0110, 0011, 1100, 0011, 0110. Which resources are at risk, and what is the cheapest instrumentation that would have told you?
Worked: OR them: 0011 | 0110 | 0011 | 1100 | 0011 | 0110 = 1111. Every bit is covered, so no resource is entirely neglected — but the counts are uneven: bit 0 appears 3 times, bit 1 five times, bit 2 three times, bit 3 one time. So resource 3 is at risk without being starved outright. The OR is the cheapest check and it is necessary but not sufficient; the per-resource age is the sufficient one, which is why §7's block tracks ages rather than coverage.
3. Apply the service rule. Four resources start at age 0. A refresh with mask 1001 is accepted at cycle 5, and another with mask 0110 at cycle 9. Give every resource's age at cycle 12, with a due age of 6.
Worked: resources 0 and 3 serviced at 5, so at cycle 12 their age is 7 — both due. Resources 1 and 2 serviced at 9, so their age is 3 — neither due. The instructive part is that all four were serviced within the window and two are nonetheless due, which is normal and not a failure; §7's res_due is a request, not a violation.
4. Break the negative clause. Modify §7's block to credit every resource on every refresh, regardless of the mask. Which of §9's properties fires, and on which resources? Why would a device-wide configuration not catch it?
Worked: P2 fires on every resource not covered by the mask. A device-wide configuration uses an all-ones mask, so no resource is ever uncovered, P2's antecedent never occurs, and the bug is invisible — the modification is behaviourally identical for that configuration. That is precisely why C1's second cover exists, and why porting a device-wide design to a narrower scope is where this bug ships.
5. Sanity-check against the single-obligation case. Set RESOURCES to 1 and drive an all-ones mask. Argue that §7's block should then behave identically to Chapter 2.3's single-obligation tracker on the due signal, and say what would differ.
Worked: with one resource always covered, the age resets on every service and counts up otherwise, so res_due matches a single interval counter exactly. What differs is that refresh_deadline_tracker also holds an owed count and applies backpressure to traffic, which this block deliberately does not — it reports and never gates. The overlap is the due signal only, which is why §7 reuses that block for the device-wide case rather than replacing it.
6. Classify three claims. For each, say whether it is DDR4-specific, DDR5-specific, LPDDR-specific, or generation-neutral, and how confident you would be: (a) refresh commands affect all banks; (b) a same-bank refresh needs only one bank per group idle; (c) narrowing scope multiplies controller refresh state.
Worked: (a) DDR4-specific and verifiable in the datasheet — high confidence. (b) DDR5-specific and, in this curriculum, reported rather than retrieved — so medium confidence, and worth a citation before designing against it. (c) generation-neutral, because it follows from the definition of scope rather than from any specification — highest confidence of the three, which is a useful illustration that structural reasoning can be more reliable than a number you could not source.
7. Write the independent decode. Sketch the checker-side scope decode of §10 and state what it catches that no property of §7's block can.
15. Summary
Chapter 7.5 established that a refresh command requests maintenance, that its precondition spans banks, and what it blocks. This chapter asked the question that leaves open: what does one refresh command retire?
The answer is one unit of one obligation for each resource its scope covers — and nothing for the resources it does not. That second clause is the whole chapter, because dropping it is invisible to every timing check.
Device-wide scope means one obligation, one counter, and no selection decision, and the existing refresh_deadline_tracker plus refresh_rate_obligation are sufficient. Verified: DDR4 has device-wide refresh only — its refresh period is defined across all bank groups, and the only per-bank language in the datasheet concerns post-package repair.
Narrower scope multiplies the controller's state. One counter per resource, a selection decision that did not previously exist, and a compliance question that becomes per-resource. The consequence is the module's sharpest bookkeeping failure: an adequate aggregate refresh rate distributed unevenly starves a resource, so the total can be exactly right while data is lost — and §8's trace shows three commands in ten cycles with a resource going late.
The generation picture needs care, and so did this chapter's own description. The registry describes it as “per-bank refresh in DDR5”; DDR5's mechanism is same-bank, and per-bank is an LPDDR term the registry itself assigns to Module 24. I flagged that rather than teaching around it. DDR5's same-bank figures are reported from a Micron white paper I could not retrieve in primary form and are labelled throughout — the sixth DDR5 question this curriculum has left open, and by now a reliable guide to how differently the two generations are documented.
refresh_scope_ledger holds per-resource ages, applies a runtime scope mask so one ledger serves both a wide and a narrow command, saturates rather than wrapping so that neglect is reported truthfully, and reports the worst resource rather than a total. Its reset is honestly labelled as optimistic: it asserts every resource was just serviced, which a controller cannot actually know.
And the debugging discriminator generalises: uniform corruption points at the interval; localised corruption points at the scope or the selection. One question about the distribution routes the investigation to the right chapter.
16. What Comes Next
This chapter counts service and deliberately never schedules it. §8's trace showed a resource going late and offered no opinion about what the controller should have done.
Chapter 15.3 supplies that, and it starts from a verified surprise: the specification does not require refresh at every nominal interval. It publishes an explicit allowance for postponing and pulling in refresh commands, with hard limits — and it states, in as many words, that each pulled-in command reduces the number required later by one. That is a credit ledger, in a datasheet, and it is what makes Chapter 15.1 §5's due-is-not-late distinction quantitative rather than merely conceptual.
It also carries the module's two worked traces, and the second one answers a question that matters more than any parameter: when a refresh deadline is finally missed, at what earlier cycle did the bug actually begin?
Continue learning
Related tutorials
- Related topic
The Refresh Requirement
Leakage produces a rule about the passage of time rather than about any operation. What the maintenance operation actually does, why it costs device availability, and how a digital design tracks a deadline, arbitrates it against traffic, and proves it never silently drops the obligation.
- Related topic
DDR4
DDR4 is the generation where prefetch depth stops changing. With granularity already at a cache line, the rate had to come from overlapping independent accesses instead — which is what bank groups are, and why peak bandwidth became conditional on the access pattern.
- Related topic
Bank Groups
Not all bank pairs are equally independent. A bank group is the scope at which the internal column data path is shared, and the three-way classification of a request against its predecessor is the interface every later timing module consumes.
- Related topic
RAS# — Row Address Strobe
RAS# is named after a mechanism it no longer uses. In asynchronous DRAM it was literally a clock that latched a row address; in synchronous DRAM it became a level sampled by CK — and in DDR4 it is not even a dedicated pin.
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.
