Skip to content
VLSI Mentor

DDR · Module 9

The Row Buffer

There is no buffer. There is a sense-amplifier structure holding one row and a controller holding metadata about it — and the cache analogy is useful for one sentence and misleading afterwards.

Chapter 9.1 established that after an activate completes, the bank holds one row. This chapter asks what that held row is.

The usual name for it is the row buffer, and the name is the problem. It suggests a buffer — a distinct piece of storage, placed next to the array, that rows are copied into. That picture is wrong in a way that matters, because engineers who hold it make specific, predictable mistakes: they expect a write-back on close, they expect a backing store to miss to, and they expect to be able to read the thing.

So the chapter's question is:

What is the row buffer, precisely — and which parts of the cache analogy have to be abandoned?

The second half is the useful half. The analogy is not useless; it is load-bearing for exactly one idea and destructive beyond it, and knowing where the line falls is what separates a working mental model from a plausible one.

1. There Is No Buffer

Start with the physical claim, stated as narrowly as it can be.

Chapter 2.5 §3 established it: after an activate, the row's values are resolved and being held in the sense amplifiers themselves. The sense amplifier is not a wire that reports a value and forgets it — it is a regenerative circuit that, once resolved, holds its resolved state. There is one such circuit per bitline pair, and a row spans all of them.

So "the row buffer" is the set of sense amplifiers, in their resolved state, considered as a whole.

That is the entire object. There is no additional array of storage. Nothing was copied anywhere — the values were amplified where they already were. The row is not "in a buffer"; the row's values are what the sensing circuitry currently holds.

And there is a second half to the object, which is why the term is ambiguous in practice. When a controller engineer says "the row buffer," they often mean something that lives in the controller: the knowledge of which row is open. Chapter 9.1 §8 established that this knowledge exists nowhere else. So the term spans two things in two different places, and §4 separates them.

2. Five Things It Is Not

Each of these is confused with the row buffer often enough to be worth naming, and each confusion produces a different failure.

Not thisWhy it gets confusedWhat the confusion breaks
A CPU cacheboth exploit locality; both have "hits"expects a backing store, a write-back on eviction, and tags
An SRAM buffer"buffer" implies dedicated storageexpects a copy operation and a capacity independent of the array
A controller read FIFOboth hold recently accessed dataconflates device state with controller buffering; they empty on different events
A write bufferwrites do pass through controller storageexpects the open row to absorb writes and flush later
The controller's row metadatathe term is used for bothexpects the device to be queryable, because the metadata is

The read FIFO row deserves a second look, because it is the subtlest. A controller genuinely does hold recently returned data, and that storage genuinely does serve repeated accesses faster. But it is a different object with a different lifetime: the open row survives until a precharge, and the FIFO drains as data is consumed. Confusing them produces a performance model in which closing a row appears to lose buffered data — it does not, because the two are unrelated.

3. Where the Analogy Helps — and Exactly Where It Breaks

The one idea the cache analogy delivers correctly: the expensive step has already been paid, so subsequent accesses to the same thing are cheaper.

That is genuinely the shape of it. An activate is costly; once a row is held, column accesses to it need no further activate. Locality pays. Chapter 9.3 develops it.

Now the breaks, in order of how much damage they do:

There is no backing store to miss to. A cache miss fetches from a lower level. A DRAM row that is not open is still entirely present in the array — every cell holds its charge. Nothing is fetched from anywhere; a row is resolved into the amplifiers. This is why Chapter 9.4 has to be careful with the word "miss": nothing is missing.

There is no write-back, because there is no dirty state to write back. A cache eviction may have to preserve modified data. A precharge preserves nothing because restoration already happened during the activateChapter 9.1 §4 and Chapter 7.4 §2. The cells are already correct before the precharge begins.

Capacity is exactly one row per bank, and it is structural. A cache has a capacity chosen by its designer, with associativity and a replacement policy. A bank holds one row because it has one sense amplifier per bitline. There is no replacement policy to tune — opening a different row necessarily displaces the current one.

It cannot be read. A cache's contents can be inspected, dumped, and walked. There is no command that returns the identity of the open row, and no signal that reports it. Chapter 9.1 §8 established this and §5 builds the component that copes with it.

And "hit" is a property of a bank, not of the device. A cache hit is a property of an address. A row hit requires the same bank and the same row — and row numbers repeat per bank, so row 100 of bank 0 and row 100 of bank 1 are unrelated locations. Chapter 9.3 §2 makes this precise.

