DDR · Module 7
Precharge (PRE / PREA)
Precharge closes a bank, and its scope is decided by an operand: one bank or all of them. It is also the command that exposes why a command stream does not fully describe device state — which is the hardest problem a DV monitor faces.
Chapter 7.1 opened a row. This chapter closes it, and precharge turns out to be the most structurally interesting of the four access commands for two reasons.
Its scope is an operand. Verified DDR4 behaviour: address bit A10 high makes a precharge apply to all banks rather than one — the same bit that means auto-precharge on a read or write. So a precharge can change state in banks its command never names.
And it is where the command stream stops fully describing device state. Between all-bank precharge and the auto-precharge of 7.2 and 7.3, there are now two distinct ways for a bank to close without a command naming it.
So the chapter's question is:
How is a command's scope decided, and how does a monitor reconstruct device state from a command stream that does not fully describe it?
That second half is the hardest problem in DDR verification, and this is the chapter that builds the component which solves it.
1. PRE Closes a Bank
Semantically, PRE says: close bank B, returning it to a state where a different row can be activated.
It carries a bank and no row. There is no choice about which row to close — a bank holds one open row, so closing the bank closes that row. Chapter 5.2 §1 established why.
Its prerequisite is weak and its consequence is strong. Precharging an already-closed bank is harmless on many devices, and Chapter 5.2 §5 still reports it as illegal_precharge — because it is a modelling error even when it is electrically benign. A controller that issues it has lost track of state, and that is worth knowing before the next command depends on the same wrong belief.
And it is the command that makes row changes possible. Chapter 7.1 §1 established that an ACT requires a closed bank. So the full row-change sequence is:
bank open with row R1
PRE(B) -> bank closed
ACT(B, R2) -> bank open with row R2That two-command sequence is what Chapter 5.2 called a row conflict, and it is the most expensive thing an access pattern can do repeatedly.
2. Precharge Does Not Write Anything Back
This misconception is worth killing early because it is structural rather than incidental.
The row buffer resembles a cache, and closing a cache line does write it back. So "precharge flushes the row to the array" is an extremely natural inference.
It is wrong, and Chapter 2.6 already established why. DRAM's read is destructive — the act of sensing consumes the stored charge — so restoration is not something that can be deferred. It happens as part of the access, driven by the sense amplifiers holding their resolved value back onto the cells. By the time a precharge is issued, the row has already been restored.
So what does precharge actually do? It returns the bank's bitlines and sense amplifiers to the condition required before another row can be sensed — Chapter 3.3 and Chapter 3.5 built the mechanism. It is preparation, not a flush.
Why the distinction has consequences. A "write-back" model predicts that skipping a precharge risks data loss, which is false — and it predicts that precharge duration scales with how much was written, which is also false. The actual consequence of not precharging is that you cannot open a different row, which is a performance and legality matter rather than a durability one.
3. Scope Is an Operand
Now the structural point.
Verified: A10 sampled with a precharge selects scope. Low precharges one bank; high precharges all banks.
So a precharge with A10 high changes state in every bank, including banks its command never named. Chapter 5.2 §5 called this the deliberate exception to bank isolation, and built it into ddr_bank_state_table as CMD_PREA — the one operation that writes every entry.
Why the exception exists is worth stating: closing every bank one at a time before a device-wide event — a refresh, a power-state change, an initialisation step — would cost a command per bank. One command replaces many, and Chapter 6.3 §3 established that command-bus events are a real resource.
And a fourth way is coming. Chapter 7.5 covers refresh, which also changes bank state — and in DDR5, verified, a same-bank refresh targets one bank across all bank groups, designated by bank bits, which is a third distinct scope shape. A monitor must handle all of them, and only the first row of the figure is straightforward.
4. RTL — The Command Monitor
Engineering problem
Reconstruct device state from an observed command stream, so that a passive monitor can report real addresses for column commands and can judge protocol legality — despite the stream not fully describing the state.
Specifically: apply single-bank precharges, apply all-bank precharges to banks never named, schedule implicit precharges from auto-precharge operands, and be honest about state the monitor cannot know.
Classification
VERIFICATION-ONLY. This is a passive monitoring model, not hardware. It is not intended for synthesis, it drives nothing, and it exists to observe.
What it represents: a monitor's reconstructed per-bank model, transaction emission with the row filled in, protocol-violation reporting, and explicit uncertainty.
What it does not represent: the data path — this monitor observes commands, and correlating a read's data with its command is a scoreboard's job. No timing checking whatsoever. No DQ or DQS observation. No refresh handling — Chapter 7.5 adds it, and this block's model_uncertain output is how it declares the gap rather than guessing.
Interface contract
The observed command stream from Chapter 7.2's decoder comes in. The monitor emits transactions with the row resolved, reports violations, and exposes its model and its uncertainty.
State
Per-bank open/closed and open row, a pending auto-precharge record, and a per-bank known/unknown flag.
Combinational behaviour
Violation detection and transaction assembly.
Sequential behaviour
Model updates, including scheduled implicit precharges.
How to simulate
vlog cmd_monitor_model.sv tb_cmd_monitor_model.sv then vsim -c tb_cmd_monitor_model -do "run -all".
Expected output
A column command after an activate emits a transaction with the correct row filled in. An auto-precharge operand closes the bank some events later with no command in the stream. An all-bank precharge closes banks that were never named. And a column command to a bank the monitor has never seen activated is reported as uncertain rather than as a violation.
// ─────────────────────────────────────────────────────────────────────────
// DDR COMMAND MONITOR MODEL.
// Classification: VERIFICATION-ONLY. A passive monitoring model, NOT
// hardware. Not intended for synthesis; it drives nothing and observes.
//
// THE PROBLEM IT SOLVES: a command stream does not fully describe device
// state. A bank can close in three ways and only one names the bank:
// PRE with A10 low -- names one bank (easy)
// PRE with A10 high -- names NO bank, closes all (verified DDR4)
// auto-precharge -- NO COMMAND AT ALL (Chapters 7.2, 7.3)
// So a monitor must apply, broadcast, and SCHEDULE state changes.
//
// WHY IT KEEPS ITS OWN MODEL rather than reading the controller's:
// independence is the value. The controller's table is its BELIEF; this is
// an independent reconstruction from what appeared on the interface. The
// point is that they can DISAGREE -- and the disagreement is the bug. A
// monitor sharing the controller's state could never detect a controller
// state error.
//
// IT DECLARES UNCERTAINTY rather than guessing. After reset the monitor
// has not seen the activate history, so it does not know which rows are
// open. Reporting a confident wrong row is worse than reporting "unknown",
// because a confident wrong report gets believed.
//
// NOT MODELLED: the data path (correlating a read's data with its command
// is a scoreboard's job), timing of any kind, DQ/DQS observation, and
// REFRESH -- Chapter 7.5 adds it, and until then refresh is exactly the
// kind of event model_uncertain exists to flag.
//
// AP_COMPLETE_EVENTS is an EDUCATIONAL cycle count standing in for "when
// the access completes". Not a timing parameter.
// ─────────────────────────────────────────────────────────────────────────
module cmd_monitor_model #(
parameter int NUM_BANKS = 4,
parameter int ROW_W = 17,
parameter int COL_W = 10,
// Events from an auto-precharge column command to the implied close.
// EDUCATIONAL -- see the header.
parameter int AP_COMPLETE_EVENTS = 3,
parameter int BANK_W = (NUM_BANKS <= 1) ? 1 : $clog2(NUM_BANKS),
parameter int APC_W = (AP_COMPLETE_EVENTS <= 1) ? 1 : $clog2(AP_COMPLETE_EVENTS + 1)
) (
input logic clk,
input logic rst_n,
// ── Observed command stream, from Chapter 7.2's decoder.
input logic obs_valid,
input ddr_cmd_e obs_cmd,
input logic [BANK_W-1:0] obs_bank,
input logic [ROW_W-1:0] obs_row,
input logic [COL_W-1:0] obs_col,
input logic obs_autoprecharge,
input logic obs_allbank,
// ── Emitted transaction, with the row RESOLVED from the model. This is
// what distinguishes a command monitor from a bus trace: a column
// command carries no row, so the monitor must supply it.
output logic txn_valid,
output ddr_cmd_e txn_cmd,
output logic [BANK_W-1:0] txn_bank,
output logic [ROW_W-1:0] txn_row,
output logic [COL_W-1:0] txn_col,
// The monitor could not resolve the row, so txn_row is meaningless.
output logic txn_row_unknown,
// ── Protocol reporting.
output logic protocol_violation,
// 0 none, 1 column access to a closed bank, 2 activate on an open bank,
// 3 precharge of a closed bank.
output logic [1:0] violation_reason,
// The monitor's model is not trustworthy for the addressed bank --
// typically because it has not observed an activate for it since reset.
output logic model_uncertain,
// ── The reconstructed model, exposed for comparison against the
// controller's. Comparing them is how Chapter 7.2's silent mismatch
// is actually found.
output logic [NUM_BANKS-1:0] mon_bank_open,
output logic [NUM_BANKS-1:0] mon_bank_known
);
// ── COMPILE-TIME legality.
if (NUM_BANKS < 1) begin : g_nb
initial $fatal(1, "cmd_monitor_model: NUM_BANKS must be >= 1");
end
if (ROW_W < 1 || COL_W < 1) begin : g_w
initial $fatal(1, "cmd_monitor_model: operand widths must be >= 1");
end
// A zero-event completion would apply an auto-precharge in the same
// event as the command, hiding the fact that the close is DEFERRED --
// which is the property this block exists to model.
if (AP_COMPLETE_EVENTS < 1) begin : g_apc
initial $fatal(1, "cmd_monitor_model: AP_COMPLETE_EVENTS must be >= 1");
end
localparam logic [1:0] V_NONE = 2'd0;
localparam logic [1:0] V_COL_CLOSED = 2'd1;
localparam logic [1:0] V_ACT_OPEN = 2'd2;
localparam logic [1:0] V_PRE_CLOSED = 2'd3;
// ── The reconstructed model.
logic open_q [NUM_BANKS];
logic [ROW_W-1:0] row_q [NUM_BANKS];
// Has the monitor observed enough to trust this bank's entry? Separate
// from `open`, because "closed" and "unknown" are different claims and
// conflating them makes the monitor confidently wrong after reset.
logic known_q [NUM_BANKS];
// ── Pending implicit precharge from an auto-precharge operand. One at a
// time here; a real monitor needs one per bank, and the limitation is
// stated rather than silently tolerated.
logic ap_pending_q;
logic [BANK_W-1:0] ap_bank_q;
logic [APC_W-1:0] ap_cd_q;
always_comb begin
for (int b = 0; b < NUM_BANKS; b++) begin
mon_bank_open[b] = open_q[b];
mon_bank_known[b] = known_q[b];
end
end
logic bank_bad;
if (NUM_BANKS >= (1 << BANK_W)) begin : g_bk_full
assign bank_bad = 1'b0;
end else begin : g_bk_chk
assign bank_bad = ({1'b0, obs_bank} >= (BANK_W+1)'(NUM_BANKS));
end
logic is_col, is_act, is_pre;
assign is_col = (obs_cmd == DDR_CMD_RD) || (obs_cmd == DDR_CMD_WR);
assign is_act = (obs_cmd == DDR_CMD_ACT);
assign is_pre = (obs_cmd == DDR_CMD_PRE);
logic sel_open, sel_known;
assign sel_open = !bank_bad && open_q[obs_bank];
assign sel_known = !bank_bad && known_q[obs_bank];
// ── Violation detection, and UNCERTAINTY KEPT SEPARATE FROM VIOLATION.
//
// A column access to a bank the monitor has never seen activated is
// not necessarily illegal -- the monitor simply does not know.
// Reporting it as a violation would produce false findings after
// every reset, which is how a checker earns a reputation for noise
// and stops being read.
always_comb begin
protocol_violation = 1'b0;
violation_reason = V_NONE;
model_uncertain = 1'b0;
if (obs_valid && !bank_bad) begin
if (is_col && !sel_known) begin
model_uncertain = 1'b1;
end else if (is_col && !sel_open) begin
protocol_violation = 1'b1;
violation_reason = V_COL_CLOSED;
end else if (is_act && sel_known && sel_open) begin
protocol_violation = 1'b1;
violation_reason = V_ACT_OPEN;
end else if (is_pre && !obs_allbank && sel_known && !sel_open) begin
// Harmless on many devices and still a modelling error, exactly as
// Chapter 5.2 Section 5 framed it: the issuer has lost track.
protocol_violation = 1'b1;
violation_reason = V_PRE_CLOSED;
end
end
end
// ── Transaction emission. The row is RESOLVED from the model for column
// commands -- the step that makes this a command monitor rather than
// a trace dumper (Chapter 7.2 Section 7).
always_comb begin
txn_valid = obs_valid && !bank_bad;
txn_cmd = obs_cmd;
txn_bank = obs_bank;
txn_col = obs_col;
txn_row_unknown = 1'b0;
txn_row = '0;
if (is_act) begin
txn_row = obs_row; // the activate carries its own row
end else if (is_col) begin
if (sel_known && sel_open) txn_row = row_q[obs_bank];
else txn_row_unknown = 1'b1;
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int b = 0; b < NUM_BANKS; b++) begin
open_q[b] <= 1'b0;
row_q[b] <= '0;
// UNKNOWN, not closed. The monitor has not observed the activate
// history, so it must not claim to know.
known_q[b] <= 1'b0;
end
ap_pending_q <= 1'b0;
ap_bank_q <= '0;
ap_cd_q <= '0;
end else begin
// ── Apply the observed command to the model.
if (obs_valid && !bank_bad) begin
unique case (obs_cmd)
DDR_CMD_ACT: begin
// An activate is the monitor's only source of row information,
// and observing one is what makes a bank KNOWN.
open_q[obs_bank] <= 1'b1;
row_q[obs_bank] <= obs_row;
known_q[obs_bank] <= 1'b1;
end
DDR_CMD_PRE: begin
if (obs_allbank) begin
// ── VERIFIED DDR4: A10 high precharges ALL banks. This
// command names no bank and changes every entry -- the
// deliberate exception to isolation (Chapter 5.2).
// It also makes every bank KNOWN, because after it the
// monitor is certain they are all closed. That is a
// genuinely useful side effect: an all-bank precharge
// RESYNCHRONISES a monitor that had lost track.
for (int b = 0; b < NUM_BANKS; b++) begin
open_q[b] <= 1'b0;
known_q[b] <= 1'b1;
end
end else begin
open_q[obs_bank] <= 1'b0;
known_q[obs_bank] <= 1'b1;
end
end
DDR_CMD_RD, DDR_CMD_WR: begin
// A column access changes no row state -- unless it carries
// an auto-precharge operand, in which case the close happens
// LATER and must be SCHEDULED rather than applied now.
if (obs_autoprecharge) begin
ap_pending_q <= 1'b1;
ap_bank_q <= obs_bank;
ap_cd_q <= APC_W'(AP_COMPLETE_EVENTS);
end
end
default: ; // refresh and mode-register: Chapters 7.5 and 7.6
endcase
end
// ── Apply a scheduled implicit precharge. NO COMMAND IN THE STREAM
// CORRESPONDS TO THIS. A monitor that is purely reactive to
// commands will miss it entirely, and will then report the next
// column access to this bank with a row the device no longer has.
if (ap_pending_q) begin
if (ap_cd_q <= APC_W'(1)) begin
ap_pending_q <= 1'b0;
ap_cd_q <= '0;
open_q[ap_bank_q] <= 1'b0;
known_q[ap_bank_q] <= 1'b1;
end else begin
ap_cd_q <= ap_cd_q - APC_W'(1);
end
end
end
end
endmoduleCycle-by-cycle example
NUM_BANKS = 4, AP_COMPLETE_EVENTS = 3:
| Cycle | Observed | Model effect | Emitted transaction |
|---|---|---|---|
| 0 | RD bank 1 | none | row unknown — no activate seen yet |
| 1 | ACT bank 1, row 500 | bank 1 open, row 500, known | ACT with row 500 |
| 2 | RD bank 1, col 8 | none | RD with row 500 filled in |
| 3 | RD bank 1, col 9, A10 high | implicit precharge scheduled | RD with row 500 |
| 6 | — | bank 1 closes | nothing emitted |
| 7 | RD bank 1 | none | violation — column access to a closed bank |
| 8 | PRE, A10 high | all four banks closed and known | PRE |
Cycle 0 is the honesty. A column access to a bank whose history the monitor has not seen is reported uncertain, not as a violation. Reporting it as a violation would produce a false finding after every reset, and a checker that cries wolf stops being read.
Cycle 2 is why the monitor exists. The read carries no row, and the emitted transaction has row 500 in it — resolved from the model. A bus trace cannot do this.
Cycle 6 is the invisible state change. The bank closes, and there is no command in the stream at that cycle. The monitor applied a change it scheduled three cycles earlier.
Cycle 7 is the consequence of getting cycle 6 right. A purely reactive monitor would still believe bank 1 was open, would emit row 500 again, and would report no violation — quietly disagreeing with the device.
And cycle 8 resynchronises everything. An all-bank precharge makes all four banks closed and known — which is genuinely useful: it is the one command that recovers a monitor which had lost track.
Waveform expectation
§5. Watch the model change at a cycle with no observed command, and a transaction carry a row the command did not.
Synthesis implication
None — this is verification-only. It would synthesise, and doing so would be pointless: it drives nothing and its purpose is observation. Declaring it non-synthesisable is not a limitation but a classification, and Chapter 7.3 §4's block is the synthesisable counterpart.
Corner cases
Reset leaves every bank unknown, not closed — the most important corner case here, and the one a naive monitor gets wrong. AP_COMPLETE_EVENTS == 0 does not elaborate, because it would apply the implicit close in the same event as the command and hide the deferral. An all-bank precharge when everything is already closed is a no-op and not reported — matching Chapter 5.2 §5's framing. An invalid bank index emits no transaction. Only one auto-precharge is tracked at a time, which is a stated simplification: a real monitor needs one pending record per bank.
Debugging clues
If the monitor reports violations immediately after every reset, known_q is being initialised to one, or uncertainty is being reported as violation. If column transactions carry stale rows, the scheduled auto-precharge is not being applied — check that the countdown runs on cycles with no observed command. If the monitor and the controller disagree after an all-bank precharge, one of them is applying it to only the named bank. If violations appear for precharges of closed banks in a system that works, that is correct behaviour and the finding is real — the issuer has lost track even though the device tolerated it.
Limitations
No refresh handling — Chapter 7.5 adds it, and until then refresh is exactly the kind of event model_uncertain exists to flag. One pending auto-precharge. No data correlation, no timing, no DQ/DQS observation. And AP_COMPLETE_EVENTS is an educational stand-in for "when the access completes".
5. State Changing With No Command
cmd_monitor_model — row resolution, a scheduled close, and a resynchronising all-bank precharge
10 cyclesCycle 0 and cycle 7 both show row_unknown, for opposite reasons. At cycle 0 the monitor has never seen an activate for bank 1, so it does not know — and reports no violation, because ignorance is not evidence of a fault. At cycle 7 the monitor does know: the bank is closed, so a column access to it is a genuine violation.
Distinguishing "I don't know" from "that's illegal" is the difference between a useful checker and a noisy one.
Cycle 6 is the chapter's point. obs_cmd shows nothing and mon_open[1] falls. The model changed with no command. That change was scheduled at cycle 3 when the read carried A10 high, and a monitor that only reacts to commands would have missed it entirely — then reported cycle 7 as legal with row 500.
Cycle 8's all-bank precharge sets every known bit. After it, the monitor is certain about every bank. That makes PREA the one command that can recover a lost monitor, which is worth knowing when instrumenting a system whose early command history you did not capture.
Representative educational cycles. The three-event interval from the auto-precharge command to the implied close is a property of this model and corresponds to no timing requirement.
6. Four Assertions Worth Writing
// VERIFICATION-ONLY, inside cmd_monitor_model.
// Note these are assertions ON A MONITOR -- properties of the observer,
// not of the device. A monitor is a design under test like any other, and
// its failure mode is silent, misleading confidence.
// P1 -- THE HONESTY PROPERTY. Uncertainty and violation are mutually
// exclusive. A monitor that reported "illegal" when it meant "I don't
// know" produces false findings after every reset, and a checker that
// cries wolf stops being read -- which loses the real findings too.
property p_uncertain_xor_violation;
@(posedge clk) disable iff (!rst_n)
!(model_uncertain && protocol_violation);
endproperty
assert property (p_uncertain_xor_violation);
// P2 -- a resolved row is only emitted when the monitor actually knows it.
// A transaction carrying a confidently wrong row is worse than one
// carrying none, because it gets believed.
property p_row_only_when_known;
@(posedge clk) disable iff (!rst_n)
(txn_valid && ((txn_cmd == DDR_CMD_RD) || (txn_cmd == DDR_CMD_WR))
&& !txn_row_unknown)
|-> (mon_bank_known[txn_bank] && mon_bank_open[txn_bank]);
endproperty
assert property (p_row_only_when_known);
// P3 -- THE ALL-BANK PROPERTY. A precharge with A10 high closes every
// bank, including ones it never named. Verified DDR4 behaviour, and the
// deliberate exception to isolation.
property p_prea_closes_all;
@(posedge clk) disable iff (!rst_n)
(obs_valid && (obs_cmd == DDR_CMD_PRE) && obs_allbank)
|=> (mon_bank_open == '0);
endproperty
assert property (p_prea_closes_all);
// And it makes everything KNOWN, which is what lets it resynchronise a
// monitor that had lost track.
property p_prea_makes_known;
@(posedge clk) disable iff (!rst_n)
(obs_valid && (obs_cmd == DDR_CMD_PRE) && obs_allbank)
|=> (mon_bank_known == '1);
endproperty
assert property (p_prea_makes_known);
// P4 -- RESET LEAVES EVERYTHING UNKNOWN, not closed. The corner case a
// naive monitor gets wrong, and the one that generates false findings.
property p_reset_is_unknown;
@(posedge clk)
!rst_n |=> (mon_bank_known == '0);
endproperty
assert property (p_reset_is_unknown);
// P5 -- a scheduled implicit precharge is eventually applied. The
// liveness companion: P1 to P4 all forbid, and a monitor that never
// applied a deferred close would satisfy every one of them while
// silently diverging from the device.
property p_pending_ap_completes;
@(posedge clk) disable iff (!rst_n)
$rose(ap_pending_q)
|-> ##[1:AP_COMPLETE_EVENTS] !mon_bank_open[$past(ap_bank_q)];
endproperty
assert property (p_pending_ap_completes);P1 and P4 are the properties that make this monitor trustworthy rather than merely functional, and both are about not claiming knowledge it lacks. That is an unusual thing to assert and it matters here because a monitor's failure mode is confident wrongness — it does not crash, it reports, and its reports are believed.
P5 is the liveness companion and it guards the chapter's central mechanism. A monitor that scheduled implicit precharges and never applied them would pass P1 through P4 completely, and would diverge from the device on every auto-precharge. Restrictive properties always need a companion that requires something to happen — this module's fifth instance.
What none of them prove. Nothing about timing. Nothing about the data a read returns, which is a scoreboard's job. Nothing about refresh, which this monitor does not model. And — most importantly — nothing about whether the monitor's model matches the device's. These properties establish internal consistency; the disagreement that finds real bugs is found by comparing this model against the controller's, which is a testbench-level activity no assertion inside the monitor can perform.
7. DV — What This Monitor Makes Possible
§4's block is the component Chapter 7.2 §7 and Chapter 7.3 §8 both pointed at, and it is worth naming what it enables.
Transactions with real addresses. A column command carries a bank and a column. Only a monitor with an open-row model can say which address was accessed — and without that, every read and write transaction in a trace is incomplete.
Protocol violation reporting the device cannot do. Chapter 7.2 §1 established that a column command gives the device nothing to check against. The monitor is the only component that can report a column access to a closed bank, and it can only do so because it reconstructed the state.
Controller state-model validation. Comparing mon_bank_open against the controller's own table is how Chapter 7.2 §8's silent mismatch is actually found. Two independent models, compared — and the disagreement is the bug.
And an honest uncertainty signal. A monitor attached partway through a run, or after a reset, genuinely does not know the state. Declaring that is what keeps its other reports credible.
One practical caution. The monitor is only as good as the decoded stream it receives. If Chapter 7.2's decoder mis-extracts A10, this monitor will faithfully model the wrong thing — scheduling auto-precharges that were not requested, or missing ones that were. Shared decode means a decoder bug produces consistent wrongness across the whole environment, which is easier to find than three components disagreeing, but is still a single point of failure worth testing directly.
8. Debugging — The Monitor and the Device Disagree
Symptom. A protocol checker reports violations that appear spurious, or fails to report violations that later analysis proves occurred. The monitor's bank model and the controller's disagree.
A monitor is a design under test, and this is the chapter's debugging lesson: when the observer and the observed disagree, the observer is a suspect.
Mechanism 1 — the monitor missed an implicit precharge. Inspect: whether auto-precharge operands on earlier column commands were being tracked. Expected evidence: the monitor believing a bank open that the device closed, with no precharge command between. Discriminator: search the stream for A10 on prior column commands to that bank. §4's central mechanism, and the most likely cause because it requires the monitor to be predictive rather than reactive.
Mechanism 2 — the monitor applied an all-bank precharge to one bank. Inspect: whether a precharge with A10 high updated every entry. Expected evidence: disagreement on banks other than the one named. Discriminator: check the banks the command did not name. Verified behaviour, and an easy thing to implement as a single-bank update by accident.
Mechanism 3 — the monitor started from a wrong assumption. Inspect: what the model held immediately after reset or after attachment. Expected evidence: violations clustered at the start of a run and disappearing later. Discriminator: do the spurious findings stop once traffic has been running? That signature names initialisation: a monitor assuming all banks closed rather than unknown will be wrong until it has observed an activate for each.
Mechanism 4 — the decoder feeding the monitor is wrong. Inspect: whether A10 is being extracted per-operation, as Chapter 7.2 §6's P1 requires. Expected evidence: the monitor tracking auto-precharges on precharge commands, or missing them on reads. Discriminator: check whether auto-precharge is ever reported for a non-column command. The fault is upstream of the monitor and the monitor will faithfully model the wrong stream.
Mechanism 5 — the monitor is right and the controller is wrong. Inspect: the actual device behaviour against both models. Expected evidence: the monitor's model matching the device and the controller's not. Discriminator: which model matches reality? This is the outcome the whole arrangement exists to produce — and it is worth listing last precisely because the instinct is to assume the tool is broken rather than the design.
Discrimination, cheapest first. Ask whether spurious findings cluster at the start of a run, which names mechanism 3 immediately. Then search the command stream for A10 on column commands to the disputed bank. Then check whether all-bank precharges updated every entry. Then check the decoder's per-operation extraction. Then — and only then — consider that the controller may be the one that is wrong.
The reasoning lesson. Verification components have failure modes that are systematically harder to find than design failure modes, because they fail by reporting rather than by breaking. A monitor with a state bug does not crash; it produces a confident, detailed, wrong picture — and every conclusion drawn from it inherits the error. So a monitor needs its own assertions (§6), its own reset discipline, and an explicit uncertainty signal. The question "is the tool telling me the truth" belongs early in the list, not as a last resort — but not first either, because assuming the tool is broken is how real design bugs get dismissed.
9. Common Misconceptions
"PRECHARGE writes the data back." Wrong model: precharge is a flush, like evicting a dirty cache line. Why it is tempting: the row buffer resembles a cache and the analogy holds in several other respects. Consequence: a belief that skipping a precharge risks data loss, and that precharge cost scales with how much was written. Both are false, and the second leads to nonsensical performance models. It also obscures the real cost, which is that you cannot open a different row until you precharge. Correct model: Chapter 2.6 established that DRAM's read is destructive, so restoration cannot be deferred — it happens as part of the access. By the time a precharge is issued the row is already restored. Precharge prepares the bitlines and sense amplifiers for the next row. It is preparation, not a flush. Prevention: ask what precharge transfers. Nothing.
"A precharge always names the bank it closes." Wrong model: a command's scope is implied by its operands naming a target. Why it is tempting: every other access command names exactly what it acts on. Consequence: a state model that tracks only the named bank, so an all-bank precharge closes one entry and leaves the rest wrongly open. The divergence then surfaces on a completely unrelated bank, which makes it hard to trace back. Correct model: verified — A10 high makes the precharge apply to all banks, and the command names no particular one. It is Chapter 5.2's deliberate exception to isolation, and it exists because closing banks one at a time before a device-wide event would cost a command each. Prevention: for any command, ask whether its scope is fixed or carried in an operand. Precharge's is carried.
"If a monitor sees every command, it knows the device state." Wrong model: the command stream fully describes state. Why it is tempting: commands are what change state, so observing all of them feels sufficient — and it is nearly true. Consequence: a monitor that misses the two cases where state changes without a naming command: an all-bank precharge which names no bank, and an auto-precharge which produces no command at all. The second is worse, because there is nothing in the stream at the moment of the change. Chapter 7.5 adds a third. Correct model: a monitor must apply named changes, broadcast all-bank ones, and schedule implicit ones. It must be predictive, not purely reactive. Prevention: list every way a bank can close. If the list has one entry, it is incomplete.
"A monitor should assume banks are closed after reset."
Wrong model: closed is the safe default.
Why it is tempting: it is the device's actual state after reset, and it avoids an extra state.
Consequence: false violation reports for any bank whose activate the monitor did not observe — which is every bank if the monitor was attached partway through a run. And a checker that reports spurious violations gets its findings dismissed wholesale, losing the real ones.
Correct model: unknown is not closed. They are different claims, and a monitor that has not observed a bank's history should say so. §4's known_q and §6's P4 exist for exactly this.
Prevention: ask what the monitor would report if attached mid-run. If it reports violations immediately, it is conflating ignorance with evidence.
10. Interview Reasoning
"What does a precharge actually do — does it write the row back?" No. DRAM's read is destructive — sensing consumes the stored charge — so restoration cannot be deferred; it happens as part of the access, with the sense amplifiers driving their resolved value back onto the cells. By the time a precharge is issued the row has already been restored. What precharge does is return the bank's bitlines and sense amplifiers to the condition needed before a different row can be sensed. So it is preparation, not a flush, and the practical consequence of not precharging is not data loss but that you cannot open another row in that bank.
"How is a precharge's scope decided?" By an operand. Address bit A10 sampled with a precharge selects scope: low precharges the named bank, high precharges all banks. So a precharge can change state in banks its command never names, which is the deliberate exception to bank isolation. It exists because closing every bank individually before a device-wide event — a refresh, a power-state change, an initialisation step — would cost a command per bank, and command-bus events are a real resource. It is also the same bit that means auto-precharge on a read or write, which is why a decoder must identify the operation before interpreting any operand.
"Why is building a DDR command monitor hard?" Because the command stream does not fully describe device state, so the monitor cannot be purely reactive. There are three ways a bank closes and only one names the bank: a single-bank precharge names it, an all-bank precharge names none and closes everything, and an auto-precharge operand on an earlier read or write closes it later with no command in the stream at all. A monitor has to apply the first, broadcast the second, and schedule the third. On top of that it must resolve rows for column commands, since a column command carries no row — which means it needs the activate history to report a real address. And it must distinguish not knowing from knowing something is illegal, or it will generate false findings after every reset.
"Why should a monitor keep its own state model rather than reading the controller's?" Because independence is the entire value. The controller's table is its belief about the device; the monitor's is an independent reconstruction from what actually appeared on the interface. The point is that they can disagree, and the disagreement is the bug — comparing them is how you find a controller that thinks a row is open when it is not. A monitor that read the controller's table could never detect a controller state error, because it would share it. That is the same reason a reference model in an assertion must not reuse the logic it is checking: a checker sharing its subject's implementation proves only that the implementation equals itself.
"A protocol checker is reporting violations that look spurious. How do you approach it?" First by asking whether they cluster at the start of a run and stop once traffic has been flowing, because that signature names an initialisation problem — a monitor assuming banks are closed after reset rather than unknown will report violations for every bank whose activate it did not observe. Then search the command stream for A10 on column commands to the disputed bank, because an auto-precharge closes the bank with no command in the stream and a reactive monitor will miss it. Then check whether all-bank precharges updated every entry rather than just the named one. Then check the decoder feeding the monitor, since if it mis-extracts A10 the monitor will faithfully model the wrong stream. And it is worth holding open the possibility that the monitor is right and the controller is wrong — that is the outcome the whole arrangement exists to produce, though assuming the tool is broken is how real design bugs get dismissed.
11. Engineering Exercise
Verified operand behaviour; educational cycle counts; no timing parameters implied.
1. A monitor observes: ACT bank2 row10, RD bank2 col4 (A10=1), then RD bank2 col5. What should it report for each? ACT with row 10. The first RD with row 10 resolved, plus a scheduled implicit precharge. The second RD is a protocol violation — the bank closed at the implied completion, and the monitor must know that even though no command said so.
2. Same sequence, but the monitor is purely reactive. What does it report? All three as legal, with the second RD carrying row 10 — which the device no longer has. No violation, a wrong address, and silent disagreement with the device. That is the failure §4 exists to prevent.
3. After reset, a monitor sees RD bank0. Should it report a violation? No — it should report uncertainty. It has not observed an activate for bank 0, so it does not know whether the bank is open. Reporting a violation would be a false finding, and a checker producing those gets its real findings dismissed. §6's P1 and P4 assert the distinction.
4. Which single command can resynchronise a monitor that has lost track of every bank, and why? An all-bank precharge. After it, every bank is closed and the monitor is certain of that — verified behaviour. §4 sets known_q for every bank on it. That is genuinely useful when instrumenting a system whose early command history you did not capture.
5. §4's monitor tracks one pending auto-precharge. Construct a sequence it gets wrong, and say what a real monitor needs. RD bank1 (A10=1) then immediately RD bank2 (A10=1). The second overwrites the first's pending record, so bank 1 never closes in the model. A real monitor needs one pending record per bank, since auto-precharges to different banks are independent and can overlap.
6. A read is issued to a bank the monitor believes is open with row 7, and the device returns row 9's data. Which component is wrong, and how would you tell? Either the monitor or the controller — the device is doing exactly what it was told. Tell them apart by replaying the command stream: find the last activate for that bank and check whether an auto-precharge or all-bank precharge intervened. If the stream implies row 9, the monitor mis-modelled; if it implies row 7, the device saw commands the monitor did not — which points at the decoder or at a lost command (Chapter 6.2 §4).
12. Summary
PRE closes a bank, returning it to a state where a different row can be activated. It carries a bank and no row, because a bank holds one open row. Its prerequisite is weak — precharging a closed bank is often harmless and is still a modelling error, because the issuer has lost track.
It does not write anything back. Chapter 2.6 established that DRAM's destructive read makes restoration non-deferrable — it happens as part of the access, so the row is already restored when a precharge arrives. Precharge prepares the bitlines and sense amplifiers; it is not a flush.
Its scope is an operand. Verified: A10 high precharges all banks — the same bit that means auto-precharge on a read or write. So a precharge can change state in banks its command never names, which is Chapter 5.2's deliberate exception to isolation and exists because per-bank closing would cost a command each.
And this is where the command stream stops describing device state. Three ways a bank closes and only one names it: a single-bank precharge names it, an all-bank precharge names none, and an auto-precharge produces no command at all. Chapter 7.5 adds a fourth.
So a monitor must be predictive, not reactive — applying named changes, broadcasting all-bank ones, and scheduling implicit ones. It must also resolve rows for column commands, since a column command carries none, which is what makes a transaction an address rather than an offset.
And it must distinguish not knowing from knowing something is illegal. Unknown is not closed. A monitor that assumes closed after reset produces false violations for every bank whose activate it did not observe — and a checker that cries wolf loses its real findings.
Its own model must be independent of the controller's, because the disagreement between them is how Chapter 7.2 §8's silent mismatch gets found. A monitor sharing the controller's state could never detect a controller state bug.
And an all-bank precharge is the one command that resynchronises a lost monitor — after it, every bank is known to be closed.
13. What Comes Next
Chapter 7.5 adds the fourth way bank state changes, and the first command in this module that is pure maintenance.
REFRESH moves no data, carries no column, and — like an all-bank precharge — can affect banks its command does not name. It also has a precondition rather than merely a prerequisite: the device requires banks to be idle before it will accept one, which makes it the first command whose legality depends on more than one bank.
And DDR5 changed its scope shape. Verified: alongside the all-bank refresh available in earlier generations, DDR5 adds a same-bank refresh that targets one bank across all bank groups — a third scope pattern, and one that makes the monitor's job harder again.
Return to Read and Write for the auto-precharge operand this chapter's monitor must schedule, Activate for the command PRE undoes, Banks for the isolation exception, or Restore Operations for why precharge is not a write-back. The full path is on the DDR tutorials index.
Continue learning
Related tutorials
- Related topic
Write (WR / WRA)
A write's data arrives after its command, which makes three events impossible to conflate: observed, accepted, and completed. A monitor, a protocol checker and a scoreboard each attach to a different one.
- Related topic
Physical Mapping
Five chapters of fields assembled into one map, and then the two questions none of them could ask alone: is the decomposition lossless, and can a monitor invert it from what it actually observed?
- Related topic
Row Opening
ACTIVATE transfers no data. It changes a bank from closed to holding one row, and it does so over an interval rather than instantly — which is why a bank has transitional states and why a controller must track them.
- Related topic
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.
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.
