Skip to content
VLSI Mentor

DDR · Module 3

Rows

A DRAM row is not an address range. It is the group of cells one shared selection conductor connects at the same instant — and that physical fact is where row granularity, controller-visible row state and state-dependent access cost all come from.

Module 2 finished the physics. A cell is one transistor and one capacitor with no drive and no gain; reading it shares its charge onto a much larger conductor and consumes what it held; sensing must amplify that small evidence; and restoration must drive the value back. Module 2 also kept pointing here, because every one of those mechanisms was described for one cell and a real device holds billions.

So Module 3 asks the organising question, and this chapter starts it:

How do you arrange enormous numbers of cells so that selection, sensing and restoration remain affordable — and what does the answer do to the way a system must access memory?

The answer, in one word, is sharing. And the first thing cells share is their selection.

This chapter is about what that produces: a row. Not a range of addresses, not a software abstraction, not a cache line — a physical group of cells that one conductor connects at the same instant. Everything downstream in this curriculum, up to and including why a memory controller is a scheduler, is a consequence of that group existing.

1. Why Per-Cell Access Is Not an Option

Start from the alternative, because the row is easier to accept once the alternative has been priced.

Suppose every cell could be selected independently. Each would need its own selection conductor reaching it, and something to drive that conductor, and a decoder output dedicated to it. Chapter 1.6 §2 established the governing arithmetic: anything provided per cell is paid at every bit. A device with billions of cells would pay billions of selection conductors, billions of decoder outputs, and the routing to deliver them.

That is not a cost problem to be optimised. It is the same structure as Chapter 1.2's register file — per-entry access paths — and it fails for the same reason: the wiring and the periphery, not the storage, come to dominate everything, and the technology loses the only property that justifies its existence.

So cells must share their selection. One conductor, many cells. And the moment that decision is made, a row exists — because the set of cells one conductor connects is now a meaningful object whether anyone wanted it to be or not.

2. What a Row Physically Is

Be concrete, because the word "row" invites a spreadsheet mental model that is actively wrong.

A selection conductor runs horizontally across the array. Along its length it passes the gate of one access transistor per cell. Each of those cells has its own storage capacitor, and each connects — through its own access transistor — to its own vertical conductor, shared with the cells above and below it in a different group.

One horizontal selection conductor passes the gate of one access transistor per cell. Four cells are shown along it, each with its own storage capacitor and each connecting through its own access transistor to its own separate vertical conductor. Asserting the selection conductor connects all four cells to their four vertical conductors at the same instant.Cell 0own capacitorCell 1own capacitorCell 2own capacitorCell 3own capacitorVertical 0shared downVertical 1shared downVertical 2shared downVertical 3shared downwhen onwhen onwhen onwhen on12
Figure 1 — one selection conductor, many cells: asserting it connects every cell along it to its own vertical conductor, simultaneously.

Read the figure for the asymmetry it shows. The cells across the top share one thing: the selection conductor that gates all four access transistors, which is why they are a row. They share nothing else — four separate capacitors, four separate access transistors, four separate vertical conductors. So the row is a group for exactly one purpose: being selected together.

Two clarifications that prevent most later confusion.

The vertical conductors are shared in the other direction. Each one runs down past the cells of other rows. So each cell sits at the intersection of two sharing schemes — one horizontal, one vertical — and belongs to a different group in each. Chapter 2.4 Fig 2 showed the vertical view (many cells, one conductor); this figure shows the horizontal one. Both are true at once, and the array is that intersection.

Nothing in the figure is a bit of storage except the capacitors. The conductors carry no data at rest. A row's contents live in its cells; a row's identity is the conductor that reaches them.

3. Why Many Bits Participate When Few Were Wanted

Here is the consequence that most surprises engineers arriving from software, and it needs stating plainly.

A requester asks for a small amount of data — a few bytes, perhaps a cache line's worth. To get it, the array must select the row containing it. Selecting that row asserts the shared conductor, which turns on every access transistor along it, which connects every cell in the row to its vertical conductor.

So every cell in the row participates. Every one of them shares its charge away (Chapter 2.5 §4). Every one of them must be sensed, and every one of them must be restored (Chapter 2.6 §2).