4. What the Controller Actually Holds

Three distinct objects, in three places. Keeping them apart is the practical content of this chapter.

Four banks with independent row context. Bank zero holds row seventeen, bank one is closed and holds no row, bank two holds row ninety-one and bank three holds row four. Below them, the controller's row-context table holds one entry per bank mirroring this state, and the monitor's reconstructed model holds a third copy with per-bank known bits. The device itself reports none of this, so the two models can disagree with each other and with the device without any signal indicating it.Bank 0holds row 17Bank 1closed — noneBank 2holds row 91Bank 3holds row 4Controller tablewhat it believesMonitor modelwhat it observedNo query existsdevice reports nothingmirroredinferredcan disagree12
Figure 1 — Per-bank row context. Each bank independently holds one row or none, and the controller's table is a separate object that mirrors it.

Object one — the device's actual state. The sense amplifiers of each bank, resolved or not. Authoritative, and unobservable.

Object two — the controller's row-context table. One entry per bank: a state and a row. Chapter 9.1 §5's bank_row_fsm is this object. The controller acts on it, so when it is wrong the controller issues legal-looking commands against a state the device is not in.

Object three — a monitor's reconstructed model. Built by watching the command interface. Chapter 8.6 §4 needed it to invert an address; this chapter builds it properly.

Objects two and three are both models of object one, and neither can be checked against it. They can only be checked against each other — which is a weaker but genuinely useful thing, because they were built from different information. The controller knows what it intended; the monitor knows what appeared on the wire. When those disagree, something between intent and interface is broken, and that is a finding even without access to the device.

5. RTL — Two Models and Their Divergence

The engineering problem

Maintain an independent row-context model from observed commands, and compare it against the controller's model — while being honest about the banks for which the observation has not yet established anything.

Why hardware needs it

It does not; a verification environment needs it. This block exists because the authoritative state is unobservable, so the only available check is agreement between two independently derived models.

Classification

VERIFICATION-ONLY EDUCATIONAL MODEL.

What it models

A per-bank row-context model derived only from observed commands and completion events; per-bank knowledge of whether that model has been established; and classified divergence against a supplied controller model.

What it does NOT model

The device (nothing can). Command decode — commands arrive decoded, from Chapter 7.2's decoder. Timing (Modules 13, 14). Column commands and data. All-bank precharge (Chapter 7.4 §3 owns that, and its monitor already handles the broadcast case). And it does not adjudicate — it reports that two models disagree, never which one is right.

