Skip to content
VLSI Mentor

DDR · Module 30

Controller-Scheduling Question

Whiteboard a scheduler. The failure is collapsing request, candidate, grant and committed command into one thing — and the interviewer's next three additions expose it. Includes a fairness fix that issues illegal commands.

“Design a DDR memory-controller scheduler on the whiteboard.” It is the broadest question in the set and the one most often failed with a correct-sounding answer.

The failure is not a missing feature. Nobody is marked down for omitting a prefetcher. The failure is collapsing four distinct things into one — and the interviewer does not have to point it out, because their next three additions will do it for them.

The law this chapter is built on:

A request is not a candidate, a candidate is not a grant, and a grant is not a committed command. Answer which requests are legal before which one should issue, and never in the same box.

1. What the Question Is Really Grading

Four things, and only the fourth is about DDR.

What is gradedHow it shows
Whether you separate legality from policythe first box you draw
Whether you hold four stages aparthow you answer "why didn't it issue?"
Whether your structure absorbs a new requirementthe three perturbations in §6–§8
Whether you know the DDR-specific constraintswhich rules you name, and their scope

The third row is the whole interview. An interviewer with fifteen minutes cannot check a scheduler's correctness. What they can do is add one requirement and watch whether you change a layer or start again.

So the answer is graded on structure, and structure is decided in the first two minutes — which is why §2 is about build order rather than about DDR.

2. Build Order — What to Draw First

Draw the pipeline, in this order, and say the order out loud as you draw it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   1  REQUEST INGEST        what arrives, and what an entry retains
   2  EXPANSION             one request -> one to three commands
   3  LEGALITY              per candidate command: is it legal THIS cycle?
   4  POLICY                among the legal ones, which is preferable?
   5  ARBITRATION           one winner, one bus slot
   6  COMMIT                the single instant state may change
   7  TIMING STATE UPDATE   fed ONLY by the commit

Stages 3 and 4 being separate boxes is the answer to the question. Everything else is detail that can be filled in under questioning.

CURRICULUM-DERIVED from 17.4 §3, which owns the rule and states the consequence exactly: a broken legality filter issues illegal commands with a brilliant policy; a broken policy issues legal commands badly. The first is a protocol violation and the second a performance problem, and keeping them in separate blocks is what keeps their bugs separable.

Stage 7 is the loop that most whiteboards omit. Timing state is updated by the commit, never by the grant and never by the request — 17.1 owns the commit point as the single instant architectural state may change. A design that updates timing state on the grant has a timing model that diverges from the bus the first time a grant does not commit.

The three wrong first moves, in descending order of how often they happen:

Wrong first moveWhat it signalsWhat it costs later
Drawing the arbiter firstpolicy is being treated as the problemlegality ends up inside it — 17.4 §3's inverse error, which is worse
Drawing the queue first and stoppingthe question was heard as design a queueexpansion is never reached, so one request stays one command
Listing heuristics — FR-FCFS, open-page, write batchingrecall is substituting for structureno place to put them; 23.4 and 23.5 own them and they are stage 4

The third is the most common and the most damaging, because it sounds knowledgeable. Naming a heuristic before drawing the box it lives in gives the interviewer nowhere to put it, and every follow-up then lands on a structure that does not exist.

3. The Four-Way Separation

One command's life has four stages, and each collapse produces a specific, recognisable wrong answer.

StageIt meansCollapsing it into the next produces
Requesta requester asked for bytesone request is one command — no precharge, no activate, no expansion
Candidatea specific command that is legal this cyclethe arbiter checks timing17.4 §3's worse error
Grantpolicy and arbitration chose itchosen means issued — no accounting for the cycle a grant is dropped
Committed commandit is on the bus; state may now changetiming state updates on selection — a model that drifts from the bus

Each row is a different chapter's subject and the four-way checklist is this chapter's. 17.2 owns row one, 17.4 rows two and three, 17.1 row four.

The single question that tests all four at once: a read has been waiting 200 cycles — why has it not issued? A design with four stages answers precisely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   it is not yet a CANDIDATE      -> a timing rule blocks it; WHICH rule
   it is a candidate, never GRANTED -> policy prefers others; starvation
   granted but not COMMITTED      -> the bus slot went elsewhere
   committed, and this is the NEXT command of the same request
                                  -> expansion; it was never one command

