Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 ahead

This 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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

The refresh manager state machine, with five states. The machine starts in normal, where normal command issue is permitted and no refresh obligation is outstanding. When the credit ledger from chapter fifteen point three reports the interval elapsed, it moves to pending. In pending, normal issue is still permitted, because the verified postponement allowance means an obligation does not yet compel action; this is where deferral policy lives. From pending the machine moves to drain when the ledger reports urgency, or opportunistically when the banks happen to be idle already. In drain, normal command issue is still permitted, because the precharge commands that close the banks are themselves normal commands; what changes is policy, so no new rows are opened and open rows close until the device converges on idle. When all banks are idle the machine reaches ready, where the refresh request is asserted to the scheduler. When the refresh command commits, the machine enters busy for the occupancy window, during which normal issue remains blocked. When occupancy expires the machine returns to normal, or back to pending when the ledger still reports debt, so a second refresh is not delayed by an interval it does not owe.NORMALPENDINGDRAINREADYBUSYledger reports dueledger reports dueurgent, or banks already idleurgent, or banks already idleurgent, orbanks already…debt remains: serve againdebt remains: serve againdebtremains:…all banks idleall banksidleREF commitsREF commitsoccupancy expiresoccupancy expires

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.

PolicyLeaves PENDING whenGainsCosts
Eagerimmediately on duesimple; debt never accumulates; short worst-case drainpays the full refresh cost at every interval, including during peak demand
Deferredon urgentmoves refresh out of demand peaks; uses the verified allowancedebt accumulates; a burst of refreshes may be owed at once; longer drains
Opportunisticon due if banks already idle, else on urgenttakes free refreshes when the device is quietneeds 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 stateOwnerUpdated onRead byMust not be updated on
Interval tick15.3 ledgercycle countthe ledger itselfanything in Module 17
Signed debt / credit15.3 ledgercommit of REF; interval tickmanager, arbitrationgrant of REF; request
due / urgent / overdue15.3 ledgerderived from debtmanagernever written directly
Manager statethis chapterdue, urgent, all_banks_idle, commitscheduler, monitorcandidate generation
normal_issue_allowedthis chapterpure function of state17.1 commit, candidate filterledger outputs directly
Occupancy elapsedthis chaptercycle count while BUSYmanager onlyrequest; grant
Occupied vs preparing cycles15.5 monitorthis block's two outputsreportinganything 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────
// 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;

endmodule

9. 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 RESUMES

The 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_ok is still high here, and that is the point: cycles 540 and 546 commit PRE commands, which is how the banks close at all. What has changed is policy — preparing_for_refresh is 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_busy is high and preparing_for_refresh is 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 ends

At 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 interval

The 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

SituationCorrect behaviourFailure if mishandled
due while all banks already idleopportunistic path to DRAIN then READYa free refresh wasted; full drain later
due while queue is emptysame — nothing to drainmanager waits for traffic that never comes
urgent asserts during DRAINno effect; already drainingredundant transition
REF not granted for many cyclesstay in READY; overdue eventually assertsa retry timer hides a real scheduler bug
occupancy expires with debt remainingBUSY to PENDING, not NORMALa second refresh waits a full interval it does not owe
OCCUPANCY_CYCLES = 1OCC_W guarded to 1; one busy cyclezero-width counter
reset during BUSYS_NORMAL, counter clearedgate stuck low after reset — total deadlock
ref_committed while NORMALerr_ref_without_requestledger credited for a phantom refresh
gate low and REF grantedcommits — 17.1 exempts REFdeadlock in READY
back-to-back refresh owedPENDING re-entered immediatelydebt 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

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.