The array cannot do less. Not because it is inefficient, and not because the designers did not consider it — because one conductor cannot select a subset of the cells it physically touches. There is no "select half a row" operation to implement. Row granularity is a property of the wiring, not a policy.

Three consequences worth separating, because they are often blurred into one:

A read of a few bytes costs a whole row's worth of physical work. Sensing and restoration happen across the row regardless of how much of it anyone wanted.

That work is not wasted if more of the row gets used. Which is precisely why 3.2 exists and why locality has the value it does — but that is a consequence, not the reason the row exists.

And the work has already been done when the next request arrives. Which is where row state comes from, and the rest of this chapter.

4. From a Physical Event to Digital State

This is the chapter's pivot: the point where an arrangement of conductors becomes something a controller must track.

Selecting a row is not instantaneous and not free. The cells share their charge, the sensing circuitry resolves the values, and the resolved row is then held at full strength outside the array (Chapter 2.6 §3). That condition — this particular row has been selected and resolved, and its values are available — persists after the request that caused it has been answered.

So the array is in one of a small number of distinguishable conditions:

No row selected. The array is in its ready condition, and any row may be selected.

Row k selected and resolved. Row k's values are available. Further accesses to row k can use them. A different row cannot be selected until this one is released and the array returned to its ready condition.

In transition. Selection is under way, or release is under way. Neither serving row k nor ready for another row.

That is a state machine, and it lives in the controller, not in the array. Chapter 2.4 §1 established why: the array has no digital state, cannot evaluate legality, cannot refuse an illegal operation and cannot report one. The conductors and capacitors simply do what they are told. Every bit of knowledge about which row is selected exists only in the logic driving the array — and if that logic's belief diverges from reality, nothing will tell it.

So the row's existence as a physical group creates an obligation to track it. The rest of this chapter builds that tracker.

5. RTL — A Digital Row-State Model

The problem being solved. The array has no memory of which row is selected, but every legality decision depends on knowing. Something must hold that knowledge, update it only on permitted transitions, and expose it so the rest of the design — and its verification — can use it.

Abstraction level. Digital control. This is the DIGITAL CONSEQUENCE layer of the module, not the physical one.

What it models. Which row is currently selected, whether anything is selected at all, the transitions between those conditions, and refusal of control combinations that make no sense.

What it deliberately does NOT model. No cell, capacitor, conductor, charge or sensing — those are physics and belong to prose and diagrams (Chapter 2.1 §8). No DDR commands: the inputs here are called open_request and close_request rather than any protocol command name, because the commands have not been taught and naming them would imply a mapping that later modules own. No timing parameters. No column access (that is 3.2) and no sensing-completion distinction (that is 3.5, which refines this model). One row at a time, so no banks.

Interface. open_request with requested_row asks for a row to become the selected one. close_request asks for the selected row to be released. row_open and open_row_id report the state. busy says a transition is under way. err reports a request that the state does not permit.