A design with one box answers "it's busy". CURRICULUM-DERIVED from 17.4 §3: an empty result from a combined legality-and-policy block is just a loop that found nothing, while an empty mask arriving at a pure policy block is a reportable fact with an attributable cause. That is the argument for the separation, and it is a debuggability argument rather than an aesthetic one — which is why it is worth making unprompted.

4. The Life of One Command

Four stages, and the three edges people forget.

A state machine with four states tracing one command through a memory-controller scheduler. The command begins in the queued state, where a request entry exists and the command has been expanded but is not yet legal. When every applicable timing rule is met it becomes a candidate. From candidate it can return to queued, because legality is re-evaluated every cycle and a rule can block it again, for instance after another command commits to the same resource. From candidate it can also loop back to itself when it is legal but not chosen by policy, and that self loop is exactly where starvation lives. When arbitration selects it, it moves to granted, and when the command bus slot is taken that cycle it reaches committed, the accepting state and the single instant at which architectural state may change. From committed it can return to queued, because one request expands into several commands and the next command of the same request starts the cycle again. The two edges people forget on a whiteboard are the return from candidate to queued, which shows that legality is not latched, and the return from committed to queued, which shows that one request is not one command.QUEUEDCANDIDATEGRANTEDCOMMITTEDevery timing rule metevery timing rule meta rule re-blocks ita rule re-blocks itlegal, not chosenlegal, not chosenarbitration selectsarbitration selectsbus slot this cyclebus slot this cyclenext cmd, same requestnext cmd, same requestnext cmd,same…

Three edges are worth more than the four states.

CANDIDATE back to QUEUED — legality is not latched. Another command committing to the same resource can withdraw candidacy on the next cycle. A whiteboard that computes a legal set once and then arbitrates over it has a stale set, which is 30.2's stale-state hazard in scheduler form.

The self-loop on CANDIDATE — legal every cycle and never chosen. That loop is starvation, and 17.4 §8 owns demonstrating it concretely rather than asserting it. §8 is the interviewer asking you to bound it.

COMMITTED back to QUEUEDone request is many commands. A conflicting read commits a precharge, then re-enters the queue for an activate, then again for the column command. A candidate whose diagram has no such edge has answered a question about one command, not about a scheduler.

5. Working the Question

Q1 (establishing). What does the scheduler choose among?

Commands, not requests — and stating that distinction is the answer. Each queued request has expanded into the specific next command it needs, which depends on the target bank's row state: a hit needs a column command, a miss an activate, a conflict a precharge first. CURRICULUM-DERIVED from 17.1 §3.

So the input to legality is a set of candidate commands, one or more per request, and the output is at most one commit per cycle — because there is one command bus.


Q2 (applying). Five requests are queued. Which one issues this cycle?

The correct answer is two phases and refusing to merge them.

Phase one, legality — a filter that knows nothing about preference:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   for each candidate command, ask ONLY:
     does the target resource's timing state permit it THIS cycle?

   the rules, with their SCOPE -- and scope is the part that is
   graded (30.3 §3, 14.6):

     same bank        tRCD, tRP, tRAS, tRC
     same bank group  the longer column spacing
     any bank         the shorter column spacing; tRRD; tFAW
     the whole rank   direction turnaround; refresh occupancy

   output: a MASK. no ordering, no preference, no scores.

Phase two, policy — a chooser that never re-checks legality:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   among the legal mask ONLY, prefer in layers (17.4 §4):

     layer 1  urgency   an aged entry must become reachable (§8)
     layer 2  locality  row hits over misses (23.3, 23.4)
     layer 3  direction batch to avoid turnaround (§7)
     layer 4  rotation  a pointer, advanced on COMMIT (17.4 §7)

   each layer NARROWS the set. if a layer would empty it,
   it passes the set through unchanged.

The narrowing rule is the mechanism worth stating. A layer that empties the set has expressed a preference that cannot be satisfied, and the correct behaviour is to ignore that preference rather than to issue nothing. CURRICULUM-DERIVED from 17.4 §4's layered mask: layers, not numbers.