Interface and parameter contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// row_context_monitor
//
// Classification: VERIFICATION-ONLY EDUCATIONAL MODEL.
//   Not intended for synthesis. It consumes the controller's internal
//   state array, which is not a port on real silicon, and it drives
//   nothing.
//
// MODELS: an INDEPENDENT per-bank row-context model built only from
// OBSERVED commands, with per-bank knowledge bits, plus classified
// divergence against the controller's model.
//
// MODELS NO PHYSICAL OR ANALOG BEHAVIOUR. No signal represents a charge,
// a bitline voltage, a sense amplifier or a transistor.
//
// RELATION TO EXISTING BLOCKS: Chapter 9.1's bank_row_fsm is the
// CONTROLLER's model -- one copy, synthesisable. Chapter 7.4's
// cmd_monitor_model monitors PRECHARGE SCOPE with known bits. This is the
// second, independent row-context model neither provides, and the only
// component in the module that can compare two models.
//
// IT DOES NOT ADJUDICATE. It reports disagreement, never which model is
// correct -- because nothing here can observe the device.
// ─────────────────────────────────────────────────────────────────────────
module row_context_monitor #(
  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,

  // ── OBSERVED command stream, decoded and qualified (Chapter 7.2).
  input  logic                  obs_act,
  input  logic                  obs_pre,
  input  logic [BA_W-1:0]       obs_bank,
  input  logic [ROW_W-1:0]      obs_row,
  // Abstract completion events, as in Chapter 9.1. NOT pins.
  input  logic                  obs_act_done,
  input  logic                  obs_pre_done,
  input  logic [BA_W-1:0]       obs_done_bank,

  // ── The controller's model, sampled from the DUT. NOT recomputed here:
  //    a recomputed copy would share this block's bugs and agree with it
  //    for the wrong reason.
  input  bank_row_state_e       ctrl_state [NUM_BANKS],
  input  logic [ROW_W-1:0]      ctrl_row   [NUM_BANKS],
  input  logic [NUM_BANKS-1:0]  ctrl_row_valid,

  // ── This monitor's own model.
  output bank_row_state_e       mon_state  [NUM_BANKS],
  output logic [ROW_W-1:0]      mon_row    [NUM_BANKS],
  // THE HONEST BIT. Low until a full transition has been observed for this
  // bank. After reset the monitor knows NOTHING -- unlike the controller,
  // which legitimately knows it has issued nothing. Comparing against an
  // unknown is not a divergence, and reporting one would train an engineer
  // to ignore this output.
  output logic [NUM_BANKS-1:0]  mon_known,

  // ── Divergence, only over banks the monitor actually knows.
  output logic                  diverged,
  output logic [NUM_BANKS-1:0]  diverge_mask,
  output logic [BA_W-1:0]       diverge_bank,
  // 1 state differs, 2 state agrees but row differs,
  // 3 controller claims a valid row and the monitor does not,
  // 4 monitor claims a valid row and the controller does not.
  output logic [2:0]            diverge_kind
);

  if (NUM_BANKS < 1) begin : g_nb
    initial $fatal(1, "row_context_monitor: NUM_BANKS must be >= 1");
  end
  if (ROW_W < 1) begin : g_rw
    initial $fatal(1, "row_context_monitor: ROW_W must be >= 1");
  end

  // ── Index legality. Same shape as Chapter 9.1 Section 5, and for the
  //    same reason: a cast to BA_W would truncate for a power-of-two
  //    NUM_BANKS and make the comparison permanently false.
  logic obs_idx_bad, done_idx_bad;
  if (NUM_BANKS >= (1 << BA_W)) begin : g_idx_full
    assign obs_idx_bad  = 1'b0;
    assign done_idx_bad = 1'b0;
  end else begin : g_idx_partial
    assign obs_idx_bad  = ({1'b0, obs_bank}      >= (BA_W+1)'(NUM_BANKS));
    assign done_idx_bad = ({1'b0, obs_done_bank} >= (BA_W+1)'(NUM_BANKS));
  end

  // ── The monitor's model. Same transition graph as Chapter 9.1, applied
  //    to OBSERVED events. Deliberately identical in shape so that a
  //    divergence means the inputs differed, not the rules.
  always_ff @(posedge clk) begin
    if (!rst_n) begin
      for (int b = 0; b < NUM_BANKS; b++) begin
        mon_state[b] <= BANK_CLOSED;
        mon_row[b]   <= '0;
        // NOT known. This is the line that distinguishes a monitor from a
        // controller, and getting it wrong produces confident nonsense
        // for every access until the first observed transition.
        mon_known[b] <= 1'b0;
      end
    end else begin
      if (obs_act && !obs_idx_bad && (mon_state[obs_bank] == BANK_CLOSED)) begin
        mon_state[obs_bank] <= BANK_OPENING;
        mon_row[obs_bank]   <= obs_row;
      end
      if (obs_pre && !obs_idx_bad && (mon_state[obs_bank] == BANK_OPEN)) begin
        mon_state[obs_bank] <= BANK_CLOSING;
      end
      if (obs_act_done && !done_idx_bad
          && (mon_state[obs_done_bank] == BANK_OPENING)) begin
        mon_state[obs_done_bank] <= BANK_OPEN;
        // Knowledge is established by a COMPLETED transition, not by a
        // command. An observed activate that never completes tells the
        // monitor a row was requested, not that one is held.
        mon_known[obs_done_bank] <= 1'b1;
      end
      if (obs_pre_done && !done_idx_bad
          && (mon_state[obs_done_bank] == BANK_CLOSING)) begin
        mon_state[obs_done_bank] <= BANK_CLOSED;
        mon_known[obs_done_bank] <= 1'b1;
      end
    end
  end

  // ── Comparison, over known banks only.
  logic [NUM_BANKS-1:0] mon_valid;
  always_comb begin
    for (int b = 0; b < NUM_BANKS; b++)
      mon_valid[b] = (mon_state[b] == BANK_OPEN);
  end

  always_comb begin
    diverge_mask = '0;
    for (int b = 0; b < NUM_BANKS; b++) begin
      if (mon_known[b]) begin
        if (mon_state[b] != ctrl_state[b])
          diverge_mask[b] = 1'b1;
        else if (mon_valid[b] && (mon_row[b] != ctrl_row[b]))
          diverge_mask[b] = 1'b1;
        else if (ctrl_row_valid[b] != mon_valid[b])
          diverge_mask[b] = 1'b1;
      end
    end
  end

  assign diverged = |diverge_mask;

  // ── Report the lowest-numbered diverging bank and classify it. One at a
  //    time is a stated simplification: diverge_mask carries the full set,
  //    and a real environment would log every bit of it.
  always_comb begin
    diverge_bank = '0;
    diverge_kind = 3'd0;
    for (int b = NUM_BANKS - 1; b >= 0; b--) begin
      if (diverge_mask[b]) begin
        diverge_bank = BA_W'(b);
        if (mon_state[b] != ctrl_state[b])            diverge_kind = 3'd1;
        else if (mon_valid[b] && (mon_row[b] != ctrl_row[b])) diverge_kind = 3'd2;
        else if (ctrl_row_valid[b] && !mon_valid[b])  diverge_kind = 3'd3;
        else                                          diverge_kind = 3'd4;
      end
    end
  end

endmodule

State representation and transitions

The same four-state graph as Chapter 9.1, plus one bit per bank. The identical graph is deliberate: if the two models implemented different rules, a divergence would be ambiguous between "the inputs differed" and "the rules differed." Same rules, different inputs, so a divergence means the inputs differed — which is the finding.

The mon_known bit only ever goes from 0 to 1, on an observed completed transition. It is never cleared except by reset, because once the monitor has seen a bank through a full transition it has a basis for its model from then on.

Combinational behaviour

Per-bank comparison with a priority chain producing the lowest diverging bank and its classification. The descending loop means bank 0 wins ties, which is arbitrary and stated.

Sequential behaviour

Nonblocking throughout. The four acceptance and completion branches are mutually exclusive per bank for the same reason as Chapter 9.1 §5: each requires a different current state.

Reset behaviour

mon_state closed, mon_row zero, and mon_known LOW for every bank. This is the single most important line in the block.

A controller may reset its model to closed and be right, because it knows it has issued no commands. A monitor may not, because a monitor may have been attached to a system that was already running, or the device may have been left in some state by whatever ran before. So the monitor's honest position after reset is I do not know, and comparing against that is not a divergence.

Get this wrong and the block is worse than useless: it reports divergence for every bank until the first transition, an engineer learns to ignore diverged, and the one real divergence later is ignored with it.

Cycle-by-cycle example

NUM_BANKS = 4. The monitor attaches with everything unknown, then observes bank 1 opening row 0x0104:

CycleObservedmon_state[1]mon_known[1]diverged
0CLOSED00 — nothing known
1ACT b1 r0104OPENING00
2act_done b1OPEN10
3OPEN10 — both models agree
4controller's table corrupted to row 0x0105OPEN11, kind 2

Cycle 0 is the lesson. The models disagree about nothing yet because the monitor claims nothing. Cycle 2 is where the monitor earns the right to an opinion, and cycle 4 is where the opinion pays.

How to simulate, and expected output

Drive an observed command stream, sample the controller's model, and check diverged. Then the cases worth directing:

Attach the monitor mid-stream, with banks already open. Every mon_known must be low and diverged must stay low until transitions are observed. A monitor that reports divergence here is broken, and this is the test that catches it.

Corrupt one field of the controller's model at a time and confirm the diverge_kind classification: a wrong state gives kind 1, a wrong row with the right state gives kind 2, and a validity mismatch gives 3 or 4 depending on direction. Kinds 3 and 4 are different bugs — the controller believing a row is open when the monitor saw it closed is a stale controller; the reverse is a controller that lost a transition it made.

Drive an observed activate that never completes. mon_known must stay low for that bank: a requested row is not a held row.

Invalid bank index with NUM_BANKS = 3 must mutate nothing.

Expected waveform

§6. Look for mon_known rising one cycle after the first observed completion, and diverged staying low before that regardless of what the controller's model says.

Synthesis implications

None claimed — it is verification-only. It would elaborate; synthesising it would be meaningless because ctrl_state has no hardware source and nothing consumes its outputs. Declaring it non-synthesisable is a classification, not a limitation, and it is the same distinction Chapter 7.4 drew.

Corner cases

NUM_BANKS == 1 works through the $clog2 guard and selects the partial-index arm, so index 1 is reported. Non-power-of-two bank counts are the only configuration where the index check can fire. Both models simultaneously wrong in the same way produces no divergence — a real limitation, and it is why the monitor's rules must be derived independently rather than copied from the controller's implementation. A bank that is never accessed stays unknown forever, which is correct and means mon_known should be reported as coverage rather than assumed complete.

Failure modes and debugging clues

diverged asserting at time zero for every bank means mon_known is not being reset low. diverged never asserting in a system with a known injected bug means either mon_known never rose — check that completion events reach the monitor — or the monitor is being fed the controller's model as its own input, which makes agreement automatic and is the most embarrassing way to get a clean regression.

Limitations

One divergence classified per cycle. No all-bank precharge. It cannot say which model is right, only that they differ. And both models can be wrong together, which no amount of comparison detects — the only cure for that is a real device or a trusted reference.

6. Persistence, in Cycles

row_context_monitor — the row persists, and the monitor learns

10 cycles
Ten cycles. An observed activate for bank one row 0x0104 is followed by a completion event, after which the monitor's state is open and its known bit asserts. Three column accesses then occur at different columns, and neither the monitor's state nor its row changes across any of them, because a column access does not alter row context. The divergence output stays low throughout. At the final cycle the controller's model is shown holding a different row, and divergence asserts with kind two, meaning the states agree but the rows do not.one row context, 4 accessesone row context, 4 accessesmodels disagreemodels disagreerow context establishedrow context establishedmonitor now knowsmonitor now knowscontroller's row changescontroller's row changesdivergence, kind 2divergence, kind 2CKobs commandACT----RDRD--WR--RD--obs_act_donemon_state[1]CLOSEDOPENINGOPENOPENOPENOPENOPENOPENOPENOPENmon_row[1]0000010401040104010401040104010401040104mon_known[1]ctrl_row[1]0000010401040104010401040104010401050105divergedt0t1t2t3t4t5t6t7t8t9
Figure 2 — One activate, several column accesses, one row context. REPRESENTATIVE EDUCATIONAL STATE TRANSITIONS; spacing implies no timing.

Cycles 2 to 7 are the chapter's positive claim. Four column accesses — two reads, a write, a read — and mon_state[1] and mon_row[1] do not move. A column access selects from what the amplifiers hold; it does not change what they hold. That is what "the row persists" means, and it is the entire mechanism Chapter 9.3 is about.

Cycles 0 to 1 are the honesty. mon_known[1] is low, so diverged is low regardless of what the controller's table says. The monitor has no basis for an opinion and does not offer one.

Cycle 9 is the payoff. The controller's row changed to 0x0105 with no observed activate to justify it. The states still agree — both say OPEN — so this is kind 2: same state, different row. That is the most dangerous divergence there is, because every column command the controller now issues will be well-formed, legal, and aimed at the wrong row.

REPRESENTATIVE EDUCATIONAL STATE TRANSITIONS. Spacing between commands corresponds to no DDR timing parameter.

7. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for (genvar b = 0; b < NUM_BANKS; b++) begin : g_mon_asrt

  // P1 -- the monitor never claims knowledge it has not earned. A bank
  // becomes known only through an OBSERVED COMPLETED transition, so
  // knowledge cannot appear from a command alone or from reset.
  property p_knowledge_is_earned;
    @(posedge clk) disable iff (!rst_n)
      (!$past(mon_known[b]) && mon_known[b])
        |-> ($past(obs_act_done) || $past(obs_pre_done))
            && ($past(obs_done_bank) == BA_W'(b));
  endproperty
  assert property (p_knowledge_is_earned);

  // P2 -- no divergence is reported for a bank the monitor does not know.
  // This is what keeps the diverged output trustworthy, and it is the
  // property that fails if mon_known is reset high.
  property p_no_divergence_without_knowledge;
    @(posedge clk) disable iff (!rst_n)
      !mon_known[b] |-> !diverge_mask[b];
  endproperty
  assert property (p_no_divergence_without_knowledge);

  // P3 -- a column access changes no row context. The claim Section 6
  // makes visually, stated over the monitor's model: only activate and
  // precharge events may move it.
  property p_row_context_survives_column_access;
    @(posedge clk) disable iff (!rst_n)
      (!obs_act && !obs_pre && !obs_act_done && !obs_pre_done)
        |=> (mon_state[b] == $past(mon_state[b]))
            && (mon_row[b] == $past(mon_row[b]));
  endproperty
  assert property (p_row_context_survives_column_access);

  // P4 -- agreement on a known bank means agreement on BOTH fields. The
  // property that stops a partial comparison passing: a monitor that
  // compared only state would miss the Section 6 cycle-9 case, which is
  // the most dangerous divergence in the chapter.
  property p_agreement_covers_state_and_row;
    @(posedge clk) disable iff (!rst_n)
      (mon_known[b] && !diverge_mask[b])
        |-> (mon_state[b] == ctrl_state[b])
            && ((mon_state[b] != BANK_OPEN) || (mon_row[b] == ctrl_row[b]));
  endproperty
  assert property (p_agreement_covers_state_and_row);

end

What these prove. P1 and P2 together make diverged trustworthy, which is the precondition for anyone acting on it. P3 is the chapter's architectural claim as a contract — that column traffic does not disturb row context — and it would fail immediately if a monitor were written to update row state on a column command, which is a real and tempting error given that column commands carry a bank. P4 forbids a partial comparison, closing the gap through which the kind 2 divergence would otherwise escape.

What they do not prove. Nothing here proves either model matches the device, because nothing here can observe the device — that is the block's defining limitation, stated in its header and unfixable by assertion. Nothing proves the models are not both wrong in the same way, which is why the monitor's rules must be derived from the specification rather than copied from the controller's code. Nothing proves anything physical: no property here concerns charge, sensing, restoration or bitline state, and the block contains no representation of any of them. And nothing proves timing legality — an observed completion event is taken on trust, and whether it arrived at a legal moment is Modules 13 and 14'.

8. DV — Reconstructing Row Context From Commands

Chapter 8.6 §4 needed a tracked open row to invert an address and took it as an input. This is where it comes from, and the requirements are worth stating as a checklist because each one is a real failure mode.

Index by {rank, bank}, not by bank. Chapter 8.5 §4 established this: each rank's banks hold their own rows. A per-bank model works flawlessly during single-rank bring-up and fails when the second rank is populated.

Apply transitions on modelled completion, not on the command. Chapter 9.1 §8's rule. A model that opens at the activate is open during an interval the device is not, and the too-early column access looks legal.

Start unknown, per bank. The mon_known bit. A monitor that starts closed is asserting something it cannot know, and every comparison before the first transition is noise.

Never update row context on a column command. P3. Column commands carry a bank, which makes the error easy to write and hard to see — the model stays plausible and drifts only when a column command carries operands the model misreads.

Compare state and row, not state alone. P4, and §6's cycle 9. Agreement on state with disagreement on row is the divergence that produces wrong data with a clean trace.

Log mon_known as coverage. A regression in which half the banks were never accessed has a monitor that knows half the device, and a clean diverged output that means much less than it appears to. Unknown is not the same as agreeing.

And derive the monitor's rules independently. The one failure comparison cannot catch is both models being wrong identically. Writing the monitor from the specification rather than from the controller's source is the only defence, and it is a process discipline rather than a code one.

9. Debugging — Two Models, One Device

Symptom. A scoreboard reports data corruption. The command trace is legal and well-formed. diverged asserted some time earlier, on one bank, with kind 2.

Candidate mechanisms.

  1. The controller lost a transition it made — it issued a precharge and an activate, and its table recorded only one of them, so its row is stale.
  2. The controller's table was written out of band — a row updated without an accepted activate, which Chapter 9.1's P3 catches at the source.
  3. The monitor missed a transition — a command it failed to decode, or a completion event that never reached it. The monitor is wrong and the controller is right.
  4. The monitor is applying transitions on the command rather than on completion, so it is transiently wrong during every interval and the divergence is a false positive that happens to coincide.
  5. An auto-precharge closed a row with no precharge command on the wire — Chapter 7.4 §5's invisible state change — and only one of the two models modelled it.

Evidence to collect. The cycle diverged first asserted and the diverge_kind at that cycle — first, not the one you noticed, because divergence persists and the later evidence is contaminated. Both models' full (state, row) for the diverging bank. The complete activate and precharge history for that bank since the last agreement. And whether any column command in the window carried an auto-precharge flag.

Discriminator.

  • Count activates and precharges for that bank in both models' input paths. Unequal counts identify which model dropped an event, and that alone separates mechanism 1 or 2 from mechanism 3.
  • Check whether the divergence appeared during a transition and cleared afterwards. A divergence that resolves by itself is mechanism 4 — the monitor is transiently wrong by construction — and the fix is in the monitor, not the DUT.
  • Check diverge_kind at first assertion. Kind 1 (state differs) usually means a dropped transition. Kind 2 (state agrees, row differs) almost always means an out-of-band write to one model's row field, because a dropped transition normally moves the state too.
  • Look for an auto-precharge flag on a column command in the window. Mechanism 5 has a clean signature: the divergence begins immediately after a column command rather than after an ACT or PRE, and that is diagnostic because nothing else does.
  • If both models agree and corruption persists, row context is exonerated. The fault is in the address map (Chapter 8.6), the data path, or timing — and this is the outcome to hope for, because it narrows the search by an entire layer.

Responsible layer. Mechanisms 1, 2 and 5 are the controller's model — level C. Mechanisms 3 and 4 are the verification environment, not the design, and mechanism 4 in particular will consume a day if the monitor's transition rule was never reviewed. None is a physical fault, and the legal trace is the evidence for that.

Fix. For 1 and 2, Chapter 9.1's P2 and P3 at the source. For 3, the decode gap. For 4, move the monitor's transition to completion and add P1. For 5, model auto-precharge in both, using Chapter 7.4 §4's scheduled-close approach.

10. Common Misconceptions

"A row buffer is just SRAM."

Why it is tempting: the word "buffer," and the fact that it holds data and is fast.

Concrete failure: a model with a separate storage array that rows are copied into. It cannot explain why closing a row moves no data, and it invites a capacity or associativity parameter that has no meaning.

Correct model: the sense amplifiers themselves, in their resolved state. Nothing is copied anywhere. §1.

Prevention: ask what state the sense amplifiers are in, never where the buffer is.

"Every bank shares one row buffer."

Why it is tempting: "the row buffer" is singular in most prose, including this chapter's title.

Concrete failure: a model with one open row for the whole device. It cannot represent the parallelism Chapter 5.2 exists to explain, and it will mis-classify every access to a bank other than the last one touched.

Correct model: one per bank, independent. Figure 1. Chapter 5.3 adds the nuance that banks in a group share a column data path while still holding independent rows — sharing the path is not sharing the row.

Prevention: index everything by bank, and by rank above that.

"A row hit means the same row number."

Why it is tempting: the row number is the obvious thing to compare, and it is half the answer.

Concrete failure: a classifier that compares rows and ignores banks reports hits for row 100 of bank 0 against row 100 of bank 1 — unrelated locations. The hit rate looks excellent and the classifier is meaningless.

Correct model: row numbers repeat per bank, so a hit requires the same bank and the same row and a usable open state. Chapter 9.3 §2.

Prevention: the bank-ignored bug produces a suspiciously high hit rate, which Chapter 9.6 §9 lists as a symptom.

"The controller can query the DRAM for its current open row."

Why it is tempting: every other subsystem can be interrogated, and mode registers are readable in DDR5 — so readback exists, just not for this.

Concrete failure: a debug plan that assumes the open row can be dumped, and a verification environment with no row-context model because it expected to observe one.

Correct model: no such query exists. The controller knows because it tracked. §4, and Chapter 9.1 §8.

Prevention: build the model deliberately, assert on it, and treat it as a design object.

"If the controller's model and the monitor's model agree, both are right."

Why it is tempting: two independent derivations agreeing is strong evidence, and usually it is.

Concrete failure: both models were written from the same wrong assumption — both apply transitions at the command, say — so they agree perfectly and are both open during an interval the device is not.

Correct model: agreement is evidence, not proof. §5's stated limitation.

Prevention: derive the monitor from the specification, never from the controller's source, and review the transition rule explicitly.

"A column access refreshes or extends the open row."

Why it is tempting: activity feels like it should keep something alive, by analogy with LRU.

Concrete failure: a model in which reading an open row postpones its closure, producing predictions that never match measurement.

Correct model: a column access selects from what is held and changes nothing about the row context. P3, and §6's cycles 2 to 7. What closes a row is a precharge or an auto-precharge, not inactivity.

Prevention: P3. It is cheap and it forbids the whole class.

11. Interview Reasoning

"What is the row buffer, physically?"

The bank's sense amplifiers in their resolved state. A sense amplifier is a regenerative circuit — once it has resolved a small bitline difference into a full logic level it holds that level — and there is one per bitline pair, so a row spans the whole structure. Nothing is copied into a separate store; the values are amplified where they already were. That is why closing a row moves no data, why capacity is exactly one row per bank and not a design parameter, and why there is no backing store for a "miss" to fetch from — every row is always present in the array.

"Where does the cache analogy break?"

It delivers exactly one idea correctly: the expensive step is already paid, so locality pays. Everything else differs. There is no backing store, so a miss fetches nothing. There is no write-back, because restoration happened during the activate and the cells are already correct. Capacity is structural at one row per bank, with no associativity and no replacement policy to tune. It cannot be inspected — no command returns the open row. And "hit" ranges over a bank rather than over an address, because row numbers repeat per bank.

"How would a verification monitor reconstruct open-row state?"

By maintaining its own per-{rank, bank} model from the observed command stream, applying transitions on a modelled completion rather than on the command, and starting from unknown rather than from closed — because a monitor may attach to a running system and cannot know the initial state. Then it compares against the controller's model. What it cannot do is check either model against the device, since nothing observable reports the open row; the two models can only be checked against each other, which is still useful because they were built from different information — intent versus wire.

"Two models of the open row agree. What have you proved?"

That two independent derivations from different information reached the same answer, which is good evidence and not proof. If both were written from the same wrong assumption they agree and are both wrong — the classic case being both applying the transition at the command rather than at completion, which makes both claim the bank is open during an interval when it is not. The practical consequence is a process one: the monitor has to be derived from the specification rather than from the controller's source, or the comparison is measuring a copy against itself.

"A column access happens to an open row. What changes in the row context?"

Nothing. A column access selects which part of the held row reaches the interface; it does not alter which row is held, and it does not extend the row's lifetime. A row is closed by a precharge or by an auto-precharge, never by inactivity or by activity. The mistake is tempting because column commands carry a bank operand, which makes it easy to write a monitor that updates row state on them — and such a monitor stays plausible and drifts slowly, which is the worst failure profile there is.

12. Engineering Exercise

NUM_BANKS = 4. A monitor attaches to a running system at cycle 0.

1. At cycle 0, bank 2 is physically open on row 50 and the controller's table says so. What does the monitor report, and is diverged asserted?

2. The monitor then observes PRE b2 followed by pre_done b2. What is mon_known[2] afterwards, and why did a precharge establish knowledge?

3. Reconstruct the controller's open-row table from this observed trace, assuming each command completes before the next: ACT b0 r10 · ACT b3 r77 · PRE b0 · ACT b0 r11 · ACT b1 r5.

4. After that trace, which banks are open and which are unknown to a monitor that attached at the start of it?

5. A monitor updates mon_row on every command carrying a bank, including reads. Give the exact sequence that makes it diverge, and say what diverge_kind would report.

6. Both models apply transitions at the command instead of at completion. What does diverged show, and what has been lost?

13. Summary

There is no buffer. The row buffer is the bank's sense amplifiers in their resolved state — a regenerative structure holding one row's values where those values already were. Nothing was copied.

The cache analogy is good for one sentence. Locality pays, because the expensive step is already done. Then it breaks: no backing store, so nothing is fetched on a miss; no write-back, because restoration happened during the activate; capacity is structurally one row per bank, with no replacement policy; it cannot be inspected; and "hit" ranges over a bank, not an address.

A column access changes no row context. It selects from what is held. A row is closed by a precharge or an auto-precharge — never by inactivity, and never extended by activity.

Three objects, in three places: the device's actual state, which is authoritative and unobservable; the controller's table, which it acts on; and a monitor's reconstruction, built from the wire. The two models can only be checked against each other, and that is worth doing because they were built from different information.

A monitor must start from unknown, per bank. A controller may reset to closed because it knows it issued nothing; a monitor may not, because it may have attached to a running system. Unknown is not agreement, and a clean divergence output over banks that were never accessed means much less than it looks like.

And the most dangerous divergence is the one where the states agree. Same state, different row: every subsequent column command is well-formed, legal, and aimed at the wrong place.

14. What Comes Next

The state is now established and the vocabulary for talking about it is in place. Chapter 9.3 — Row Hits asks the first of three questions about what a request does when it meets that state.

The best case is that the bank already holds the row the request wants. Chapter 9.3 defines that case precisely — and precision matters more than it sounds, because the obvious definition is wrong in two ways: it forgets that row numbers repeat per bank, and it forgets that a bank can hold a row while not yet being usable.

It also establishes the taxonomy the rest of the module uses, and sets out what a row hit does not mean — starting with the idea that it is free.


Return to Row Opening for the four-state model this chapter mirrors, Sense Amplifiers for the circuit that does the holding, Restore Operations for why the data was never at risk, Banks for per-bank independence, Bank Groups for what banks share that is not the row, and Precharge for the monitor whose known-bit idea this chapter reuses.

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.