How to simulate it. vlog row_state_tracker.sv tb_row_state_tracker.sv then vsim -c tb_row_state_tracker -do "run -all"; with VCS vcs -sverilog row_state_tracker.sv tb_row_state_tracker.sv && ./simv; with Xcelium xrun -sv row_state_tracker.sv tb_row_state_tracker.sv. Every later block in Module 3 simulates the same way.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// DIGITAL ROW-STATE MODEL.  Classification: EDUCATIONAL DIGITAL MODEL.
//
// Models the CONTROLLER-SIDE knowledge of which row is selected. It is NOT
// a cell, NOT an array, NOT a sense amplifier, and NOT a DDR command engine
// -- the inputs are deliberately named open_request/close_request rather
// than any protocol command, because the commands are a later module's.
//
// The cycle counts are PEDAGOGICAL, chosen to be legible in a waveform.
// No device timing is implied and none should be inferred.
// ─────────────────────────────────────────────────────────────────────────
module row_state_tracker #(
  parameter int ROWS = 8,
  // Representative transition costs. Each must be >= 1: the counted-wait
  // control flow has no meaning at zero.
  parameter int OPEN_CYCLES  = 3,
  parameter int CLOSE_CYCLES = 2,
  // DERIVED. The guard keeps ROWS == 1 legal instead of a zero-width index.
  parameter int ROW_W = (ROWS <= 1) ? 1 : $clog2(ROWS)
) (
  input  logic             clk,
  input  logic             rst_n,

  input  logic             open_request,
  input  logic [ROW_W-1:0] requested_row,
  input  logic             close_request,

  // The state this module exists to hold.
  output logic             row_open,
  output logic [ROW_W-1:0] open_row_id,
  output logic             busy,
  // One-cycle report of a request the current state does not permit. The
  // ARRAY could never produce this -- which is the point of the model.
  output logic             err
);

  typedef enum logic [1:0] {
    S_CLOSED,    // nothing selected; array ready for any row
    S_OPENING,   // a row is being selected
    S_OPEN,      // a row is selected and its values are available
    S_CLOSING    // the selected row is being released
  } state_e;

  state_e state, state_n;

  localparam int WAIT_MAX = (OPEN_CYCLES > CLOSE_CYCLES) ? OPEN_CYCLES
                                                         : CLOSE_CYCLES;
  localparam int WAIT_W   = (WAIT_MAX <= 1) ? 1 : $clog2(WAIT_MAX + 1);
  logic [WAIT_W-1:0] wait_cnt;

  // ── Request legality, one term per rule, written separately so a reader
  //    can point at the rule rather than decode a boolean. ───────────────
  logic legal_open, legal_close, both_requested;
  // A row may be selected only from the ready condition. Selecting a second
  // row while one is selected is the illegal case this refuses (§4).
  assign legal_open     = (state == S_CLOSED);
  // Releasing is meaningful only when something is selected.
  assign legal_close    = (state == S_OPEN);
  // Asking to open and close in the same cycle is not a race to resolve --
  // it is a requester bug, and silently picking one would hide it.
  assign both_requested = open_request && close_request;

  always_comb begin
    state_n = state;
    if (both_requested) begin
      state_n = state;          // no transition; err reports it below
    end else begin
      unique case (state)
        S_CLOSED:  if (open_request)  state_n = S_OPENING;
        S_OPEN:    if (close_request) state_n = S_CLOSING;
        S_OPENING: if (wait_cnt == 1) state_n = S_OPEN;
        S_CLOSING: if (wait_cnt == 1) state_n = S_CLOSED;
        default:                      state_n = S_CLOSED;
      endcase
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state       <= S_CLOSED;
      wait_cnt    <= '0;
      row_open    <= 1'b0;
      // Reset clears the CONTROLLER'S BELIEF and does nothing to the array.
      // A design coming out of reset must not assume a row is selected --
      // and, symmetrically, must not assume the array is in its ready
      // condition either. A real initialisation sequence establishes that.
      open_row_id <= '0;
      err         <= 1'b0;
    end else begin
      state <= state_n;
      err   <= 1'b0;

      // Load the wait counter on entry to a counted state, else count down.
      if (state_n != state) begin
        unique case (state_n)
          S_OPENING: wait_cnt <= WAIT_W'(OPEN_CYCLES);
          S_CLOSING: wait_cnt <= WAIT_W'(CLOSE_CYCLES);
          default:   wait_cnt <= '0;
        endcase
      end else if (wait_cnt != '0) begin
        wait_cnt <= wait_cnt - 1'b1;
      end

      // ── The two assignments that define the state, and their timing is
      //    the whole correctness question of this module. ───────────────
      if (state == S_OPENING && wait_cnt == 1) begin
        // Selection is COMPLETE. Only now is the row genuinely selected,
        // and only now may open_row_id be believed.
        row_open    <= 1'b1;
        open_row_id <= requested_row;
      end
      if (state == S_CLOSING && wait_cnt == 1) begin
        // Release is COMPLETE. Clearing this at the close REQUEST instead
        // would let the design believe the array was ready while it was
        // still finishing -- see Chapter 2.6 for what that destroys.
        row_open <= 1'b0;
      end

      // Illegal requests: reported, never silently repaired.
      //
      // The state exemptions matter. A requester is expected to HOLD its
      // request until the transition it asked for completes, so a request
      // still asserted during its own transition is not an error -- only a
      // request that the state could never have accepted is. Flagging the
      // held case would make correct requesters look broken.
      if (both_requested) begin
        err <= 1'b1;
      end else if (open_request && !legal_open && (state != S_OPENING)) begin
        err <= 1'b1;
      end else if (close_request && !legal_close && (state != S_CLOSING)) begin
        err <= 1'b1;
      end
    end
  end

  assign busy = (state == S_OPENING) || (state == S_CLOSING);

endmodule

Combinational decisions. Three legality terms and the next-state function. The terms are separate signals rather than inlined conditions so that each maps to one rule from §4, and so that verification can reference them by name.

Sequential updates. The state register, the wait counter, and the two assignments that define row_open and open_row_id. Those last two are where the module's correctness lives, and both are deliberately tied to completion rather than to request.

Cycle-by-cycle example. From reset: assert open_request with requested_row = 5. The machine enters S_OPENING and stays for OPEN_CYCLES; row_open is low throughout, because the row is not selected until selection finishes. On the cycle selection completes, row_open rises and open_row_id becomes 5. Assert close_request: the machine enters S_CLOSING for CLOSE_CYCLES with row_open still high, and only when release completes does it fall. Assert open_request while in S_OPEN and err rises for one cycle with no state change.

Simulation expectations. A testbench that opens row 5, checks open_row_id == 5, closes, and then opens row 2 should see row_open low across both transitions and err never asserted. A testbench that asserts open_request and close_request together should see err and no transition.

Synthesis implications. A four-state machine, a small counter, a row-index register and two flags — a few tens of flip-flops plus comparators, fully synthesizable. Nothing here describes how a DRAM device is built internally; a real device is not an RTL state machine, and this is the controller's model of it.

Corner cases. ROWS == 1 gives ROW_W == 1 through the guard rather than a zero-width index. Reset asserted mid-transition returns to S_CLOSED with row_open low, which is the safe direction. A request arriving in the same cycle a transition completes is evaluated against the current state, so it may be refused — deliberately, because a requester should not depend on the exact completion cycle.

Debugging observations. If open_row_id is wrong, check whether it is being captured at the request rather than at completion; a requested_row that changes during S_OPENING would then be captured late. If err never asserts in a test that expects it, the stimulus is probably always well-ordered — the illegal cases need directed tests. If the module hangs, verify both cycle parameters are at least 1.

Limitations. One row at a time. No column access, no sensing-completion distinction, no banks, no data path, no protocol commands, no real timing. Chapter 3.5 refines row_open into the more accurate row_ready, and 3.6 extends the whole thing into the module's synthesis model.

6. The Row Lifecycle, in Cycles

row_state_tracker — select row 5, then release it

10 cycles
Ten cycles of the row-state tracker. An open request for row five is accepted from the closed state and the machine spends three cycles opening, with row-open low throughout. Row-open rises and the open row identifier becomes five when selection completes. A close request then drives two cycles of closing during which row-open remains high, falling only when release completes.not usable yetnot usable yetselection beginsselection beginsselected — id now validselected — id now validrelease beginsrelease beginsready for another rowready for another rowclkopen_reqclose_reqstateOPNGOPNGOPNGOPENOPENCLSGCLSGCLSDCLSDCLSDrow_openopen_row_idXXX5555555busyt0t1t2t3t4t5t6t7t8t9
Figure 2 — a row becomes selected only when selection completes, and stays selected until release completes.

Cycles 0–2 — selecting, and not yet selected. The request was accepted and the machine is in S_OPENING, but row_open is low and open_row_id is not meaningful. This is the distinction the phase band marks: a request has been accepted and the row is not available. Chapter 2.5 explains what is physically happening in these cycles; the point here is that the digital state correctly refuses to claim the row yet.

Cycle 3 — selected. row_open rises and open_row_id becomes 5 in the same cycle. Neither is valid before this point, and asserting them earlier is the first bug §9's assertions catch.

Cycles 5–6 — releasing, and still selected. close_request has been accepted but row_open stays high. The row is not released until release completes, and believing otherwise is the destructive bug of Chapter 2.6 §6.

Cycle 7 — ready again. row_open falls and the array may be given a different row.

And the asymmetry to notice. open_row_id retains the value 5 after row_open falls. That is deliberate: the identifier is only meaningful while row_open is high, and leaving it unchanged rather than clearing it costs nothing and avoids a pointless write. §9's first assertion is what makes that safe — it pins the rule that the identifier may only be believed under row_open.

7. Row State Makes Access Cost Depend on History

The chapter can now state its architectural payoff, which is one of the most important conceptual milestones in the curriculum.

Given the state in §4, a new request for row r meets one of three situations:

Nothing is selected. Row r must be selected before anything can be read from it. The requester waits for selection.

Row r is already the selected row. The work of selecting it has already been done, and its values are already available. The request can be served without selecting anything.

A different row is selected. The current row must be released and the array returned to its ready condition before row r can be selected. The requester waits for a release and then a selection.

Which means: two requests that look identical at the interface can cost very different amounts, depending on what happened before them. Chapter 1.4 demonstrated exactly this at cycle level and called it the defining property of the tier. Here is where it comes from — not from a protocol decision, not from a scheduling policy, but from the fact that selection is shared and therefore has state.

Why this chapter stops here. Naming and classifying those three situations formally, and deciding what a controller should do about them, is 3.6 once the rest of the array is understood. Scheduling policy — whether to leave rows selected, how to order requests to exploit the cheap case — belongs to later modules entirely. What belongs here is the derivation: row state exists because selection is shared, and access cost depends on state because row state exists.

8. Verification Perspective

The module in §5 is small and its contract is almost entirely about when things are true.

Legal transitions, exhaustively. Closed → opening → open → closing → closed is the only cycle. Every other transition is illegal, and a state-coverage plan should confirm each legal one is taken and each illegal request refused.

The two assignment timings. row_open must rise exactly when selection completes and fall exactly when release completes. These are the module's two most consequential lines and deserve directed tests rather than reliance on random stimulus.

Identifier validity. open_row_id must be trustworthy whenever row_open is high. A test should include a requested_row that changes during S_OPENING, because a design capturing it at the request rather than at completion passes every test where it holds steady.

Illegal requests. Open while open. Close while closed. Open and close in the same cycle. Open during a transition. Each should produce err and no state change.

Reset behaviour. Reset during S_OPENING and during S_CLOSING. And the contract worth thinking hard about: reset clears the controller's belief and does nothing to the array, so a design must not assume after reset either that a row is selected or that the array is in its ready condition. A real initialisation sequence has to establish that, and a testbench that silently assumes it is testing a system that does not exist.

Coverage targets. Every state entered; every legal transition taken; each illegal combination refused; requested_row at its minimum and maximum; requested_row changing mid-transition; reset in each transition state.

9. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY, inside row_state_tracker.

// P1 -- the identifier is meaningful only under row_open. This is what makes
// it safe to leave open_row_id unchanged after a release (§6): consumers are
// contractually forbidden from reading it when no row is selected.
property p_id_meaningful_only_when_open;
  @(posedge clk) disable iff (!rst_n)
    !row_open |-> !$isunknown(open_row_id) || 1'b1;
endproperty
// NOTE: the above is deliberately trivial and is NOT asserted -- it is shown
// to make a point. "Meaningful only when open" is a statement about how
// CONSUMERS may use the signal, not a property of the signal itself, so it
// cannot be asserted here. It belongs in the consumer, or as a coverage
// exclusion. Writing an assertion that merely looks like it proves this is
// worse than admitting the limit. The three properties below are real.

// P2 -- row_open rises ONLY on completion of a selection. Catches the bug
// where the flag is set at the request, which would advertise a row that is
// not yet available.
property p_open_rises_only_on_completion;
  @(posedge clk) disable iff (!rst_n)
    $rose(row_open) |-> $past(state == S_OPENING && wait_cnt == 1);
endproperty
assert property (p_open_rises_only_on_completion);

// P3 -- row_open falls ONLY on completion of a release. The symmetric bug is
// the dangerous one: clearing it at the close request lets the design believe
// the array is ready while it is still finishing (Chapter 2.6).
property p_open_falls_only_on_completion;
  @(posedge clk) disable iff (!rst_n)
    $fell(row_open) |-> $past(state == S_CLOSING && wait_cnt == 1);
endproperty
assert property (p_open_falls_only_on_completion);

// P4 -- the identifier is STABLE while a row is selected. A row's identity
// cannot change without a release and a new selection, so any change to
// open_row_id while row_open stays high is a tracking bug -- and it is the
// one that makes a controller read the wrong row while believing it is right.
property p_id_stable_while_open;
  @(posedge clk) disable iff (!rst_n)
    (row_open && $past(row_open)) |-> $stable(open_row_id);
endproperty
assert property (p_id_stable_while_open);

What these prove. P2 and P3 pin the module's two most consequential assignments to completion rather than to request, in both directions. P4 is the tracking property: it fails the moment the design's belief about which row is selected can change without a release, which is the fault that produces correct-looking accesses to the wrong row.

What they do not prove — and P1 is included precisely to show the limit. "The identifier is meaningful only when a row is open" sounds like an assertion and is not one: it is a constraint on how consumers may use the signal, and nothing observable at this module's boundary distinguishes a consumer that obeys it from one that does not. Writing a property that appears to cover it would be worse than leaving it uncovered, so it is stated as a consumer obligation instead. More generally, none of these prove that the array actually selected the row — the array has no digital observables (Chapter 2.4 §5), so a controller's self-consistency is the strongest thing digital verification can establish here. Real controllers are additionally verified against device models that independently reject illegal sequences.

10. Common Misconceptions

"A row is just an address range." Wrong model: a row is a software-level grouping of consecutive addresses. Engineering action: the engineer reasons about rows as if their boundaries and sizes were a mapping choice, and assumes the device could group data differently if it were convenient. Resulting bug: incorrect performance models, and — worse — an inability to explain why row granularity is not negotiable. Address mapping does decide which addresses land in which row, but that is a later module's subject and it does not change what a row physically is. Correct model: a row is the set of cells one selection conductor connects. Its membership is fixed by the wiring. Prevention: ask which conductor reaches these cells? That question has a physical answer; "which addresses are in this row" has a mapping answer, and they are different questions.

"A row is a cache." Wrong model: the selected row is a small cache the device keeps for performance. Engineering action: treating it as an optional optimisation, or expecting cache-like properties such as several rows retained simultaneously per array, or replacement policy. Resulting bug: architecture that assumes behaviour the device does not provide, and confusion about why selecting a new row requires releasing the old one. Correct model: it is the unavoidable residue of sensing (Chapter 2.6 §3). Selecting a row necessarily resolves its values outside the array; keeping it selected keeps them available. Nobody added it and it could not have been omitted. Prevention: derive it. A structure that exists because it could not not exist has different properties from one that was designed in.

"Reading one location means only that location participated." Wrong model: the array reads what was asked for. Engineering action: cost models in which a small read is small work; stimulus that never considers what happened to the rest of the row. Resulting bug: performance predictions that are wrong in a direction the engineer cannot explain, and a mental model with no room for why sensing, restoration and row state exist. Correct model: one conductor connects every cell along it. The whole row participates, is sensed, and is restored, every time. Prevention: when reasoning about any DRAM access, ask what happened to the rest of the row. The answer is never "nothing".

"A row hit means zero latency." Wrong model: if the row is already selected, the access is free. Engineering action: performance models that treat same-row accesses as costless, and conclusions that a high hit rate means memory is not a factor. Resulting bug: optimistic predictions that real systems miss, and misattributed bottlenecks. Correct model: a request to the selected row skips the selection work — a large saving — but still has to select the wanted subset, move data, and traverse the path between requester and device. Cheap is not free. Prevention: say "skips selection" rather than "is free". 3.2 is what still has to happen.

"The controller can find out which row is selected." Wrong model: the array can be queried for its state. Engineering action: a design that relies on reading back array state after an error, a reset, or an unexpected condition. Resulting bug: the design's belief and the array's condition diverge with nothing to detect it, and accesses go to a row the controller does not think is selected. No error is reported, because the array cannot report. Correct model: the array has no digital state and no readback. The controller's model is the only model. After any event that could invalidate it — reset included — the array's condition must be established by an explicit sequence, not discovered. Prevention: treat the tracker as authoritative-by-construction and make every path that could desynchronise it re-establish the array explicitly.

11. Debugging — Data From the Wrong Row

Symptom. Reads return well-formed data that belongs to a different row than the one requested. It is reproducible for particular request sequences, no error is reported, and single isolated accesses are always correct.

"Well-formed but from the wrong place" is the signature of a tracking fault rather than a physical one, and the mechanisms below are distinguished by exactly when the tracker's belief went wrong.

Mechanism 1 — the identifier was captured at the request rather than at completion. Inspect: requested_row across the whole S_OPENING interval, and the cycle open_row_id changes. Expected evidence if true: failures only when requested_row changes during a selection — so they correlate with a back-to-back request pattern rather than with any particular address. Discriminator: hold requested_row steady through selection and the failures stop. P2's sibling check on the capture cycle catches it in simulation.

Mechanism 2 — row_open was asserted before selection completed. Inspect: the interval between the open request and row_open rising, against OPEN_CYCLES. Expected evidence: accesses issued earlier than the array can serve, returning unresolved values — which typically look like garbage rather than like another row's data. Discriminator: the character of the wrong data. Another row's plausible data points at mechanism 1 or 4; unresolved garbage points here. P2 fails immediately.

Mechanism 3 — a release was believed complete before it was. Inspect: whether row_open clears at the close request or at completion. Expected evidence: corruption of the row that was being released, plus wrong data from the newly selected row — two damaged things, which nothing else in this list produces. Discriminator: count what went wrong. P3 catches it.

Mechanism 4 — the tracker desynchronised from the array. Inspect: every path that can leave the state machine — error handling, an aborted sequence, a clock-domain event, and especially reset — and whether each re-establishes the array explicitly. Expected evidence: correct operation until some exceptional event, then persistent wrong-row behaviour until the next full initialisation. Discriminator: the failure starts at an identifiable event and does not recover on its own. This is §10's fifth misconception in practice, and it is the mechanism most often missed because the tracker looks internally consistent throughout.

Mechanism 5 — the row is right and the subset is wrong. Inspect: whether the returned data is from the correct row but the wrong position within it. Expected evidence: data that is plausible and adjacent to what was wanted. Discriminator: compare against the row's contents rather than against the expected value. If the row is right, this chapter is not the cause — 3.2 is.

Discrimination, in order of cost. First, is the row wrong or the position within it wrong? That separates mechanism 5 from the rest for free. Then, is the wrong data plausible or garbage? Garbage points at mechanism 2. Then, did the failure start at an identifiable event? Yes points at mechanism 4. Then, how many things are damaged? Two points at mechanism 3. What remains is mechanism 1, and holding requested_row steady confirms it.

The reasoning lesson. Every mechanism here is the tracker's belief being wrong, and the array cannot contradict it. So the investigation is entirely about reconstructing when the belief was formed relative to when the array actually changed — which is why a tracker that exposes its state, as §5's does, is dramatically more debuggable than one that keeps it private. Observability of control state is not a luxury when the medium cannot report faults.

12. Interview Reasoning

"Why does DRAM select many cells together instead of one cell at a time?" Because per-cell selection would need a selection conductor, a decoder output and the routing for every cell, and anything provided per cell is paid at every bit. At billions of cells the wiring and periphery would dominate and the technology would lose the density that justifies it. So cells share a selection conductor — and the set of cells one conductor reaches is a row. A strong answer notes that the row is a consequence of the sharing decision, not a grouping someone chose.

"A requester wants eight bytes. Why does a whole row participate?" Because one conductor cannot select a subset of the cells it physically touches. Asserting it turns on every access transistor along it, so every cell in the row shares its charge, must be sensed, and must be restored. There is no partial-row operation to implement — row granularity is a property of the wiring rather than a policy.

"Why does a DRAM controller have to track which row is selected, when a cache controller can just look at its tags?" Because the array has no digital state and no readback. Conductors and capacitors do what they are told; nothing in the array can evaluate legality, refuse an illegal operation, or report what condition it is in. So the controller's model is the only model, and if it diverges from reality nothing will say so. The practical consequence is that any event which could desynchronise the model — including reset — obliges the design to re-establish the array explicitly rather than discovering its condition.

"Two identical-looking requests take very different times. Why?" Because selection is shared and therefore has state. If the wanted row is already the selected one, the selection work is already done and its values are already available. If a different row is selected, the current one must be released and the array returned to its ready condition before the wanted row can be selected. The cost depends on history, which is why a memory controller is a scheduler rather than a translator.

"Is the selected row a cache?" It behaves somewhat like one and it was not designed as one. It is the unavoidable residue of sensing: resolving a row necessarily leaves its values at full strength outside the array, and keeping the row selected keeps them available. Calling it a cache invites expectations it does not meet — multiple retained rows per array, replacement policy — whereas deriving it from sensing predicts its actual properties, including why selecting a new row requires releasing the old one.

13. Engineering Check

A tracker like §5's currently reports row_open = 1 with open_row_id = 7. Three requests arrive in sequence: a read from row 7, then a read from row 7 again, then a read from row 2.

1. What happens on the first request? Row 7 is the selected row, so the selection work has already been done and its values are already available. The request is served without selecting anything — the cheap case of §7.

2. What happens on the second? The same. Nothing about serving the first request changed which row is selected, so the second is equally cheap. This is why the row's state rather than the request's content determines cost, and why repeated same-row access is the pattern every memory controller tries to create.

3. What must happen before the third can be served? Row 7 must be released and the array returned to its ready condition, and only then can row 2 be selected. Two transitions before any data moves. In §5's model that is S_OPEN → S_CLOSING → S_CLOSED → S_OPENING → S_OPEN.

4. What does the requester see, and what does it not see? It sees a much longer wait for the third request than for the first two. It does not see why — nothing in the interface reveals which row is selected. That asymmetry is exactly why performance counters classifying requests by row state exist, and why a requester cannot predict its own memory latency.

5. If the third request had been for row 7 as well, and the design had already released the row, what would that tell you? That the policy of releasing the row was wrong for this access pattern, not that anything was broken. The tracker would be behaving correctly and the decision to release early would have converted three cheap accesses into one cheap and two expensive ones. When to release a row is a policy question with real performance consequences — and it belongs to a later module, which is why this chapter builds only the state and refuses to schedule.

6. Which single signal would you add to make this measurable? A classification output on each served request saying whether it found the row already selected. Without it, a performance investigation has to infer row behaviour from timing, which is unreliable; with it, the hit and miss counts are a direct measurement. 3.6 builds exactly that classification once the rest of the array is in place.

14. Summary

Cells must share their selection, because a selection conductor and decoder output per cell would be paid at every bit and the wiring would overwhelm the storage. One conductor, many cells — and the set of cells one conductor reaches is a row.

So a row is not an address range, not a software grouping and not a cache. It is a physical group defined by shared selection, with fixed membership set by the conductor's reach. Cells in a row share exactly one thing — being selected together — and share nothing else: separate capacitors, separate access transistors, separate vertical conductors, each of which is shared in the other direction with cells of other rows.

Because one conductor cannot select a subset of the cells it touches, every cell in a row participates every time, whatever the requester asked for. All of them share their charge, all of them are sensed, all of them are restored. Row granularity is a property of the wiring, not a policy.

And because selecting a row is a real operation whose result persists, the array is left in a state: nothing selected, row k selected, or in transition. That state lives only in the controller, because the array has no digital state, no legality checking and no readback — so the controller's model is the only model, and any event that could desynchronise it obliges an explicit re-establishment rather than a discovery.

From which the chapter's architectural payoff follows: access cost depends on history. A request to the selected row skips the selection work; a request to a different row must wait for a release and then a selection. Two identical-looking requests, very different costs — not because of a protocol decision or a scheduling policy, but because selection is shared and therefore has state.

15. What Comes Next

This chapter established that selecting a row makes a whole row's worth of data available. It did not say how the requester gets the few bytes it actually wanted out of that.

Chapter 3.2 is that question, and it is a genuinely different one: selection along the other axis. It explains why row and column addresses exist as separate fields, why the two selections have such different costs and such different relationships to state, and why the width of what leaves the device is so much smaller than the width of what a row makes available. It also builds the address decomposition that the rest of Module 3's RTL depends on.

Return to Restore Operations for the mechanism that leaves a row's values available, The DRAM Cell (1T1C) for the structure underneath, or DRAM for the system-level consequence of state-dependent cost. The full path is on the DDR tutorials index.

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.