And the sentence that shows you understand why layers rather than scores: a weighted score collapses the two phases back together, because a large enough weight can outvote legality. Layers cannot, because legality is not a layer — it is the set the layers operate inside.

6. Perturbation One — The Interviewer Adds Refresh

“Now handle refresh.”

The tempting answer is a high-priority request class, and it is wrong for a structural reason — not because the priority is too low.

CURRICULUM-DERIVED from 17.3 §2, which owns the argument in full: an arbiter's job is to choose among legal candidates, so if refresh is a candidate, then by construction there exist cycles where something else is chosen instead. Raising its priority makes those cycles rarer, and rarer is not a property you can bound.

And the constraint is the wrong shape for an arbiter. An all-bank refresh requires all banks idle before issue, so the controller cannot merely prefer refresh — it must stop opening rows and wait for open ones to close. That is a phase change in what the scheduler is permitted to do, lasting many cycles, and no priority number expresses it.

So what changes in the §2 pipeline:

StageChange
3 Legalityduring refresh occupancy, normal commands are illegal — they leave the mask entirely
4 Policya new input, preparing_for_refresh: stop opening rows, prefer precharges
5 Arbitrationunchanged
6 Committhe refresh commit is what starts the occupancy window

Two boxes changed and nothing was restructured — which is the answer the perturbation is testing for. And the distinction to say out loud: during the drain the read is legal and merely not preferred, which is policy; during occupancy it is not a candidate at all, which is legality. A refresh does not outrank a read; for part of the window it removes the read from the legal set17.3 §2.

7. Perturbation Two — The Interviewer Adds Writes

“Now there are writes too.”

The trap is answering add a write queue. A second queue is trivial. What writes actually introduce is a shared resource with a direction, and direction is structurally unlike every other dimension.

CURRICULUM-DERIVED from 17.4 §10, which owns direction as a dimension with its own, independent starvation problem.

Why it is different from the other layers: every other preference costs a little efficiency when you get it wrong. Direction costs a turnaround penalty on the shared data bus every time you change it, so the efficient policy is to batch, and batching is exactly what starves the other direction.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   DERIVED from a stated model, ILLUSTRATIVE turnaround = 8 cycles,
   ILLUSTRATIVE burst occupancy = 4 cycles per command.

   alternating R W R W R W        6 commands, 5 turnarounds
     data cycles  6 * 4 = 24
     turnaround   5 * 8 = 40
     -> 24 / 64 = 37.5% of the bus carried data

   batched  R R R W W W          6 commands, 1 turnaround
     data cycles  6 * 4 = 24
     turnaround   1 * 8 =  8
     -> 24 / 32 = 75% of the bus carried data

   so batching DOUBLES useful bus occupancy here -- and a read
   arriving at the start of a write batch waits for the whole batch.

That is the trade stated as a number rather than as an opinion, and stating it as a number is the difference between a senior answer and a plausible one. Chapter 12.4 owns the measure this is — slot utilisation — and why it must stay distinct from the other three.

What changes in the pipeline: direction enters as layer 3 of policy with a bounded batch length, and turnaround enters legality as a rank-scope rule. Two stages again, and the bound is the part that must be stated — an unbounded batch is §8's problem with a different name.

8. Perturbation Three — “Prove It Cannot Starve”

This is the one that separates levels, because a layered design starves by construction and saying so is the correct first move.

CURRICULUM-DERIVED from 17.4 §8, which demonstrates starvation concretely: a locality preference plus a stream of row hits to one bank means another bank's request is legal every cycle and chosen on none. §4's self-loop is that entry.

So the honest answer begins: as described, it starves, and here is the mechanism — then the fix.

CURRICULUM-DERIVED from 17.4 §9: the fix must make the starving entry reachable, which means a layer above the preference, not a bigger number inside it. Age each entry; above a threshold, the aged entry becomes the layer-1 set, and the preference layers then narrow within it and find nothing to narrow.

Three things an interviewer will probe, and all three have real answers:

The threshold must exceed the longest legitimate wait, or the mechanism fires constantly and the preference layer is disabled. CURRICULUM-DERIVED from 17.4 §9: that bound includes tRAS plus tRP plus tRCD in the conflict case, plus the refresh drain and occupancy, plus contention from every other entry.

