DDR · Module 17
The Refresh Manager
Refresh due, refresh legal, refresh issued and refresh complete are four distinct events separated by many cycles. The manager turns an obligation into the drain, the gate and the request a scheduler consumes.
Chapter 17.1's commit condition consumed a bit called normal_issue_allowed and said nothing about where it came from. Chapter 15.3 built a ledger that reports refresh debt and explicitly declined to decide anything with it. This chapter is the block between them.
The idea it exists to defeat is the most common misconception in DDR controller design:
Refresh due, refresh legal, refresh issued and refresh complete are four different events, separated by many cycles — and a controller that collapses them into one periodic pulse is broken in a way that only appears under load.
1. What Chapter 15.3 Handed Over
Briefly, because it is prerequisite rather than content — and because getting the handover wrong is how this chapter's block ends up duplicating a ledger that already exists.
Chapter 15.3 §1 verified, from a named Micron DDR4 datasheet, that the device explicitly permits postponing and pulling in refresh commands, with limits that are counts, not durations: up to 8 postponed in 1X mode, 16 in 2X, 32 in 4X, with a separately bounded pull-in allowance of 8 in both 1X and 2X and 32 in 4X — the two allowances are not symmetric, and 2X is where they differ. Each pulled-in command reduces the number required later by one — a credit rule, and therefore integer arithmetic rather than a timer comparison.
From that it built refresh_credit_ledger, which publishes:
due the nominal interval has elapsed; an obligation exists
urgent the ledger is approaching its bound — POLICY threshold
overdue the bound has been crossed — a specification violation
debt signed, how many commands behind or aheadThis chapter adds no accounting whatsoever. It reads those four and decides what the scheduler does about them. If you find yourself writing an interval counter here, 15.3 already has one and two counters will drift apart.
2. Refresh Is Not a Request
The tempting architecture: treat refresh as a high-priority entry in the request pool, let it compete in the arbiter, give it a big priority number. It is tempting because it needs no new machinery.
It is wrong, and the reason is precise.
The practical consequence for debugging: if refresh is a gate, then “refresh was never serviced” is impossible by construction once the gate closes, and the bug must be in the gate's own transition logic. If refresh is an arbiter entry, “never serviced” is a starvation question with no bounded answer, and you are debugging a policy rather than a state machine.
3. Four Events
Name them separately, because the whole chapter is the gap between them.
1. REFRESH DUE the ledger's interval elapsed.
15.3 owns this. Nothing is required YET —
the allowance permits deferral.
2. REFRESH LEGAL all banks idle (REFab, verified in 15.2).
May be many cycles after due: open rows must
close, and tRAS may forbid closing them yet.
3. REFRESH ISSUED a REF command COMMITTED — 17.1's commit
point, not a grant. Only now does the
ledger's credit change.
4. REFRESH COMPLETE the occupancy window expired and normal
traffic is legal again.The gap between 1 and 2 is the manager's real work, and it is not small. Consider: refresh becomes due while bank 3 has a row that was activated one cycle ago. tRAS forbids precharging that row for many more cycles. The controller cannot make the device ready sooner; it can only stop making things worse and wait.
4. The Manager
Three features of this machine are the design.
PENDING permits normal issue. This is the whole point of 15.3's verified allowance. A machine that blocked traffic the instant refresh became due would throw away a flexibility the device explicitly offers — and would cost bandwidth on every interval rather than on the ones that need it.
DRAIN is a separate state from READY. In DRAIN the device is not yet idle, so refresh is not yet legal, and the controller is converging toward it. Crucially the gate is still open here: the drain is carried out by ordinary PRE commands, and blocking normal issue in DRAIN would make idleness unreachable and hang the machine — §8's RTL comment states this at the line where it matters. What DRAIN changes is policy, via preparing_for_refresh. The hard gate belongs to READY and BUSY. Collapsing DRAIN and READY is the 1-and-2 conflation of §3.
BUSY is entered on commit, not on request. The transition is labelled REF commits for the same reason every transition in 17.1 §6's FSM is.
5. Drain Is Not Instant
DRAIN deserves more than one line, because how a controller drains is one of the few places a designer has real freedom, and the naive implementation is expensive.
What must happen: every open row must close. What the manager controls: whether new rows open, and whether PRE commands are preferred. What it does not control: tRAS, which sets the earliest a just-activated row may close.
Two drain policies, both legal:
Passive drain. Stop issuing ACT, allow in-flight column commands to complete, and let banks close as their requests finish naturally. Cheap, but slow and unbounded in the worst case — a bank whose request queue keeps producing row hits may stay open indefinitely, and the drain never converges.
Active drain. Stop issuing ACT and preferentially issue PRE to every open bank as soon as each one's tRAS permits. Converges in a bounded time, at the cost of closing rows that had pending row hits — work that will have to be redone with a full ACT afterwards.
The block below implements the gate and the request and leaves the choice of drain aggressiveness to the scheduler, exposing preparing_for_refresh so that policy can act on it — and so that 15.5's refresh_availability_monitor can account for those cycles separately from the occupied ones.
6. The Policy Dial
The manager has exactly one genuinely free parameter, and naming it prevents a lot of confused argument: how long to stay in PENDING.
| Policy | Leaves PENDING when | Gains | Costs |
|---|---|---|---|
| Eager | immediately on due | simple; debt never accumulates; short worst-case drain | pays the full refresh cost at every interval, including during peak demand |
| Deferred | on urgent | moves refresh out of demand peaks; uses the verified allowance | debt accumulates; a burst of refreshes may be owed at once; longer drains |
| Opportunistic | on due if banks already idle, else on urgent | takes free refreshes when the device is quiet | needs idle detection; can still be caught by a peak |
The third row is the one worth building, and it is what the RTL below does: refresh while the machine is already idle costs almost nothing, because there is no drain to perform and no locality to destroy.
What no policy can do is change the total number of refreshes required. Chapter 15.5 established the bandwidth cost as an obligation on the device's time, and deferral moves that cost rather than reducing it. A controller that defers every refresh to the bound has not saved anything — it has arranged to pay for all of them consecutively, at a moment it no longer controls. Module 23 owns whether a given policy performs well on a given workload; this chapter's claim is only the mechanical one.
7. Who Owns Refresh State
Chapter 17.1 §8 established the rule: one piece of architectural state, one owner. Refresh is where that rule is broken most often, because the obligation is genuinely shared between two blocks and it feels natural to let each keep its own copy.
| Refresh state | Owner | Updated on | Read by | Must not be updated on |
|---|---|---|---|---|
| Interval tick | 15.3 ledger | cycle count | the ledger itself | anything in Module 17 |
| Signed debt / credit | 15.3 ledger | commit of REF; interval tick | manager, arbitration | grant of REF; request |
due / urgent / overdue | 15.3 ledger | derived from debt | manager | never written directly |
| Manager state | this chapter | due, urgent, all_banks_idle, commit | scheduler, monitor | candidate generation |
normal_issue_allowed | this chapter | pure function of state | 17.1 commit, candidate filter | ledger outputs directly |
| Occupancy elapsed | this chapter | cycle count while BUSY | manager only | request; grant |
| Occupied vs preparing cycles | 15.5 monitor | this block's two outputs | reporting | anything else |
Read the second row carefully, because it is the one that costs data. Debt is credited on commit. Not when the manager enters READY, not when it asserts refresh_request, not when the arbiter grants. The manager knows it asked; only 17.1's commit knows it happened.
The rule to carry forward: the ledger owns what is owed; the manager owns what the scheduler may do about it. Every signal crossing between them goes one way — obligations down into the manager, and a single commit event back up. A signal crossing the other way is a design smell, and a second copy of any of the ledger's state is a bug waiting for a long enough simulation.
One consequence is worth stating because it reverses an intuition. The manager is not allowed to decide that a refresh has happened. Even when it has requested one, watched the scheduler grant it, and moved to BUSY — if the commit did not occur, no refresh happened, and the debt is unchanged. err_ref_without_request in §8's RTL exists for the inverse case, and the pairing of the two is what makes the handover auditable from either side.
8. The Manager Block
// ─────────────────────────────────────────────────────────────────────
// refresh_manager_fsm
//
// CLASSIFICATION
// Synthesizable educational RTL. Sequential. One responsibility:
// converting a refresh OBLIGATION, reported by Chapter 15.3's
// ledger, into SCHEDULER CONTROL — a gate, a request, and a drain
// indication.
//
// WHAT IT DOES NOT MODEL
// - No refresh accounting. due/urgent/overdue are INPUTS from
// refresh_credit_ledger (15.3). There is no interval counter here
// and there must not be: two counters drift.
// - No physics. No leakage, charge decay, sense amplifiers, bitlines,
// retention or temperature behaviour. Modules 2 and 15 own all of
// it; this block models none of it.
// - No bank state. all_banks_idle is an input from Chapter 5.2's
// bank-state model.
// - No arbitration. Refresh is a GATE, not a candidate (§2).
// - No command encoding. Module 7 owns that.
// - Per-bank refresh (REFpb) is NOT modelled: this is the all-bank
// REFab case Chapter 15.2 verified, whose precondition is that all
// banks are idle. A narrower-scope generation needs a different
// precondition and different bookkeeping — 15.2 §5.
// ─────────────────────────────────────────────────────────────────────
module refresh_manager_fsm #(
// Refresh occupancy, in cycles. An abstract stand-in for the device's
// refresh-to-activate period; the real magnitude is density- and
// generation-dependent (Chapter 14 and 15.2) and belongs in a
// parameter, never in this file as a literal.
parameter int OCCUPANCY_CYCLES = 8,
// Take a free refresh when the device is already idle and one is due.
parameter bit OPPORTUNISTIC = 1'b1,
parameter int OCC_W = (OCCUPANCY_CYCLES <= 1)
? 1 : $clog2(OCCUPANCY_CYCLES + 1)
) (
input logic clk,
input logic rst_n,
// ── From Chapter 15.3's refresh_credit_ledger. Consumed, never
// recomputed.
input logic ledger_due,
input logic ledger_urgent,
input logic ledger_overdue,
// ── From Chapter 5.2's bank-state model. The verified REFab
// precondition (15.2 §3).
input logic all_banks_idle,
// ── From Chapter 17.1's commit point. NOT a grant. The manager
// advances on the same event every other block advances on.
input logic ref_committed,
// ── Scheduler control — this block's entire output contract.
// Low blocks NORMAL commands only; Chapter 17.1's commit
// condition exempts REF, or the machine would deadlock in READY.
output logic normal_issue_allowed,
// Asserted in READY: the scheduler should present a REF candidate.
output logic refresh_request,
// The occupancy window. Published separately from the gate because
// 15.5's refresh_availability_monitor accounts for occupied cycles
// and preparing cycles as DIFFERENT things.
output logic refresh_busy,
output logic preparing_for_refresh,
output logic [2:0] state_out,
// ── Design-error observability.
output logic err_ref_without_request,
output logic err_overdue_while_normal
);
if (OCCUPANCY_CYCLES < 1)
$fatal(1, "refresh_manager_fsm: OCCUPANCY_CYCLES must be >= 1");
typedef enum logic [2:0] {
S_NORMAL = 3'd0,
S_PENDING = 3'd1,
S_DRAIN = 3'd2,
S_READY = 3'd3,
S_BUSY = 3'd4
} ref_state_e;
ref_state_e state, next_state;
logic [OCC_W-1:0] occ_cnt;
// ── Outputs are a pure function of state. Keeping them combinational
// from the state register — rather than registering them
// separately — guarantees the gate can never disagree with the
// state that owns it, which is a class of bug that is very hard to
// see on a waveform.
always_comb begin
// DRAIN PERMITS ISSUE, and this is the subtle part. The drain is
// performed BY normal command issue -- the PRE commands that close
// the banks are normal commands. Gating them here would make
// all_banks_idle unreachable and lock the machine in DRAIN forever,
// because nothing else closes a bank. What DRAIN changes is POLICY,
// published as preparing_for_refresh: stop opening new rows, prefer
// PRE. The hard gate belongs to READY and BUSY, where an ACT would
// genuinely break the device's idle precondition or its occupancy.
normal_issue_allowed = (state == S_NORMAL) || (state == S_PENDING)
|| (state == S_DRAIN);
refresh_request = (state == S_READY);
refresh_busy = (state == S_BUSY);
preparing_for_refresh = (state == S_DRAIN);
state_out = state;
end
always_comb begin
next_state = state;
unique case (state)
S_NORMAL:
if (ledger_due) next_state = S_PENDING;
// ── The allowance lives here. Normal traffic continues; the
// obligation is outstanding but not yet compelling (15.3 §1).
S_PENDING: begin
if (ledger_urgent) next_state = S_DRAIN;
else if (OPPORTUNISTIC && all_banks_idle) next_state = S_DRAIN;
end
// ── Preparing. Issue is still PERMITTED -- the scheduler drains
// by issuing PRE -- but preparing_for_refresh tells it to stop
// opening new rows so the device converges on idle (§5).
S_DRAIN:
if (all_banks_idle) next_state = S_READY;
// ── Requesting. Note there is no timeout: if the scheduler never
// presents REF, the machine stays here and ledger_overdue
// eventually asserts. That is the correct failure — visible,
// attributable, and not silently papered over by a retry.
S_READY:
if (ref_committed) next_state = S_BUSY;
S_BUSY:
if (occ_cnt == OCC_W'(OCCUPANCY_CYCLES - 1)) begin
// Back-to-back service when debt remains: re-enter PENDING
// rather than NORMAL so a second refresh is not delayed by a
// full interval it does not owe.
next_state = ledger_due ? S_PENDING : S_NORMAL;
end
default: next_state = S_NORMAL;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_NORMAL;
occ_cnt <= '0;
end else begin
state <= next_state;
// Occupancy counts only while BUSY, and clears on entry so that
// back-to-back refreshes each get a full window.
if (state == S_BUSY) occ_cnt <= occ_cnt + OCC_W'(1);
else occ_cnt <= '0;
end
end
// ── A REF committed when the manager was not asking for one. Either
// the scheduler invented it or the gate logic is wrong; either way
// the ledger is about to be credited for something unrequested.
assign err_ref_without_request = ref_committed && (state != S_READY);
// ── Overdue is a specification violation (15.3 §3). If it asserts
// while the machine is still permitting normal traffic, the
// urgency threshold was set too late — a policy error with a
// normative consequence.
assign err_overdue_while_normal = ledger_overdue && normal_issue_allowed;
endmodule9. Trace — A Refresh Under Load
EDUCATIONAL TIMING — NOT JEDEC VALUES. OCCUPANCY_CYCLES = 8. Two banks are open when refresh becomes due, and one of them has just been activated.
cyc state due urg idle norm_ok req busy prep what happens
─── ─────── ─── ─── ──── ─────── ─── ──── ──── ──────────────
500 NORMAL 0 0 0 1 0 0 0 normal traffic
512 NORMAL 1 0 0 1 0 0 0 interval elapsed
513 PENDING 1 0 0 1 0 0 0 traffic CONTINUES
520 PENDING 1 0 0 1 0 0 0 allowance in use
538 PENDING 1 1 0 1 0 0 0 ledger says urgent
539 DRAIN 1 1 0 1 0 0 1 prepare: no new ACT
540 DRAIN 1 1 0 1 0 0 1 bank 3 PRE COMMITS
541 DRAIN 1 1 0 1 0 0 1 bank 6 tRAS not met
546 DRAIN 1 1 0 1 0 0 1 bank 6 PRE COMMITS
549 DRAIN 1 1 1 1 0 0 1 all banks idle
550 READY 1 1 1 0 1 0 0 gate CLOSES; REF requested
551 READY 1 1 1 0 1 0 0 not granted yet
552 READY 1 1 1 0 1 0 0 REF COMMITS
553 BUSY 0 0 1 0 0 1 0 occupancy starts
560 BUSY 0 0 1 0 0 1 0 occ_cnt = 7
561 NORMAL 0 0 1 1 0 0 0 traffic RESUMESThe shape of that trace is the lesson. Refresh became due at 512 and issued at 552 — forty cycles later — and the controller was not broken for any of them.
Break the interval down, because each piece has a different owner:
- 512 to 538, twenty-six cycles in
PENDING. Normal traffic ran at full rate. This is 15.3's allowance being spent deliberately, and it is bandwidth the eager policy of §6 would have given away. - 539 to 549, eleven cycles of drain.
norm_okis still high here, and that is the point: cycles 540 and 546 commitPREcommands, which is how the banks close at all. What has changed is policy —preparing_for_refreshis high, so the scheduler stops opening new rows. No refresh is happening yet, so these eleven cycles are pure cost, and §5 says a late urgency threshold makes them worse. Note cycle 541: bank 6 cannot close because tRAS has not elapsed, and no policy shortens it. - 550 to 552, three cycles in
READY. The manager is asking; the scheduler has not yet committed. Short here, but not guaranteed to be. - 553 to 560, eight cycles of occupancy. The device is working.
refresh_busyis high andpreparing_for_refreshis low, which is exactly the distinction 15.5's monitor needs to separate genuine refresh cost from drain cost.
Only eleven of the forty cycles actually blocked normal traffic — 550 to 560 — and eight of those were the refresh itself. But the drain's eleven cycles are not free either: normal issue was permitted and yet nothing useful was served, because policy had turned to closing banks. So the honest overhead is twenty-two cycles of degraded service, of which eight are the device working and fourteen are the controller getting out of its own way. A controller reporting only “refresh cost eight cycles” under-reports by nearly two thirds, and the drain portion is the part a better urgency threshold can shrink.
10. Trace — Where the Occupancy Boundary Goes Wrong
The same run with one line changed: normal traffic is re-enabled when occ_cnt reaches OCCUPANCY_CYCLES rather than OCCUPANCY_CYCLES - 1, or the state leaves BUSY one cycle early.
cyc state occ_cnt norm_ok ACT issued? device state
─── ─────── ─────── ─────── ─────────── ────────────────
552 READY - 0 no REF commits
553 BUSY 0 0 no refreshing
...
559 BUSY 6 0 no refreshing
560 NORMAL - 1 YES ← STILL REFRESHING
561 NORMAL - 1 yes occupancy truly endsAt cycle 560 the controller issues an ACT into an occupancy window that has one cycle left. This fails immediately in simulation against any device model that checks it — which makes it, of the four §3 conflations, the easy bug.
The instructive part is what makes it easy: the device notices. Compare with the 2-and-3 conflation, where the ledger is credited for a REF that never issued. Nothing in the device notices that; no timing checker sees it; the only symptom is that refreshes gradually fall behind, and the first real consequence is data loss in a part of the array nobody is watching. The loud bug is the cheap one. This is why §12's independent model reconstructs credit from committed commands rather than trusting the manager's own state.
11. What the Assertions Prove
These belong in a bind unit or the module itself — refresh_manager_fsm has its own clk and rst_n.
// ── P1. The gate and the occupancy window cannot both permit traffic.
// The §10 bug, caught structurally rather than by waiting for a
// device model to complain.
property p_busy_blocks_normal;
@(posedge clk) disable iff (!rst_n)
refresh_busy |-> !normal_issue_allowed;
endproperty
a_busy_blocks_normal: assert property (p_busy_blocks_normal);
// ── P2. A REF may commit only while the manager is requesting one.
// Catches a scheduler that invented a refresh, and a gate that
// dropped its request early.
property p_ref_only_when_requested;
@(posedge clk) disable iff (!rst_n)
ref_committed |-> refresh_request;
endproperty
a_ref_only_when_requested: assert property (p_ref_only_when_requested);
// ── P3. Refresh is issued only when the device permits it. The
// verified REFab precondition from 15.2, restated as a property.
property p_ref_requires_idle;
@(posedge clk) disable iff (!rst_n)
refresh_request |-> all_banks_idle;
endproperty
a_ref_requires_idle: assert property (p_ref_requires_idle);
// ── P4. The obligation is never silently dropped: the machine may
// re-enter NORMAL only from BUSY, i.e. only by way of a committed
// refresh. This catches a lost obligation — the failure mode with no
// device-visible symptom (§10).
// NOTE ON THE SHAPE. The antecedent must NOT also assert that the
// current state is non-NORMAL; conjoining `state != NORMAL` with
// `state == NORMAL` is a contradiction, and the property would be
// vacuous — passing forever without ever being evaluated.
property p_obligation_not_lost;
@(posedge clk) disable iff (!rst_n)
(state_out == 3'd0 && $past(state_out, 1) != 3'd0)
|-> $past(state_out, 1) == 3'd4;
endproperty
a_obligation_not_lost: assert property (p_obligation_not_lost);
// ── P5. Bounded progress, with its assumption explicit. Once urgent
// and idle, the machine must reach READY promptly — it has nothing
// left to wait for. Stated with the antecedent it genuinely needs,
// so it cannot fail on a legitimate long drain.
property p_urgent_idle_reaches_ready;
@(posedge clk) disable iff (!rst_n)
(state_out == 3'd2 && all_banks_idle) |=> (state_out == 3'd3);
endproperty
a_urgent_idle_reaches_ready: assert property (p_urgent_idle_reaches_ready);
// ── Covers. Each names a scenario that must actually occur.
c_defer_used: cover property (@(posedge clk) disable iff (!rst_n)
state_out == 3'd1 ##[1:$] state_out == 3'd1);
c_long_drain: cover property (@(posedge clk) disable iff (!rst_n)
state_out == 3'd2 [*5]);
c_back_to_back: cover property (@(posedge clk) disable iff (!rst_n)
state_out == 3'd4 ##1 state_out == 3'd1);What they do not prove. Nothing here shows the refresh deadline is met — that is 15.3's ledger and its overdue output, and a manager can satisfy P1 through P5 while being far too slow. Nothing shows the drain terminates: P5 assumes all_banks_idle has already arrived, and if the scheduler never precharges, the machine sits in DRAIN forever with every property still passing. That gap is deliberate and it is the honest one — liveness here depends on a block this one does not own, and pretending otherwise with a vacuous property would be worse than leaving it open.
12. The Independent Model
The model must reconstruct refresh service from committed commands only, never from the manager's state — otherwise it agrees with a manager that credits itself wrongly.
Maintain independently: a count of committed REF commands; the cycle of each; an expected-obligation count derived from elapsed intervals using 15.3's rules; and a per-cycle record of whether any normal command was committed while the reconstructed occupancy window was open.
REFRESH ACCOUNTING DIVERGENCE
cycle : 4,096
REF commands committed : 14
obligations elapsed : 17
reconstructed debt : 3
manager reported state : NORMAL
ledger reported due : 0
diagnosis : the manager left READY without a committed REF on
three occasions; each cleared the request but no
command reached the device.
first event: cycle 1,204 — READY at 1,203, NORMAL at 1,204,
ref_committed low throughout
device-visible symptom : NONE at this time
projected consequence : gap bound crossed at approximately
cycle 5,900 on the current intervalThe last two lines are why the model exists. There is no symptom — and the model can still name the first bad cycle and project when it becomes a violation.
13. Corner Cases
| Situation | Correct behaviour | Failure if mishandled |
|---|---|---|
| due while all banks already idle | opportunistic path to DRAIN then READY | a free refresh wasted; full drain later |
| due while queue is empty | same — nothing to drain | manager waits for traffic that never comes |
urgent asserts during DRAIN | no effect; already draining | redundant transition |
REF not granted for many cycles | stay in READY; overdue eventually asserts | a retry timer hides a real scheduler bug |
| occupancy expires with debt remaining | BUSY to PENDING, not NORMAL | a second refresh waits a full interval it does not owe |
OCCUPANCY_CYCLES = 1 | OCC_W guarded to 1; one busy cycle | zero-width counter |
reset during BUSY | S_NORMAL, counter cleared | gate stuck low after reset — total deadlock |
ref_committed while NORMAL | err_ref_without_request | ledger credited for a phantom refresh |
gate low and REF granted | commits — 17.1 exempts REF | deadlock in READY |
| back-to-back refresh owed | PENDING re-entered immediately | debt grows unboundedly under sustained load |
The reset row is worth dwelling on. A manager that resets into any state other than NORMAL — or that resets its occupancy counter without resetting its state — can come up with normal_issue_allowed low and no path to raise it. The controller then issues nothing, forever, and the symptom is indistinguishable from a dead scheduler.
14. Synthesis, Cost and Limits
Cost. Three state bits, one occupancy counter, and a handful of comparators. Nothing here is a timing or area concern, and it is worth saying so explicitly: the refresh manager is one of the cheapest blocks in a DDR controller and one of the most consequential. Its cost is measured in the bandwidth its policy spends, not in gates.
Where it sits. normal_issue_allowed feeds 17.1's commit condition and the candidate filter, so it is on the critical path described in 17.1 §14. Because it is a registered output of a small FSM it arrives early and adds essentially nothing to that path — which is a reason to keep it a pure function of state, as §8 does, rather than computing it from ledger inputs combinationally.
What a production refresh manager has that this does not:
- Per-bank refresh (
REFpb). Chapter 15.2 §5 established that narrower-scope refresh exists, with a weaker precondition — only the named banks must be idle. That changes the drain from device-wide to bank-scoped and changes the bookkeeping from one counter to per-bank counters. It is a genuinely different manager. - Temperature-dependent refresh rate. The 2X and 4X modes of 15.3 §1 exist because the required rate varies with temperature. A production manager reads a temperature sensor or a mode register and changes the interval — which changes the allowance limits too, since they are mode-dependent.
- Self-refresh and power-down. Entry and exit interact with everything here and are not modelled.
- Multi-rank refresh staggering. Refreshing all ranks simultaneously creates a current surge; real controllers stagger them, which needs per-rank managers and a stagger policy.
- ZQ calibration, which competes for the same idle windows.
None of these change the four-event structure of §3. All of them add states or parameters.
15. Debugging
Symptom: the controller issues nothing at all, indefinitely. Read state_out first — it is three bits and it answers the question immediately. S_DRAIN forever means the drain is not converging: some bank is never being precharged, so look at whether the scheduler actually prefers PRE while preparing_for_refresh is high, or whether a request stream of row hits is keeping a bank open. S_READY forever means the scheduler is not presenting REF — check that 17.1's commit condition exempts REF from the gate, which is the deadlock of §13's ninth row. S_BUSY forever means the occupancy counter is not advancing.
Symptom: refresh is never serviced under heavy traffic. If refresh is a gate, this should be impossible — so the first question is whether somebody made it an arbiter entry after all (§2). If it genuinely is a gate, check whether ledger_urgent ever asserts: a threshold set at or beyond the bound leaves the machine in PENDING until overdue, which is what err_overdue_while_normal exists to catch.
Symptom: throughput collapses long before the deadline. The opposite failure, and the more common one in practice. The manager is leaving PENDING too early — either the policy is eager when it need not be, or ledger_urgent is asserting far ahead of the real bound. Compare time-in-PENDING against time-in-DRAIN across a run: §9's trace spent twenty-six cycles deferring and eleven draining, and a controller whose ratio is inverted is paying drain cost at every interval.
Symptom: refreshes gradually fall behind, with no error anywhere. §10's silent failure. ref_committed is not reaching the ledger, or the ledger is credited from refresh_request instead. Only the independent model of §12 finds this, and only if it reconstructs from committed commands.
16. Misconceptions
“Refresh is issued every tREFI.” Chapter 15.3 verified an explicit allowance for postponing and pulling in. Consequence: the flexibility the device offers is discarded, and refresh cost lands in demand peaks. Clue: a controller whose refresh timing is perfectly periodic under all loads.
“tREFI is a periodic pulse generator.” It is an interval in a credit ledger whose limits are counts, not durations. Clue: a timer comparison where there should be integer arithmetic.
“Refresh due means REF must issue this cycle.” §3 — due, legal, issued and complete are four events. Clue: a design with no drain state.
“Refresh is just another request with high priority.” §2 — a competitor can lose, and the precondition is a phase change no priority number expresses. Clue: normal commands occasionally issued inside a refresh window.
“A refresh manager and a refresh command are the same thing.” Chapter 15.2 owns the command; this chapter owns the block that decides when one is wanted. Clue: refresh logic scattered across the scheduler with no single owner.
“The refresh cost is the occupancy window.” §9 — nineteen blocked cycles, eight of them occupancy. The drain is real cost and is invisible unless measured separately. Clue: a bandwidth model that matches simulation at low load and over-predicts at high load.
“Deferring refresh reduces its cost.” §6 — it moves the cost. The total is set by the device. Clue: a policy that always defers to the bound, and periodic throughput cliffs.
“All banks must be idle for every refresh.” True for the all-bank REFab this block models and verified in 15.2 — but per-bank refresh has a weaker precondition. Clue: a design ported to a narrower-scope generation that still drains the whole device.
17. Interview Reasoning
“Why isn't refresh simply issued every tREFI?” Because the device explicitly permits postponing and pulling in, with bounded counts, and because REF is not legal until the banks are idle. A strong answer names both reasons — the allowance and the precondition — since they are independent.
“What states would you put in a refresh manager, and why?” Answer with the four events of §3 rather than a list of state names, then show which states the events force: something that permits deferral, something that stops opening rows and waits for idle without blocking the precharges that get it there, something that requests once idle and closes the gate, something that holds off traffic during occupancy.
“Should refresh urgency affect arbitration?” No — and explaining why is the discriminating question. It affects legality, globally and for every normal command at once. An arbiter entry can lose; a gate cannot.
“Your controller loses throughput at every refresh interval, well before any deadline. Where do you look?” Time in PENDING versus time in DRAIN, and the urgency threshold. The manager is almost certainly leaving PENDING on due rather than on urgent.
“Refreshes are falling behind but nothing errors. How do you find it?” Reconstruct service from committed commands independently; the manager's own state cannot be trusted to report on itself. Then look for the request being cleared without a commit.
“Where does the refresh gate connect in your scheduler?” Into the commit condition and the candidate filter — and with REF exempted from it, or the machine deadlocks waiting for a command its own gate forbids.
18. Exercises
1. §9's trace spends eleven cycles in DRAIN. Give two distinct changes that shorten it, and say which one costs row-buffer locality.
2. Set OPPORTUNISTIC to 0. Rewrite the trace's first twenty cycles for a workload that goes idle at cycle 515. How many cycles of drain does the eager-only machine pay that the opportunistic one avoids?
3. The BUSY exit goes to PENDING when ledger_due is still high. Construct the load under which removing that and always going to NORMAL causes unbounded debt growth.
4. Write the property that would have caught §10's early exit without a device model. Which of P1 through P5 already does, and why is it not enough on its own?
5. P5 assumes all_banks_idle. Write the liveness property you would want instead, then state precisely which block must be in the verification scope for it to be non-vacuous.
6. A colleague adds a timeout in S_READY that returns to DRAIN after 32 cycles. Name the bug class this converts from loud to silent.
7. Adapt the FSM for per-bank refresh (15.2 §5). Which single input changes shape, and which output must become a vector?
8. err_overdue_while_normal asserts in a long regression. Is this a policy bug or a specification violation? Justify using 15.3 §3's definitions.
19. Where This Goes
The gate is built, and with it 17.1's third commit term has an owner.
What remains is the second term's producer. Chapter 17.4 builds the policy that turns a legal-candidate set into a single grant — and it inherits a hard constraint from this chapter: refresh has already been removed from its input by the time it runs, so the arbiter never sees refresh at all. It chooses among normal commands, or it does not run. 17.5 then closes the loop at ingress, where the backpressure that begins in this chapter's DRAIN state finally reaches the upstream master.
Continue learning
Related tutorials
- Related topic
Why Refresh Exists
Every timing parameter so far has been a minimum. Refresh inverts the specification: the datasheet gives the interval as a maximum with no minimum at all, which makes it a service-rate obligation over a population of rows rather than a single deadline.
- Related topic
Refresh Scheduling
The specification does not require refresh at every nominal interval. It publishes an explicit allowance for postponing and pulling in commands, with hard limits — and states that each pulled-in command reduces the number required later by one.
- 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
Refresh (REF)
Refresh is the first command whose legality depends on more than one bank, and the first that occupies the device rather than requesting a transfer. DDR5's same-bank variant exists precisely to weaken that precondition.
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.
