Skip to content
VLSI Mentor

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_prechargebecause 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   bank open with row R1
     PRE(B)        ->  bank closed
     ACT(B, R2)    ->  bank open with row R2

That 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.

A bank can close in three ways. A single-bank precharge names the bank explicitly and is fully described by its command. An all-bank precharge names no bank and changes every bank's state at once. An auto-precharge operand on an earlier read or write closes the bank at completion, with no command on the bus announcing it at all. A monitor reconstructing state from the command stream must handle all three, and only the first is directly described by a command.PRE, A10 lownames one bankPRE, A10 highnames no bankAuto-prechargeno command at allFully describedmonitor just applies itChanges every bankscope from an operandMust be scheduledapplied at completionsososo12
Figure 1 — three ways a bank closes, and only one of them names the bank.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// 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

endmodule

Cycle-by-cycle example

NUM_BANKS = 4, AP_COMPLETE_EVENTS = 3:

CycleObservedModel effectEmitted transaction
0RD bank 1nonerow unknown — no activate seen yet
1ACT bank 1, row 500bank 1 open, row 500, knownACT with row 500
2RD bank 1, col 8noneRD with row 500 filled in
3RD bank 1, col 9, A10 highimplicit precharge scheduledRD with row 500
6bank 1 closesnothing emitted
7RD bank 1noneviolation — column access to a closed bank
8PRE, A10 highall four banks closed and knownPRE

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 handlingChapter 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 cycles
Ten cycles. A read to a bank whose history has not been observed is reported with an unknown row rather than as a violation. An activate then makes the bank known and supplies its row, after which a read emits a transaction with that row resolved. A read carrying an auto-precharge operand schedules an implicit close, which is applied three events later at a cycle with no observed command. A subsequent read to that bank is reported as a protocol violation. A final all-bank precharge closes every bank and makes them all known, resynchronising the model.row resolved from modelrow resolved from modelmodel says closedmodel says closedunknown, not a violationunknown, not a violationcloses — no command herecloses — no command herePREA resynchronisesPREA resynchronisesCKobs_cmdRDACTRDRD------RDPREA--obs_A10mon_open[1]mon_known[1]txn_row?500500500------?----row_unknownviolationt0t1t2t3t4t5t6t7t8t9
Figure 2 — the model changes at a cycle where nothing was commanded.

Cycle 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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: verifiedA10 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

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.