The age counter must be wide enough to hold the threshold. A threshold of 64 in a 6-bit counter makes the comparison unreachable — a fairness mechanism present in the source and absent in the silicon, and the COUNT-versus-INDEX hazard in its most consequential form.

And the bypass must select only among legal candidates. This is where the fix most often breaks the thing it was added to, and §9 is that code.

The qualification that makes the answer senior: there is no single correct bypass mechanism. CURRICULUM-DERIVED from 17.4 §9, which lists age thresholds, bypass counting, urgency promotion and rotating preference as all legitimate with different bounds. What is general is that a preference layer with no bounded override starves and the override must sit above the preference to be reachable. Claiming one mechanism is the answer is the failure; naming the general rule and one mechanism is the answer.

9. RTL Review — Candidate Generation and the Bounded Bypass

The intended contract:

  1. legal_mask is an input. This block must not compute or re-check legality — 17.4 §3.
  2. grant must be a subset of legal_mask: an entry that is not legal can never be granted, whatever its age.
  3. At most one grant bit is set, because there is one command bus.
  4. Policy may only narrow the legal set; a layer that would empty it passes it through.
  5. An entry legal on every cycle must be granted within AGE_THRESHOLD cycles of becoming legal.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// sched_policy_pick -- INTENTIONALLY DEFECTIVE, for review (§9).
