DDR · Module 9
Row Misses
Nothing is ever missing in DRAM — every row is always in the array. A miss means the bank holds nothing, which needs exactly one command, and how it came to be closed is information most models discard.
Chapter 9.3 fixed the taxonomy and developed the best case. This chapter takes the simpler of the two non-hit cases:
The bank holds nothing at all. What does that cost, and what does it tell you?
The first half has a short answer — one command, no ordering obligation — and that shortness is exactly what separates this case from Chapter 9.5's. The second half is where this chapter earns its place, because a closed bank is not merely an absence. It got that way somehow, and the four ways it can happen debug completely differently.
1. Nothing Is Missing
Start by discarding the word.
In a cache, a miss means the data is not here and must be fetched from somewhere that has it. Every part of that is false for DRAM:
The row is here. Every cell of every row holds its charge, in the array, at all times. Chapter 2.2 established what a cell is and Chapter 2.3 established what keeps it. A row that is not open is not somewhere else — it is exactly where it always was.
Nothing is fetched. An activate does not move a row from a slower place to a faster place. It resolves the row where it already is, by the mechanism Chapter 9.1 §2 traced: wordline, charge sharing, amplification, restore.
There is no level below. A cache miss has a next level to consult. DRAM is the level. There is nothing under it to miss to.
2. The Response Is One Command
This is the whole of the direct cost, and it is worth stating starkly because it is what makes a miss cheaper than a conflict.
bank is CLOSED, request wants row R
→ ACTIVATE bank, row R
That is the entire required row-state work. One command.
No ordering obligation, because there is nothing to close first.Compare Chapter 9.5's case, which needs a precharge and an activate, in that order, with the second unable to start until the first has completed. A miss has no such dependency: the bank is already in the state an activate requires.
Which yields a claim that survives every generation: a miss costs one row-state transition; a conflict costs two, serialised. No number is involved, and Chapter 9.6 builds the work model on exactly this.
And it explains why the distinction is worth maintaining. A report that merges misses and conflicts into a single "miss rate" cannot tell you whether the required work is N transitions or 2N — a factor-of-two difference in the most expensive thing the memory system does.
3. How a Bank Came to Be Closed
Here is what most models throw away. A bank in BANK_CLOSED arrived there by one of four routes, and a model that records only closed has lost the information that distinguishes them.
| Provenance | How | What it tells you |
|---|---|---|
| Never opened | no activate since reset | cold start, or a bank the workload never touches |
| Reset | reset asserted while it held a row | the model was cleared; the device may disagree |
| Explicit precharge | a PRE observed and completed | intentional, visible on the wire, attributable |
| Auto-precharge | a column command carried the flag | intentional and invisible — no PRE on the wire |
The last row is the one that matters. Chapter 7.4 §5 established that an auto-precharge closes a bank with no precharge command ever appearing on the interface. So a monitor watching commands sees a bank close for no observable reason, and an engineer reading a trace sees the same.
And "never opened" versus "explicitly closed" is a real diagnostic split. A bank that has never been opened during a long run is a mapping observation — Chapter 8.3's field position determines which banks traffic reaches, and a bank nobody touches usually means the map is not spreading traffic the way the designer believed. A bank that is repeatedly opened and closed is the opposite problem.
4. When Closed Is the Right Answer
A brief but necessary aside, because otherwise the module reads as though closed banks are always a failure.
A controller may close a bank on purpose, immediately after using it. If the next access to that bank is likely to want a different row, leaving the current one open converts a future miss into a future conflict — one transition into two. Closing early trades a certain small cost now against a possible larger cost later.
That choice is a page policy, and the two extremes have names: leave rows open and hope for hits, or close them immediately and accept that every access is a miss. Chapter 7.2 §3's auto-precharge flag is the mechanism that makes the second cheap — the close rides along with the column command rather than costing a separate one.
And that is as far as this module goes. Which policy suits which workload is a scheduling question owned by Module 17, informed by the mapping in Module 18 and measured in Module 23. What this module contributes is the reason the question exists: the classes have different transition costs, so the distribution of classes is worth managing.
5. RTL — Resolution and Provenance
The engineering problem
Turn a classified miss into the one action it requires, and independently record why each closed bank is closed — so that a diagnosis has information the classification alone destroys.
Why hardware needs it
The resolution half is on the request path of a real controller. The provenance half is instrumentation: cheap, and the difference between a five-minute diagnosis and a day of trace reading.
Classification
SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL. Level B and C only.
What it models
The single action a REQ_MISS requires, and a per-bank record of how each closed bank became closed.
What it does NOT model
Classification (Chapter 9.3 — consumed as an input). State (Chapter 9.1 — consumed as an input). The conflict response (Chapter 9.5). Timing (Modules 13, 14) — need_activate says an activate is required, never that it may be issued. Page policy and scheduling (Module 17). The auto-precharge mechanism itself (Chapter 7.4 §5) — its completion arrives as an input event.
Interface and parameter contract
// ─────────────────────────────────────────────────────────────────────────
// closed_bank_resolver
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// MODELS: the single action a classified miss requires, plus a per-bank
// record of HOW each closed bank became closed.
//
// MODELS NO PHYSICAL OR ANALOG BEHAVIOUR. No signal represents a charge,
// a bitline voltage, a sense amplifier or a transistor.
//
// NOT A SCHEDULER (Module 17). NOT A TIMING CHECK (Modules 13, 14):
// need_activate says an activate is REQUIRED, never that it may be issued.
//
// need_precharge is HARD-WIRED LOW. A closed-bank miss never needs a
// precharge first, and the output exists so that claim is structural
// rather than merely stated.
// ─────────────────────────────────────────────────────────────────────────
// How a bank came to hold nothing. Chapter 9.4 Section 3.
typedef enum logic [1:0] {
CLOSED_NEVER_OPENED = 2'd0, // no activate has completed since reset
CLOSED_BY_PRE = 2'd1, // an explicit precharge completed
CLOSED_BY_AUTOPRE = 2'd2, // an auto-precharge completed: NOTHING on
// the wire said so (Chapter 7.4 §5)
CLOSED_NOT = 2'd3 // the bank is not closed
} close_reason_e;
module closed_bank_resolver #(
parameter int NUM_BANKS = 4,
parameter int ROW_W = 16,
parameter int BA_W = (NUM_BANKS <= 1) ? 1 : $clog2(NUM_BANKS)
) (
input logic clk,
input logic rst_n,
// ── Request and its classification, from Chapter 9.3.
input logic req_valid,
input req_class_e req_class,
input logic [BA_W-1:0] req_bank,
input logic [ROW_W-1:0] req_row,
// ── Row context from Chapter 9.1. Read, never written.
input bank_row_state_e state_in [NUM_BANKS],
// ── Close-completion events, SPLIT BY KIND. One signal carrying both
// would destroy the distinction this block exists to record.
input logic pre_done,
input logic autopre_done,
input logic [BA_W-1:0] done_bank,
// An activate completed: the bank is no longer closed by anything.
input logic act_done,
input logic [BA_W-1:0] act_done_bank,
// ── The action a miss requires. Exactly one command.
output logic need_activate,
output logic [BA_W-1:0] act_bank,
output logic [ROW_W-1:0] act_row,
// ── Always low. See the header.
output logic need_precharge,
// ── Provenance.
output close_reason_e close_reason [NUM_BANKS],
output logic [NUM_BANKS-1:0] ever_opened
);
if (NUM_BANKS < 1) begin : g_nb
initial $fatal(1, "closed_bank_resolver: NUM_BANKS must be >= 1");
end
if (ROW_W < 1) begin : g_rw
initial $fatal(1, "closed_bank_resolver: ROW_W must be >= 1");
end
// ── Index legality, in the shape Chapter 9.1 Section 5 explains.
logic done_idx_bad, act_idx_bad;
if (NUM_BANKS >= (1 << BA_W)) begin : g_idx_full
assign done_idx_bad = 1'b0;
assign act_idx_bad = 1'b0;
end else begin : g_idx_partial
assign done_idx_bad = ({1'b0, done_bank} >= (BA_W+1)'(NUM_BANKS));
assign act_idx_bad = ({1'b0, act_done_bank} >= (BA_W+1)'(NUM_BANKS));
end
// ── Resolution. Combinational: a registered action would be an action
// for a state that may already have moved.
assign need_activate = req_valid && (req_class == REQ_MISS);
assign act_bank = req_bank;
assign act_row = req_row;
// THE STRUCTURAL CLAIM. There is no path to a 1 here.
assign need_precharge = 1'b0;
// ── Provenance. Recorded per bank, updated only on COMPLETED events --
// a requested close is not a close, for Chapter 9.1 Section 8's
// reason.
close_reason_e reason_q [NUM_BANKS];
always_ff @(posedge clk) begin
if (!rst_n) begin
for (int b = 0; b < NUM_BANKS; b++) begin
// Not CLOSED_BY_PRE. After reset no precharge has been observed,
// and claiming one would attribute a close that never happened.
reason_q[b] <= CLOSED_NEVER_OPENED;
ever_opened[b] <= 1'b0;
end
end else begin
if (act_done && !act_idx_bad) begin
reason_q[act_done_bank] <= CLOSED_NOT;
// Sticky for the life of the run: "has this bank ever been used"
// is the question Section 8 asks, and it is not the same as
// "is it open now".
ever_opened[act_done_bank] <= 1'b1;
end
if (pre_done && !done_idx_bad)
reason_q[done_bank] <= CLOSED_BY_PRE;
if (autopre_done && !done_idx_bad)
reason_q[done_bank] <= CLOSED_BY_AUTOPRE;
end
end
// ── Expose the record, but never let it contradict the state: a bank
// that is not closed reports CLOSED_NOT whatever the record holds.
// Two sources of truth about one fact is the hazard Chapter 9.2 was
// written about, and this is the cheap way to avoid creating one.
always_comb begin
for (int b = 0; b < NUM_BANKS; b++)
close_reason[b] = (state_in[b] == BANK_CLOSED) ? reason_q[b]
: CLOSED_NOT;
end
endmoduleState representation and transitions
One two-bit reason and one sticky bit per bank. The reason register moves on completed events only: an activate completion clears it to CLOSED_NOT, and either kind of close completion sets the corresponding cause.
ever_opened is sticky and is never cleared except by reset, because the question it answers — has this bank ever been used — is a property of the run, not of the moment.
Combinational behaviour
Three assignments for the action, an index check, and a per-bank reconciliation of the record against the live state. The reconciliation is the only non-obvious line: it exists so the block cannot report a stale provenance for a bank that has since reopened, which would be a second source of truth about the bank's condition.
Sequential behaviour and reset
Nonblocking only. The three update branches are not mutually exclusive by construction — a design could present pre_done and autopre_done for the same bank in one cycle — so the ordering matters and is stated: autopre_done is written last and therefore wins. That is a stated simplification, not a claim about devices; a real controller should never generate both, and §7's P4 makes the assumption checkable.
Reset sets every bank to CLOSED_NEVER_OPENED, not to CLOSED_BY_PRE. Claiming a precharge that was never observed would be inventing history, and it is the same discipline as Chapter 9.2's mon_known starting low.
Cycle-by-cycle example
NUM_BANKS = 4. A cold start followed by two different kinds of close:
| Cycle | Event | close_reason[1] | ever_opened[1] | need_activate |
|---|---|---|---|---|
| 0 | reset released | NEVER_OPENED | 0 | 0 |
| 1 | request b1 r0x40, classified MISS | NEVER_OPENED | 0 | 1 |
| 2 | act_done b1 | CLOSED_NOT | 1 | 0 |
| 3 | request b1 r0x40, classified HIT | CLOSED_NOT | 1 | 0 |
| 4 | pre_done b1 | CLOSED_BY_PRE | 1 | 0 |
| 5 | request b1 r0x41, classified MISS | CLOSED_BY_PRE | 1 | 1 |
| 6 | act_done b1, then autopre_done b1 | CLOSED_BY_AUTOPRE | 1 | 0 |
Cycles 1 and 5 are both misses and both need exactly one activate. The response is identical. The provenance differs — never-opened versus explicitly precharged — and that difference is invisible to the classifier and to the action.
Cycle 3 shows need_activate low on a hit, which is the point of gating on the class rather than on the state.
How to simulate, and expected output
Drive the table and check all four outputs. Then the cases that matter:
need_precharge must be low in every cycle of every test. It is a constant; the test is to confirm nothing downstream is relying on it ever being anything else.
A bank never touched must report CLOSED_NEVER_OPENED with ever_opened low for the whole run — this is §8's first discriminator and a regression should assert it for at least one bank deliberately.
An auto-precharge close must be distinguishable from an explicit one in the record, with no precharge command anywhere in the trace. This is the case a monitor built only on observed commands cannot reconstruct, and the reason the two completion inputs are separate.
Reset while a bank is open must give CLOSED_NEVER_OPENED and ever_opened low — the model is cleared, and §9's mechanism 2 is about the device not being.
NUM_BANKS = 3 with done_bank = 3 must mutate nothing.
Expected waveform
§6. Look for close_reason[1] moving through three distinct values while need_activate responds identically to both misses.
Synthesis implications
2 × NUM_BANKS reason bits plus NUM_BANKS sticky bits — at sixteen banks, 48 flops — and a handful of decoders. The provenance costs almost nothing and is the first thing removed from a design under area pressure, which is worth resisting: it is instrumentation whose value appears only during a failure, and its absence is discovered at the worst possible time.
Corner cases
NUM_BANKS == 1 works through the guard and selects the partial-index arm. Non-power-of-two counts are the only configuration in which the index checks fire. Simultaneous pre_done and autopre_done to one bank resolves to auto-precharge by assignment order, which is stated and asserted against. act_done and a close completion in the same cycle for the same bank would be a controller error — the close branches are written after, so the close wins, and P2 makes the situation visible rather than silent.
Failure modes and debugging clues
Every bank reporting CLOSED_BY_PRE immediately after reset means the reset value was chosen for convenience rather than truth. ever_opened all zero after a long run means either no completions are reaching the block or the address map is not distributing traffic — §8 separates them. close_reason disagreeing with state_in cannot happen by construction, and if it appears to, the two inputs are not from the same model.
Limitations
One request per cycle, one rank. It records provenance for the controller's own model — a monitor reconstructing provenance from the wire cannot see an auto-precharge at all, which is the asymmetry §8 is built on. No page policy and no scheduling. And it says nothing about timing.
6. A Cold Start, in Cycles
closed_bank_resolver — the response is identical, the history is not
10 cyclesCompare cycles 1 and 5. Both are classified MISS, both assert need_activate, and the required action is the same single command. The classifier and the resolver cannot tell them apart, and they should not — the response genuinely is identical.
Now compare close_reason[1] at those cycles: NEVER at cycle 1, BY_PRE at cycle 5. Same class, same action, different history — and if this bank were missing constantly, that difference is the first thing a diagnosis needs.
Cycle 8 is the one with no command behind it. The bank closed because a column command carried an auto-precharge flag, so nothing on the interface says a close occurred. A monitor reconstructing state from observed commands alone would still believe this bank is open, which is Chapter 9.2 §9's mechanism 5 appearing from the other side.
need_precharge is low at every cycle, as it is in every possible trace. The row is on the waveform so that its constancy is visible rather than merely claimed.
REPRESENTATIVE EDUCATIONAL STATE TRANSITIONS. No interval corresponds to a DDR timing parameter.
7. Four Assertions Worth Writing
// P1 -- a miss requires an activate, and ONLY a miss does. Both directions,
// because the reverse is what catches an action derived from the state
// instead of from the class: a bank can be CLOSED with no request at all.
property p_activate_iff_miss;
@(posedge clk) disable iff (!rst_n)
need_activate == (req_valid && (req_class == REQ_MISS));
endproperty
assert property (p_activate_iff_miss);
// P2 -- a closed-bank miss NEVER requires a precharge first. This is the
// chapter's architectural claim, and the reason need_precharge exists as
// an output at all. It is checked at the point a consumer reads it, so a
// refactor that introduced a path would fail here rather than silently
// emitting a precharge for a bank holding nothing.
property p_miss_never_precharges;
@(posedge clk) disable iff (!rst_n)
need_activate |-> !need_precharge;
endproperty
assert property (p_miss_never_precharges);
for (genvar b = 0; b < NUM_BANKS; b++) begin : g_prov_asrt
// P3 -- provenance never contradicts the live state. A bank that is not
// closed reports CLOSED_NOT, so no consumer can act on a stale reason
// for a bank that has since reopened.
property p_reason_agrees_with_state;
@(posedge clk) disable iff (!rst_n)
(state_in[b] != BANK_CLOSED) |-> (close_reason[b] == CLOSED_NOT);
endproperty
assert property (p_reason_agrees_with_state);
// P4 -- a bank is never recorded as closed by a cause that was not
// observed. Knowledge is earned from a COMPLETED event, exactly as in
// Chapter 9.2's monitor: this fails if the reason is set from a request
// rather than a completion, or invented at reset.
property p_reason_is_earned;
@(posedge clk) disable iff (!rst_n)
((close_reason[b] == CLOSED_BY_PRE) && ($past(close_reason[b]) != CLOSED_BY_PRE))
|-> $past(pre_done) && ($past(done_bank) == BA_W'(b));
endproperty
assert property (p_reason_is_earned);
endWhat these prove. P1 pins the action to the class rather than to the state, which is what stops an activate being emitted for a bank that is merely closed with nobody asking for it. P2 is the chapter's central claim, and writing it costs nothing while forbidding an entire failure mode: a precharge emitted for a bank holding nothing, which Chapter 9.1 would reject and a retrying controller could loop on. P3 prevents a second source of truth. P4 applies Chapter 9.2's earned-knowledge discipline to provenance.
What they do not prove. Nothing here says the activate may be issued — need_activate is a statement about required work, and timing legality is Modules 13 and 14'. Nothing says the provenance is complete: an auto-precharge that the controller performed but did not report to this block is invisible to it, and no property can detect a missing input. Nothing proves the classification was right — req_class is an input, so P1 proves the resolver responded correctly to the class it was given, which is Chapter 9.3's P1 to P4 to establish. And nothing proves anything physical: no property here concerns charge, sensing, restoration or bitline state.
8. DV — Closed Is Not One State
The verification value of this chapter is a single reframing: treat BANK_CLOSED as four states that happen to share a name.
Report the provenance breakdown alongside the miss count. A miss rate of 60% means something completely different depending on whether those banks were never opened, explicitly closed by a page policy, or closed by auto-precharge. The rate alone cannot distinguish a workload problem from a policy decision, and the two need opposite responses.
Track ever_opened as coverage. Banks that were never opened during a run are banks the test never exercised. A verification environment reporting a clean pass over a device where four of sixteen banks were never activated has verified twelve banks — and the four are usually determined by Chapter 8.3's field position, so they are the same four every run.
Expect a monitor's provenance to be incomplete, and say so. A controller knows it issued an auto-precharge. A monitor watching the interface does not, because nothing appeared. So a monitor's provenance has a permanent blind spot, and the honest handling is a fifth value — closed, cause unobserved — rather than defaulting to CLOSED_BY_PRE and attributing a command that never existed.
Do not reset provenance to a cause. Chapter 9.2 §5's argument again: after reset nothing has been observed, so the honest record is never opened, and a model that resets to closed by precharge has invented a command.
And cross-check the miss count against the activate count. Over a run, every miss should eventually produce exactly one activate, and every conflict two commands. A miss count that exceeds the activate count means misses are being classified and then not serviced — requests dropped, or retried and recounted, which Chapter 9.6 §5 shows corrupting the accounting invariant.
9. Debugging — Every Access Is a Miss
Symptom. A workload with real locality shows a miss rate near 100%. Almost every access finds its bank closed. Throughput is poor and roughly constant regardless of the access pattern.
Candidate mechanisms.
- A close-page policy is closing every row immediately, by design. Not a bug — every access is a miss because the controller chose it.
- Auto-precharge is being set on column commands when it should not be, so rows close after a single access without any policy intending it.
- The address map is spreading traffic so that consecutive accesses never revisit a bank before it is closed for other reasons — Chapter 8.3 §6's pathological stride, from the other end.
- Rows are being closed by something else entirely — refresh, or an all-bank precharge issued more often than intended.
- The controller's model has lost transitions, so it believes banks are closed when they are open, and issues activates that get rejected.
- The classifier is reading state that is not being updated at all, so every bank reads
BANK_CLOSEDforever.
Evidence to collect. The provenance breakdown across all closed banks — this is the evidence this chapter exists to make available. The ever_opened vector. The activate count against the miss count. Whether any column command in the trace carries an auto-precharge flag. And the per-bank distribution of misses.
Discriminator — the provenance breakdown does most of the work.
- Mostly
CLOSED_BY_AUTOPRE? Mechanism 1 or 2, and they are distinguished by intent rather than by evidence: check whether a close-page policy is configured. If it is, this is mechanism 1 and the conversation is about policy, not defects. If it is not, the auto-precharge flag is being set by something that should not be setting it — mechanism 2. - Mostly
CLOSED_BY_PRE? Mechanism 4. Explicit precharges are arriving from somewhere, and the count of them against the count the page policy should produce localises the extra source. - Mostly
CLOSED_NEVER_OPENED, withever_openedsparse? Mechanism 3 or 6. Theever_openedvector separates them: if most banks have never been opened and the ones that have are few and specific, the address map is concentrating traffic. If no bank has ever been opened while activates are being issued, the state is not being updated — mechanism 6. - Miss count far exceeding activate count? Mechanism 5. Activates are being emitted and rejected, so the misses recur and never resolve. Check Chapter 9.1's
cmd_rejectedandreject_reason. - Provenance mixed and none dominant? The miss rate is probably genuine and the locality assumption about the workload is wrong — measure the request stream's own locality independently, as Chapter 9.3 §9 describes, and compare.
Responsible layer. Mechanism 1 is a policy decision, not a fault — and recognising that first saves the most time, because the instinct is to debug. Mechanism 2 is the column-command path. Mechanism 3 is Module 8's address map. Mechanisms 4, 5 and 6 are the controller's model and command generation, level C. None is a physical fault.
Fix. Per mechanism, and in every case add the provenance breakdown to the standard report so the same diagnosis takes one glance next time.
10. Common Misconceptions
"A row miss means the data is missing."
Why it is tempting: the word is borrowed from caches, where it means exactly that.
Concrete failure: an engineer looks for the level below DRAM that a miss fetches from. There is none, and the search is a real waste of time on an unfamiliar system.
Correct model: every row is always present in the array. A miss means the bank holds no row right now. §1.
Prevention: substitute "the bank is not currently holding a row" and see whether the sentence still makes sense.
"A closed-bank access is a row conflict."
Why it is tempting: both are non-hits needing an activate, and "conflict" sounds like a general word for a problem.
Concrete failure: the response includes a precharge for a bank holding nothing. Chapter 9.1 rejects it, and a controller treating rejections as transient retries indefinitely.
Correct model: a conflict needs an open bank with a different row. A closed bank needs one activate and no ordering. §2, and P2.
Prevention: P2 makes it structurally impossible in this block.
"Any non-hit is exactly the same kind of miss."
Why it is tempting: one number is simpler than two, and both are failures to hit.
Concrete failure: a single 40% "miss rate" that could mean N transitions or 2N — a factor of two in the most expensive work the memory system does, invisible in the report.
Correct model: a miss costs one transition; a conflict costs two, serialised. §2.
Prevention: count them separately all the way to the report. Chapter 9.6.
"A closed bank means something went wrong."
Why it is tempting: the module frames hits as good, so closed looks like failure.
Concrete failure: an engineer debugs a close-page policy that is working exactly as configured, and possibly "fixes" it into a worse configuration for that workload.
Correct model: closing early trades a certain small cost now against a possible larger one later, and it is a legitimate policy. §4.
Prevention: check the provenance and the configured policy before assuming a defect. §9's first discriminator.
"A monitor can always tell why a bank closed."
Why it is tempting: precharges are commands, and commands are observable.
Concrete failure: a monitor attributes every close to a precharge. Auto-precharge closes leave no command on the wire, so the monitor's provenance is confidently wrong and its state model drifts.
Correct model: an auto-precharge close is invisible on the interface — Chapter 7.4 §5. A monitor needs a cause unobserved value.
Prevention: §8. Give the monitor a fifth value rather than a default.
"A bank that is never opened is harmless."
Why it is tempting: an unused resource costs nothing and breaks nothing.
Concrete failure: four of sixteen banks are never activated across an entire regression, so a quarter of the device's row logic is unverified — and because the address map determines which banks traffic reaches, it is the same quarter every run.
Correct model: ever_opened is coverage. An untouched bank is an untested bank.
Prevention: report the vector, and treat all-banks-touched as a coverage goal.
11. Interview Reasoning
"What is the difference between a closed-bank miss and a row conflict?"
The state the request meets, and therefore the work required. A miss finds the bank holding nothing, so the response is a single activate with no ordering obligation. A conflict finds the bank holding a different row, so the response is a precharge and then an activate, serialised — the second cannot begin until the first has completed. So a conflict costs two row-state transitions against a miss's one, and that is a claim that holds across every generation without a single timing number. It also has a practical consequence: a report that merges them cannot tell you whether the required work is N or 2N.
"Why is 'miss' a bad word for this?"
Because nothing is missing. Every row is always present in the array — a row that is not open is exactly where it always was, and an activate resolves it in place rather than fetching it. There is also no level below DRAM to miss to. The word is kept because the industry uses it, but the cache connotations it drags along — a backing store, a fetch, an eviction — are all false, and each has produced real debugging detours.
"A bank is closed. Does it matter how it got that way?"
Not for the response — the required action is the same single activate in every case. It matters enormously for the diagnosis. A bank that was never opened, one closed by an explicit precharge, and one closed by an auto-precharge that left nothing on the wire are four different situations that produce an identical classification. When a workload shows a near-100% miss rate, the provenance breakdown is what separates a deliberate close-page policy from an auto-precharge being set in error from an address map concentrating traffic — and those have completely different fixes.
"Why would a controller deliberately close a row it just used?"
Because leaving it open is a bet that the next access to that bank wants the same row. If the bet loses, the next access is a conflict costing two transitions instead of the miss's one. Closing early converts a possible future conflict into a certain future miss — a smaller cost, paid reliably. Which bet is right depends on the workload, and the mechanism that makes closing cheap is the auto-precharge flag riding along with a column command rather than costing a separate one.
"Your regression passes and four banks were never activated. Is that a problem?"
Yes, and it is worse than it sounds because it is not random. Which banks the traffic reaches is determined by the address map's field positions, so the same four banks go untested every run — the coverage hole is systematic rather than statistical. A clean pass over twelve of sixteen banks is a pass over twelve banks. The check is cheap: a sticky per-bank ever opened bit reported as coverage.
12. Engineering Exercise
NUM_BANKS = 4. All banks start closed after reset.
1. Trace close_reason and ever_opened for bank 2 through: req MISS b2 · act_done b2 · req HIT b2 · pre_done b2 · req MISS b2 · act_done b2 · autopre_done b2.
2. At which points is need_activate asserted, and is the required action ever different between them?
3. A run reports 10,000 misses and 6,200 activates. What does the gap imply?
4. A monitor reconstructing provenance from observed commands reports zero CLOSED_BY_AUTOPRE. Give two explanations and the check that separates them.
5. Write the property that catches a precharge being emitted in response to a closed-bank miss, and say what the bug would cause.
6. Reset is asserted while bank 2 is open. What does this block report afterwards, and in what sense is that record wrong?
13. Summary
Nothing is missing. Every row is always in the array; a row that is not open is where it always was. A "miss" means the bank is not currently holding a row, and an activate resolves that row in place rather than fetching it from anywhere.
The response is one command. An activate, with no ordering obligation, because there is nothing to close first. A conflict needs two, serialised — which is the entire difference between the classes and the reason a merged "miss rate" hides a factor of two.
A closed bank is four states sharing a name. Never opened, cleared by reset, closed by an explicit precharge, or closed by an auto-precharge that left nothing on the wire. The response is identical and the diagnosis is not.
An auto-precharge close is invisible to a monitor. The controller knows it happened; the interface does not say so. So a monitor's provenance has a permanent blind spot, and the honest handling is a cause unobserved value rather than a default that invents a command.
Closed is sometimes correct. Closing early converts a possible future conflict into a certain future miss — one transition instead of two. Whether that bet pays is a page-policy question owned by Module 17; that the question exists is this module's contribution.
And a bank that was never opened is a bank that was never tested. ever_opened is coverage, and because the address map decides which banks traffic reaches, an untouched bank is untouched the same way every run.
14. What Comes Next
Two of the three cases are settled: the bank holds the row you want, or it holds nothing. Chapter 9.5 — Row Conflicts takes the remaining one, and it is the hardest.
The bank is open, and it is holding the wrong row. The question that chapter opens with is the one every engineer asks first and that has a genuinely structural answer: why can the controller not simply activate the row it wants? The bank's sense amplifiers already represent a row; there is nowhere for a second one to go, so the current one has to be released before another can be resolved.
That produces a two-command response with a required order, which is the first time in the module that an action has a dependency inside it. And it produces the contention that Chapter 9.1's BANK_OPENING and BANK_CLOSING states were introduced for: a request arriving while that two-step transition is still in flight.
Return to Row Hits for the taxonomy and the classifier, Row Opening for the state and the transitions, The Row Buffer for why nothing is fetched, Precharge for auto-precharge as an invisible state change, Read for the flag that carries it, and Bank Address for why some banks never get touched.
Continue learning
Related tutorials
- Related topic
Read (RD / RDA)
READ names a column in a row that is already open — it opens nothing. And RDA is not a separate command but a READ carrying one address bit high, which is why a decoder must identify the operation before interpreting any operand.
- Related topic
Write Recovery (tWR)
The bus is free, the controller owes nothing, and the bank still cannot be closed. A precharge issued too early does not delay the write — it interferes with data still being driven into cells.
- Related topic
Timing Violations
A violation names its own evidence: two commands, one shared resource, a required separation and an actual one. Reconstructing those four facts is the whole investigation.
- Related topic
Calibration Failures
Five failures hide behind one done flag — never started, never converged, never committed, no longer valid, and valid but insufficient. One of them has no digital evidence at all.
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.