//
// CLASSIFICATION: synthesisable, ILLUSTRATIVE, and CONTAINS A BUG.
//
// WHAT IT IS MEANT TO DO: the five-clause contract above -- choose one
// grant from a legality mask supplied by a separate block, preferring
// row hits, with a bounded age-based override so no legal entry can
// starve.
//
// WHY IT EXISTS HERE: §8 establishes that a layered policy starves by
// construction and that the override must sit ABOVE the preference to
// be reachable. This block is what happens when "above the preference"
// is implemented as "above everything".
//
// HOW TO RUN IT: hold one entry's age above AGE_THRESHOLD while its
// legal_mask bit is LOW.
// EXPECTED RESULT under clause 2: it is NOT granted.
// EXPECTED TRACE: grant must remain a subset of legal_mask on every
// cycle, including the cycle the age threshold is crossed.
//
// SYNTHESIS: one age counter per entry plus two priority encoders.
// LIMITATIONS: models one dimension of preference (row hit) and one
// override (age). Direction batching (§7) and rotation are out of
// scope and are NOT what the bug is.
// ---------------------------------------------------------------------
module sched_policy_pick #(
  parameter int NUM_ENTRIES   = 8,
  parameter int AGE_THRESHOLD = 64,   // ILLUSTRATIVE
  // COUNT, not INDEX: the counter must REPRESENT AGE_THRESHOLD, so it
  // needs $clog2(AGE_THRESHOLD+1) bits. Too narrow and the comparison
  // is unreachable -- §8's "present in the source, absent in the
  // silicon". This is the hazard the guard below exists to prevent.
  parameter int AGE_W         = $clog2(AGE_THRESHOLD + 1)
)(
  input  logic                    clk,
  input  logic                    rst_n,

  input  logic [NUM_ENTRIES-1:0]  valid,
  input  logic [NUM_ENTRIES-1:0]  legal_mask,   // clause 1: an INPUT
  input  logic [NUM_ENTRIES-1:0]  row_hit,      // preference, layer 2

  input  logic                    commit,       // a grant reached the bus
  input  logic [NUM_ENTRIES-1:0]  freed,

  output logic [NUM_ENTRIES-1:0]  grant,
  output logic                    urgent_active
);
  initial begin
    if (AGE_THRESHOLD >= (1 << AGE_W))
      $fatal(1, "sched_policy_pick: AGE_W too narrow for AGE_THRESHOLD");
  end

  logic [AGE_W-1:0] age [NUM_ENTRIES];

  always_ff @(posedge clk) begin
    if (!rst_n) begin
      for (int i = 0; i < NUM_ENTRIES; i++) age[i] <= '0;
    end else begin
      for (int i = 0; i < NUM_ENTRIES; i++) begin
        if (freed[i] || (commit && grant[i]))        age[i] <= '0;
        else if (valid[i] && age[i] != {AGE_W{1'b1}}) age[i] <= age[i] + 1'b1;
      end
    end
  end

  // Lowest set bit of a mask -- one bus, one grant (clause 3).
  function automatic logic [NUM_ENTRIES-1:0] lowest(input logic [NUM_ENTRIES-1:0] m);
    return m & (~m + 1'b1);
  endfunction

  logic [NUM_ENTRIES-1:0] cand, pref, aged;

  // Layer 0 -- the legal set. Consumed, never recomputed (clause 1).
  assign cand = valid & legal_mask;

  // Layer 2 -- prefer row hits, but only NARROW (clause 4).
  assign pref = (|(cand & row_hit)) ? (cand & row_hit) : cand;

  // Layer 1 -- the bounded age override of §8.
  always_comb begin
    aged = '0;
    for (int i = 0; i < NUM_ENTRIES; i++)
      if (valid[i] && age[i] >= AGE_THRESHOLD[AGE_W-1:0])
        aged[i] = 1'b1;                    // <-- THE DEFECT
  end

  assign urgent_active = |aged;
  assign grant         = (|aged) ? lowest(aged) : lowest(pref);
endmodule

Before reading on: which clause, what trace, and why does it pass a one-hot check and a fairness check?

10. The Defect — The Fix Broke the Contract It Sat Above

The violated clause is 2, and the defect is an omitted term: aged is built from valid alone, where the contract requires valid & legal_mask.

The override was placed above the preference — correctly, per §8 — and above legality as well, which is not the same thing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ILLUSTRATIVE AGE_THRESHOLD = 64, NUM_ENTRIES = 8.
   entry 3 targets a bank whose row was just activated; tRCD has
   not elapsed, so legal_mask[3] = 0. entry 3 has been valid and
   waiting, so its age advances regardless.

   cyc   legal_mask  valid  age[3]  aged   grant     legal?
   ───   ──────────  ─────  ──────  ────   ─────     ──────
   700   0b11110111  0b1..1    62   0b0..  entry 0    yes
   701   0b11110111  0b1..1    63   0b0..  entry 0    yes
   702   0b11110111  0b1..1    64   1<<3   entry 3    NO  <--
                                    ^ aged ignores legal_mask

   grant = 0b00001000 while legal_mask[3] = 0.
   the controller commits a column command before tRCD has
   elapsed -- a protocol violation, produced by the fairness fix.

This is precisely 17.4 §3's inverse error, which is worse: a design that lets policy decide legality issues an illegal command with a perfectly sensible-looking grant.

Now the question an interviewer is actually asking: why does this pass review?

Because every property anyone thinks to write about an arbiter is about its shape. grant is one-hot. The fairness bound holds — better than required, since it fires even earlier than it should. urgent_active pulses exactly when documented. Nothing about the block's shape is wrong. The wrong thing is its content: which set it chose from. §11 is that distinction.

And note the second-order effect, which is the part worth saying unprompted: because aged ignores legality, an entry blocked for a long time accumulates age while it cannot possibly be served, so it crosses the threshold at the worst possible moment — the cycle it is most likely still illegal. The defect does not merely permit illegality occasionally; it is biased toward producing it.

The corrected layer, and the one-line rule to state alongside it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // CORRECTED. The override sits above the PREFERENCE, not above
  // LEGALITY. Clause 2: grant is a subset of legal_mask whatever an
  // entry's age. 17.4 §3 -- a broken policy issues legal commands
  // badly; a policy that decides legality issues illegal commands.
  always_comb begin
    aged = '0;
    for (int i = 0; i < NUM_ENTRIES; i++)
      if (cand[i] && age[i] >= AGE_THRESHOLD[AGE_W-1:0])
        aged[i] = 1'b1;                  // cand = valid & legal_mask
  end

And the consequence to acknowledge rather than hide: with the fix, clause 5's bound now applies only to entries that are legal, which is the only bound that was ever achievable. An entry that is never legal cannot be rescued by an arbiter, and if that happens the fault is upstream — a timing rule that never releases, which is 17.4 §9's threshold argument read backwards. Saying which bound you can actually prove is stronger than claiming the one you cannot.

11. SVA Review — Shape Versus Content

Three properties offered as proof that the arbiter is correct:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // All three pass on the defective block in §9.
  property p_grant_onehot0;
    @(posedge clk) disable iff (!rst_n) $onehot0(grant);
  endproperty
  assert property (p_grant_onehot0) else $error("multiple grants");

  property p_grant_is_valid;
    @(posedge clk) disable iff (!rst_n) (grant != '0) |-> ((grant & valid) == grant);
  endproperty
  assert property (p_grant_is_valid) else $error("granted an invalid entry");

  property p_urgent_reported;
    @(posedge clk) disable iff (!rst_n) urgent_active |-> (grant != '0);
  endproperty
  assert property (p_urgent_reported) else $error("urgent with no grant");

Q. All three pass. What do they prove, and what does the prose claim?

They prove the grant's shape: how many bits, that the entry exists, that urgency produces a grant. None of them names legal_mask, so none can detect that the grant left the legal set. A property that never mentions the set cannot constrain membership in it.

This is the review question worth carrying out of the chapter:

Before accepting a property as evidence, name the signal whose corruption it would catch. If the contract's key signal does not appear in the property, the property is about something else.

What actually covers clause 2:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // Clause 2. The subset property -- and legal_mask APPEARS in it,
  // which is the whole difference from the three above.
  property p_grant_subset_of_legal;
    @(posedge clk) disable iff (!rst_n)
      (grant & ~legal_mask) == '0;
  endproperty
  assert property (p_grant_subset_of_legal)
    else $error("grant left the legal set");

  // Clause 1. The block must not be able to widen its own input. An
  // invariant rather than an implication: there is no cycle on which
  // a non-legal grant is acceptable, so there is no antecedent.
  property p_cand_is_subset;
    @(posedge clk) disable iff (!rst_n)
      (cand & ~legal_mask) == '0;
  endproperty
  assert property (p_cand_is_subset)
    else $error("candidate set is wider than the legal set");

  // The cover WITHOUT WHICH p_grant_subset_of_legal proves little:
  // the defect only fires when an entry is AGED AND ILLEGAL. An
  // environment whose entries always become legal quickly never
  // reaches it, and the assertion passes without exercising the bug.
  cover property (@(posedge clk) disable iff (!rst_n)
                  urgent_active && ((aged & ~legal_mask) != '0));

  // And the antecedent of clause 5's fairness property, published
  // separately -- a zero here means no entry ever aged out, which is
  // a different fact from the fairness bound holding.
  cover property (@(posedge clk) disable iff (!rst_n) urgent_active);

p_grant_subset_of_legal is one line and it is the property this block needed. It is also the one nobody writes, because the mental model that produced the bug also produces the property list30.4 §8 established that pattern and this is its second instance, which is why 30.9 treats it as a method rather than an anecdote.

Follow-up an interviewer should ask: is the cover reachable in a correct design? In the corrected block, no — aged is a subset of cand by construction, so aged & ~legal_mask is identically zero and the cover is dead. That is the right answer and the right behaviour: the cover's job is to prove the defective case was reachable during bring-up, and its going dead after the fix is the evidence the fix worked. A cover that must stay live and one that must go dead are different instruments, and naming which you are writing is the senior move.

12. What Would You Measure?

Q. The controller's bandwidth is below model. What does the scheduler need to report?

InstrumentWhat it distinguishesOwner
Cycles with a non-empty legal mask and no commitpolicy failing to choose, versus nothing being legal§3
Cycles with an empty legal mask, broken down by binding rulewhich timing rule is the limit — 30.3 §1013.3
urgent_active ratewhether the fairness override is firing constantly, disabling the preference layer17.4 §9
Grants that did not committhe grant-versus-commit collapse, and its cost17.1 §7
Age histogram, not maximum agea tail a maximum hides, and whether the threshold is set sanely17.2
Turnaround cycles as a share of bus cycleswhether direction batching is working — §712.4
The cover on aged and illegalwhether §9's bug class is even reachable here§11

Rows one and two together are the whole diagnosis, and they are only separable because legality and policy are separate boxes. A combined block reports "nothing issued" and nothing more — §3's debuggability argument, arriving as a measurement plan.

Row three is the one people omit and it inverts a conclusion. A high urgent_active rate means the fairness mechanism is firing so often that the locality preference is effectively off, so the controller is fair and slow rather than unfair and fast — and the fix is the threshold, not the preference.

13. Common Wrong Answers

“The arbiter checks the timing rules.” §2, §3. 17.4 §3's inverse error, which is worse — it issues illegal commands with a sensible-looking grant. Legality is decided before the arbiter sees anything.

“Give each request a priority score and pick the highest.” §5. A large enough weight outvotes legality. Legality is not a layer; it is the set the layers work inside.

“I'd use FR-FCFS with open-page policy.” §2. Both are real and both are stage 4. Naming a heuristic before drawing the box it lives in leaves every follow-up with nowhere to land — 23.4 and 23.5 own them.

“One request becomes one command.” §4. A conflicting read is a precharge, an activate and a column command, re-entering the queue between each — 17.1 §3.

“Compute the legal set, then arbitrate over it.” §4. Legality is not latched; a commit elsewhere can withdraw candidacy next cycle. A set computed once is a stale set.

“Refresh is just a high-priority request.” §6. 17.3 §2: if it is a candidate, cycles exist where something else wins, and rarer is not a bound. It is a phase change, not a priority.

“Writes just need their own queue.” §7. The queue is trivial; the shared bus with a direction is not, and batching to avoid turnaround creates a second, independent starvation problem.

“Round-robin prevents starvation.” §8. Round-robin over a set a preference layer has already narrowed is fair within that set and starves everything outside it. The override must sit above the preference.

“My scheduler cannot starve.” §8. A layered design starves by construction, and saying so before being told is the stronger opening. Then bound it.

“Raise the priority until it stops starving.” §8. A bigger number inside a preference layer does not make an entry reachable from above it.

“The grant is one-hot and always valid, so the arbiter is proven.” §11. Those are shape properties. Neither mentions legal_mask, so neither can see the grant leave the legal set.

“The fairness assertion passes, so entries are served.” §11. Check its cover first — if no entry ever aged out, the bound held vacuously.

“Timing state updates when a command is selected.” §2, §3. On the commit. A model updated on the grant diverges from the bus the first time a grant does not commit.

14. Self-Check

  1. Write the seven pipeline stages in order from memory, then state which single adjacency is the answer to the interview question and why.

  2. For each of the four stages in §3, give the one-sentence wrong answer that collapsing it into the next produces.

  3. A read has waited 200 cycles. Give the four distinct, mutually exclusive explanations a four-stage design can offer, and name the counter that identifies each.

  4. Explain why refresh cannot be modelled as a high-priority request, then list which of the seven stages change and which do not.

  5. Using §7's illustrative model, recompute the two slot-utilisation figures for a turnaround of 4 cycles instead of 8. State whether batching is still worth its starvation cost, and name the counter you would consult.

  6. Find the defect in §9 without reading §10. Then explain why it passes a one-hot check, a validity check and a fairness check.

  7. State the one-line property that catches it, and the general review rule §11 derives from the fact that nobody writes it.

  8. Say why §11's aged & ~legal_mask cover is supposed to go dead after the fix, and name a cover in this chapter that must stay live instead.

15. Where This Goes

A scheduler question is a structure question. Draw legality and policy as separate boxes before naming a single heuristic; hold request, candidate, grant and committed command apart, because each collapse has its own wrong answer; update timing state on the commit alone; and expect three perturbations — refresh, writes, and a starvation bound — each of which a correctly layered design absorbs by changing two stages.

Three results carry forward. A fairness fix placed above legality rather than above the preference issues illegal commands, and it is biased toward doing so. A property that does not name the contract's key signal is about something else — the second instance of 30.4 §8's pattern. And some covers must stay live while others must go dead, which is an instrument choice rather than an oversight.

Chapter 30.6 crosses the boundary this chapter stopped at. Everything so far assumed the interface works; training is the question of how it came to work at all, and it has its own central confusion. Training does not create margin — it finds where the margin already is, and a pass bitmap is not a physical eye. The question is usually walk me through write levelling, and the answer has to survive being asked what happens when it passes and the interface still fails.

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.