Skip to content
VLSI Mentor

DDR · Module 7

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.

Chapter 7.2 built the decoder and established that decode answers only what operation was requested. WRITE is the mirror command — and it is where a distinction that was implicit becomes impossible to ignore.

A write's data does not accompany its command. The command is sampled at one event; the data arrives at later events. So the chapter's question is:

When is a command observed, when is it accepted, and when is the operation complete — and who cares about each?

Those are three different events, separated in time, with three different consumers. Chapter 7.2 could treat them loosely because a read's command and its data are both "the read". A write forces them apart, which makes it the right place to build the distinction properly.

1. WRITE Writes Into an Open Row

Semantically, WRITE says: accept data for column C of the row currently open in bank B.

The symmetry with READ is exact in the ways that matter. It carries no row addressChapter 7.2 §1's point applies unchanged, so the row must already be open and the device cannot verify it is the right one. It changes no row state. And it needs a prior ACT.

And WRA is a WRITE with A10 high, exactly as RDA is a READ with A10 high — verified. Same encoding, same operation, and the bank closes by itself afterwards with no command on the bus saying so.

The asymmetry is where the data comes from. A read's data is produced by the device; a write's data is produced by the controller and must be delivered to it. Chapter 6.9 §1 established that the controller commits to driving the bus at command time and drives it a fixed interval later. So a write command is a promise about future bus activity, and the operation is not finished when the command is decoded.

2. Three Events, Three Consumers

Here is the distinction this chapter owns.

Observed. The command appeared on the interface at a sampling event, was qualified, and decoded to a semantic operation. This is a fact about the wires.

Accepted. The decoded operation was legal in the device's current state, so the device will act on it. This is a fact about state.

Completed. The operation finished — for a write, the data was transferred and committed. This is a fact about the outcome.

A command is observed when it appears on the interface and decodes, which is a fact about the wires and is what a passive monitor sees. It is accepted when the decoded operation is legal in the device's current state, which is a fact about state and is what a protocol checker evaluates. It is completed when the operation finishes and for a write the data has been transferred and committed, which is a fact about the outcome and is what a scoreboard compares. Conflating the three makes each consumer unable to report what it is responsible for.Observedappeared and decodedAcceptedlegal in current stateCompleteddata committedMonitor sees thisa fact about wiresChecker evaluates thisa fact about stateScoreboard compares thisa fact about outcomethenlater12
Figure 1 — three events, separated in time, each with a different consumer.

3. The Write Flow

A controller issues an activate and the bank opens. It then issues a write command naming a bank and a column, which the device decodes and accepts. The data itself is driven by the controller a fixed number of events later, arriving with its strobe. Only when that data has been transferred and committed is the operation complete. If the write carried an auto-precharge operand the device then closes the bank itself, with no command on the bus announcing it.Write: command first, data later, completion later stillController / PHYDRAM bankACT: bank B, row RWRITE: bank B,column Caccepted — bank isopen (state)data driven on DQwith DQScommitted —operation completeif A10 was high:bank closes itself
Figure 2 — the command promises data the controller has not yet driven.

The three dashed returns are not messages. Chapter 4.1 §4: DDR has no per-command acknowledgement. They are state and outcome the controller must model, and the last one is invisible on the bus entirely — §5's monitoring problem.

And the fourth message is the one that makes writes different. The data is a separate bus activity, owned and timed by Chapter 6.9's discipline. The command committed the controller to producing it, which is why Chapter 6.9 §1 called ownership a scheduling problem rather than a reactive one.

4. RTL — The Acceptance Pipeline

Engineering problem

Separate the three events. Report an observed command; decide acceptance against device state; track the interval to completion; and keep the three visible as distinct outputs so each consumer can attach to the right one.

Classification

SYNTHESIZABLE RTL — an educational acceptance model.

What it represents: the three-stage separation, state-based acceptance for column commands, and in-flight tracking between acceptance and completion.

What it does not represent, and the first item is an architecture decision:

It does not store bank state. Chapter 5.2's ddr_bank_state_table already holds per-bank open/closed state and checks command legality against it. This block takes that state as an input, because duplicating it would create two models of one device that can disagree — which is exactly the failure Chapter 6.7 §9 and Chapter 7.2 §8 both trace. Architecture reuse over code volume.

Also absent: timing legalityWR_DATA_EVENTS is an educational cycle count, not any timing parameter, and Modules 13 and 14 own the real intervals. No data path, no masking, no bus ownership, no burst structure (Module 12), and no scheduler (Module 17).

Interface contract

obs_valid with the decoded command and operands comes from Chapter 7.2's decoder. bank_open_in comes from Chapter 5.2's table. The three stages appear as observed, accepted and completed, with reject_reason classifying refusals.

State

The in-flight record for an accepted-but-incomplete operation, and counters. No bank state — that is the point.

Combinational behaviour

The acceptance decision.

Sequential behaviour

The completion countdown and telemetry.

How to simulate

vlog cmd_accept_pipeline.sv tb_cmd_accept_pipeline.sv then vsim -c tb_cmd_accept_pipeline -do "run -all".

Expected output

A write to an open bank is observed, accepted, and completed WR_DATA_EVENTS later. A write to a closed bank is observed and not accepted, with a reason — and never completed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// COMMAND ACCEPTANCE PIPELINE.
// Classification: SYNTHESIZABLE RTL -- an educational ACCEPTANCE model.
//
// THREE EVENTS, KEPT SEPARATE:
//   OBSERVED   the command appeared and decoded  -- a fact about wires
//   ACCEPTED   it was legal in current state     -- a fact about state
//   COMPLETED  the operation finished             -- a fact about outcome
//
// A monitor attaches to the first, a protocol checker to the second, a
// scoreboard to the third. Collapsing them gives three components that
// each look correct and collectively miss the interesting failures.
//
// IT DOES NOT STORE BANK STATE -- AND THAT IS DELIBERATE.
// Chapter 5.2's ddr_bank_state_table already holds per-bank open/closed
// state and checks legality against it. This block takes that state as an
// INPUT. Duplicating it would create two models of one device that can
// disagree, which is the failure mode this curriculum keeps tracing.
// Architecture reuse over code volume.
//
// WR_DATA_EVENTS IS AN EDUCATIONAL CYCLE COUNT. It is not a timing
// parameter, corresponds to no specification value, and must not be used
// to size anything. Modules 13 and 14 own the real intervals.
//
// ALSO ABSENT: the data path, masking (Chapter 6.11), bus ownership
// (Chapter 6.9), burst structure (Module 12), and any scheduler
// (Module 17). Timing LEGALITY is not modelled at all -- only the
// completion INTERVAL, and only so the three events are separable.
// ─────────────────────────────────────────────────────────────────────────
module cmd_accept_pipeline #(
  parameter int NUM_BANKS = 4,
  // Events from an accepted write command to its data being committed.
  // EDUCATIONAL -- see the header.
  parameter int WR_DATA_EVENTS = 3,
  parameter int ACC_W  = 16,
  parameter int BANK_W = (NUM_BANKS <= 1) ? 1 : $clog2(NUM_BANKS),
  parameter int CNT_W  = (WR_DATA_EVENTS <= 1) ? 1 : $clog2(WR_DATA_EVENTS + 1)
) (
  input  logic                 clk,
  input  logic                 rst_n,

  // ── Stage 1 input: a decoded command 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                 obs_autoprecharge,

  // ── Device state, from Chapter 5.2's table. NOT stored here.
  input  logic [NUM_BANKS-1:0] bank_open_in,

  // ── Stage outputs, deliberately separate.
  output logic                 observed,
  output logic                 accepted,
  output logic                 completed,

  // 0 none, 1 bank closed, 2 bank already open, 3 invalid bank.
  output logic [1:0]           reject_reason,
  output logic                 rejected,

  // Accepted but not yet completed. A write in this state has been
  // committed to by the controller and has not yet delivered its data.
  output logic                 inflight,
  output logic [CNT_W-1:0]     inflight_countdown,

  output logic [ACC_W-1:0]     cnt_accepted,
  output logic [ACC_W-1:0]     cnt_rejected,
  output logic [ACC_W-1:0]     cnt_completed
);

  // ── COMPILE-TIME legality.
  if (NUM_BANKS < 1) begin : g_nb
    initial $fatal(1, "cmd_accept_pipeline: NUM_BANKS must be >= 1");
  end
  // A zero-event completion would make acceptance and completion the same
  // event, which is exactly the collapse this block exists to prevent.
  if (WR_DATA_EVENTS < 1) begin : g_wd
    initial $fatal(1, "cmd_accept_pipeline: WR_DATA_EVENTS must be >= 1");
  end

  localparam logic [1:0] RJ_NONE        = 2'd0;
  localparam logic [1:0] RJ_BANK_CLOSED = 2'd1;
  localparam logic [1:0] RJ_BANK_OPEN   = 2'd2;
  localparam logic [1:0] RJ_BANK_BAD    = 2'd3;

  // ── Bank index range check, using Chapter 5.1's generate pattern rather
  //    than a width cast that truncates for power-of-two counts.
  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

  // ── STAGE 1: OBSERVED. A fact about the wires. Everything the decoder
  //    reported is observed, including commands that will be rejected --
  //    that is the whole point, because a rejected command is the one a
  //    monitor most needs to report.
  assign observed = obs_valid;

  // ── STAGE 2: ACCEPTED. A fact about state.
  //
  //    The legality rules modelled here are the ones Chapter 5.2
  //    established, evaluated against state this block does not own:
  //      column access (RD/WR) requires the bank OPEN
  //      ACT requires the bank CLOSED
  //    Everything else is accepted unconditionally at this abstraction --
  //    refresh and mode-register preconditions are Chapters 7.5 and 7.6.
  logic sel_open;
  assign sel_open = !bank_bad && bank_open_in[obs_bank];

  logic is_column, is_act;
  assign is_column = (obs_cmd == DDR_CMD_RD) || (obs_cmd == DDR_CMD_WR);
  assign is_act    = (obs_cmd == DDR_CMD_ACT);

  logic accept_now;
  always_comb begin
    accept_now    = 1'b0;
    reject_reason = RJ_NONE;

    if (obs_valid) begin
      if (bank_bad && (is_column || is_act)) begin
        reject_reason = RJ_BANK_BAD;
      end else if (is_column && !sel_open) begin
        // A decoded READ or WRITE to a closed bank. Chapter 7.1's
        // question three: valid encoding, valid semantics, ILLEGAL STATE.
        reject_reason = RJ_BANK_CLOSED;
      end else if (is_act && sel_open) begin
        // An ACT to a bank that already holds an open row.
        reject_reason = RJ_BANK_OPEN;
      end else begin
        accept_now = 1'b1;
      end
    end
  end

  assign accepted = accept_now;
  assign rejected = obs_valid && !accept_now;

  // ── STAGE 3: COMPLETED. A fact about outcome.
  //
  //    Only writes are tracked to completion here, because a write's data
  //    is produced by the controller and its commit is the observable
  //    outcome. A read's completion is data ARRIVING, which is Chapter
  //    6.9's ownership problem and Module 10's mechanics -- deliberately
  //    not modelled, rather than modelled badly.
  logic [CNT_W-1:0] cd_q;
  logic             inflight_q;

  assign inflight           = inflight_q;
  assign inflight_countdown = cd_q;

  logic [ACC_W:0] a_sum, r_sum, c_sum;
  always_comb begin
    a_sum = {1'b0, cnt_accepted}  + (ACC_W+1)'(1);
    r_sum = {1'b0, cnt_rejected}  + (ACC_W+1)'(1);
    c_sum = {1'b0, cnt_completed} + (ACC_W+1)'(1);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cd_q          <= '0;
      inflight_q    <= 1'b0;
      completed     <= 1'b0;
      cnt_accepted  <= '0;
      cnt_rejected  <= '0;
      cnt_completed <= '0;
    end else begin
      completed <= 1'b0;

      if (accepted) begin
        cnt_accepted <= a_sum[ACC_W] ? {ACC_W{1'b1}} : a_sum[ACC_W-1:0];
        if (obs_cmd == DDR_CMD_WR) begin
          // The controller has now committed to delivering data. Note this
          // overwrites any in-flight write: modelling one at a time is a
          // simplification, and the limitation is stated rather than
          // silently tolerated.
          inflight_q <= 1'b1;
          cd_q       <= CNT_W'(WR_DATA_EVENTS);
        end
      end

      if (rejected) begin
        cnt_rejected <= r_sum[ACC_W] ? {ACC_W{1'b1}} : r_sum[ACC_W-1:0];
      end

      if (inflight_q && !(accepted && (obs_cmd == DDR_CMD_WR))) begin
        if (cd_q <= CNT_W'(1)) begin
          inflight_q    <= 1'b0;
          cd_q          <= '0;
          completed     <= 1'b1;
          cnt_completed <= c_sum[ACC_W] ? {ACC_W{1'b1}} : c_sum[ACC_W-1:0];
        end else begin
          cd_q <= cd_q - CNT_W'(1);
        end
      end
    end
  end

endmodule

Cycle-by-cycle example

NUM_BANKS = 4, WR_DATA_EVENTS = 3, bank 1 open and bank 2 closed:

CycleCommandobservedacceptedrejectedcompleted
0WR bank 11100
10000
20000
30001
4WR bank 21010
5ACT bank 11010

Cycle 0 and cycle 3 are the same operation, three cycles apart. Accepted at 0, completed at 3. A scoreboard comparing memory contents at cycle 0 would compare before the write happened.

Cycle 4 is observed and not accepted. Bank 2 is closed, so a decoded WRITE to it is a state violation — Chapter 7.1 §1's question three. A monitor reporting only accepted commands would not report this, and it is the most interesting event in the trace.

Cycle 5 is the other direction: an ACT to an already-open bank, which is Chapter 5.2's illegal_activate. Both cycles 4 and 5 are observed, which is what makes the observed stage worth having.

Waveform expectation

§6. Watch accepted and completed at different cycles for one command, and observed high on rejected commands.

Synthesis implication

A counter, a flag, a small comparison tree and three saturating counters. Negligible. In a real controller the acceptance decision is on the command path's critical timing, and the bank-state lookup — which here is an input — is the part that grows with bank count. The three-stage separation costs essentially nothing and is mostly a naming discipline, which is the cheapest kind of architectural clarity.

Corner cases

WR_DATA_EVENTS == 0 does not elaborate, because it would make acceptance and completion the same event — precisely the collapse this block prevents. An invalid bank index is rejected with its own reason rather than being masked. A second write accepted while one is in flight overwrites the in-flight record — a stated simplification, since a real controller pipelines writes. A read is accepted and never reported completed, deliberately: a read's completion is data arriving, which is Chapter 6.9's and Module 10's.

Debugging clues

If commands never complete, check that the countdown decrements on cycles where no new write is accepted — the guard exists so a back-to-back write does not reload the counter forever. If rejected commands are not appearing in a trace, the consumer is attached to accepted rather than observed, which is §2's most common error. If reject_reason says bank-closed for an ACT, the is_column and is_act arms are swapped. If acceptance disagrees with Chapter 5.2's table, the two are being fed different state — which is the exact hazard this block avoids by not owning state.

Limitations

No bank state, by design. No timing legality — only a completion interval, and an educational one. One write in flight, stated above. No read completion. No data, no masking, no ownership, no bursts. And refresh and mode-register preconditions are accepted unconditionally here; Chapters 7.5 and 7.6 own them.

5. Auto-Precharge Makes Completion Invisible

Chapter 7.2 §3 introduced the problem and a write shows it most sharply.

A write with A10 high closes the bank after the data is committed. So the state change happens at completion, not at acceptance — and completion is not a bus event.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   WR with A10 high, bank open
     -> command observed        (visible on the bus)
     -> command accepted        (modelled, not visible)
     -> data transferred        (visible on DQ)
     -> data committed          (modelled)
     -> bank closes             (NOT VISIBLE ANYWHERE)

A monitor tracking state from commands alone will believe the bank is still open, and will then report the next READ to it as legal when the device considers it a violation — or worse, will report a row for it that the device no longer has.

Which means a command monitor cannot be purely reactive. It must schedule the implied state change when it observes the auto-precharge operand, and apply it at the modelled completion. That is the single biggest difference between a bus trace and a command model, and Chapter 7.4 §5 builds the monitor that does it.

6. Three Events in Cycles

cmd_accept_pipeline — acceptance, completion, and two refusals

10 cycles
Ten cycles. A write to an open bank is observed and accepted, and completes three events later. A write to a closed bank is observed but not accepted, reported with a bank-closed reason, and never completes. An activate to a bank that is already open is also observed and rejected. The observed output is high for every decoded command including the rejected ones, which is what allows a monitor to report protocol violations.one write, two eventsone write, two eventsrefused, still observedrefused, stillobservedacceptedacceptedcompleted — 3 events latercompleted — 3 events laterobserved, NOT acceptedobserved, NOT acceptedCKobs_cmdWR------WRACT--WR----obs_bank1------21--1----bank_open2222222222observedacceptedrejectedcompletedt0t1t2t3t4t5t6t7t8t9
Figure 3 — accepted and completed are different cycles; rejected commands are still observed.

bank_open reads 2 throughout — bank 1 open, bank 2 closed, as a two-bit vector. It is an input, from Chapter 5.2's table, which is the architecture point of §4.

Cycles 0 and 3 are one write. accepted at 0, completed at 3. Nothing about the command changed in between — the operation was simply not finished. A scoreboard attached to accepted compares three cycles early.

Cycles 4 and 5 are the important pair. Both are observed and rejected — a write to a closed bank, and an activate to an open one. observed is high for both, which is what lets a monitor report them. A monitor attached to accepted sees neither, and those two events are the protocol violations.

Cycle 7's write is accepted and its completion falls outside the window — a reminder that in-flight operations outlive the trace you happen to be looking at.

Representative educational cycles. The three-event completion interval is a property of this model and is not a timing requirement.

7. Four Assertions Worth Writing

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

// P1 -- THE SEPARATION PROPERTY. Every decoded command is observed,
// including the ones that are rejected. A monitor attached to acceptance
// filters out exactly the events worth reporting, so this property
// protects the observability that makes protocol checking possible.
property p_all_decoded_are_observed;
  @(posedge clk) disable iff (!rst_n)
    obs_valid |-> observed;
endproperty
assert property (p_all_decoded_are_observed);

// P2 -- acceptance and rejection partition the observed commands:
// exactly one, never both, never neither.
property p_accept_xor_reject;
  @(posedge clk) disable iff (!rst_n)
    observed |-> (accepted ^ rejected);
endproperty
assert property (p_accept_xor_reject);

// P3 -- STATE LEGALITY. A column access to a closed bank is never
// accepted. This is Chapter 7.1's question three, and it is the property
// that stops decode from being mistaken for permission.
property p_column_needs_open_bank;
  @(posedge clk) disable iff (!rst_n)
    (accepted && ((obs_cmd == DDR_CMD_RD) || (obs_cmd == DDR_CMD_WR)))
      |-> bank_open_in[obs_bank];
endproperty
assert property (p_column_needs_open_bank);

// And the mirror: an activate to an already-open bank is never accepted.
property p_act_needs_closed_bank;
  @(posedge clk) disable iff (!rst_n)
    (accepted && (obs_cmd == DDR_CMD_ACT)) |-> !bank_open_in[obs_bank];
endproperty
assert property (p_act_needs_closed_bank);

// P4 -- COMPLETION FOLLOWS ACCEPTANCE AND IS NOT THE SAME EVENT. A design
// that asserted both together would collapse the distinction this whole
// chapter is about, and would still satisfy P1 to P3 completely.
property p_completion_is_later;
  @(posedge clk) disable iff (!rst_n)
    completed |-> !accepted;
endproperty
assert property (p_completion_is_later);

property p_completion_needs_inflight;
  @(posedge clk) disable iff (!rst_n)
    completed |-> $past(inflight);
endproperty
assert property (p_completion_needs_inflight);

// P5 -- a rejected command changes nothing. Report, never repair.
property p_reject_starts_nothing;
  @(posedge clk) disable iff (!rst_n)
    (rejected && !accepted) |=> (inflight == $past(inflight));
endproperty
assert property (p_reject_starts_nothing);

P4 is the chapter's property, and the reason is in its second line: P1, P2 and P3 are all satisfied by a design that asserts accepted and completed on the same cycle. That design is internally consistent, passes every legality check, and has destroyed the distinction the chapter exists to teach — so the separation needs its own assertion.

P1 protects observability rather than correctness, which is unusual and worth noticing. It does not make the design more correct; it makes the design reportable. A verification component that cannot see rejected commands cannot do its job, and P1 is what guarantees it can.

What none of them prove. Nothing about timing legality — this block models a completion interval and not a constraint, and Modules 13 and 14 own the real ones. Nothing about the data being correct, which needs a scoreboard attached to completed. Nothing about whether the bank state supplied is itself right, since it is an input — and that is the correct division, because Chapter 5.2 §6's properties govern it.

8. DV — Attaching Components to the Right Event

This is the chapter's practical payoff, and it follows directly from §2.

A passive monitor attaches to observed. It decodes every qualified command, builds a transaction, and reports it — including illegal ones, because those are what the rest of the environment needs to know about. A monitor that filters to legal commands has quietly become a scheduler's echo.

A protocol checker attaches to accepted, and needs a state model to do it. It is the component that says "this READ was issued to a closed bank" — a judgement that requires knowing the state, which no single command carries. Chapter 5.2's table is the model it needs.

A scoreboard attaches to completed. It compares memory contents against expectations, and it must not compare at acceptance — the write has not happened yet. For writes it also needs the masking information, because a masked byte was transferred and not committed.

And all three need the same decoder. Chapter 7.2 §4's block is reusable across the monitor, the checker and the controller's own loopback check — which is an argument for building it once as a standalone module rather than inlining decode into each consumer. Shared decode also means a decoder bug produces consistent wrongness rather than three components disagreeing, which is easier to find.

One caution about X and reset. A monitor must handle an interface in an unknown state — before reset is released, during initialisation, and after any event that invalidates trained state. Reporting decoded commands from an uninitialised interface produces noise that hides real findings, so a monitor's first job is to know whether the interface is live at all.

9. Debugging — A Write Appeared to Succeed and the Data Is Not There

Symptom. A write command was issued, no errors were reported, and the memory contents are unchanged or partially changed.

The three-event model is the diagnostic structure, and the first question follows directly from it: which of the three events actually happened?

Mechanism 1 — the command was observed and not accepted. Inspect: whether the target bank was open at the command. Expected evidence: a rejected command with a bank-closed reason. Discriminator: replay the command against a bank state model. This is first because it costs nothing and because a decoded-but-illegal write is the most common cause — and Chapter 7.2 §1 established the device cannot report it.

Mechanism 2 — accepted, and the data never arrived. Inspect: whether the controller drove data at the committed interval. Expected evidence: an accepted write with no corresponding DQ activity. Discriminator: was there data on the bus? Chapter 6.9 §1 established the command commits the controller to producing data; a controller that accepted the commitment and did not deliver has a scheduling bug, not a command bug.

Mechanism 3 — the data arrived and was masked. Inspect: the data mask lines during the transfer. Expected evidence: partially changed contents, with the unchanged bytes corresponding to asserted mask lines. Discriminator: is the data absent or partial? Chapter 6.11 §2 established the polarity is inverted from intuition and that an error there is all-or-nothing — so partial-with-a-pattern is genuine masking and partial-with-nothing-written is a polarity error.

Mechanism 4 — it was written to the wrong row. Inspect: the bank's open row at the command versus the intended row. Expected evidence: the data present, in a different row. Discriminator: is the data missing, or elsewhere? Chapter 7.2 §8's silent mismatch — and the write succeeded completely, just not where intended.

Mechanism 5 — an auto-precharge closed the bank mid-sequence. Inspect: whether an earlier column command to that bank carried A10 high. Expected evidence: subsequent writes rejected for a closed bank the controller believed was open. Discriminator: look for A10 on prior column commands. §5's invisible state change, and it produces mechanism 1 as a downstream symptom — so finding mechanism 1 is not the end of the investigation.

Discrimination, cheapest first. Ask whether the data is absent, partial, or elsewhere — one observation, and it separates mechanisms 2, 3 and 4 immediately. Then replay the command stream against a bank model, which resolves mechanism 1. Then look for A10 on prior column commands to that bank.

The reasoning lesson. "The write did not happen" is three different claims, and the shape of what is in memory says which. Absent data means the operation stopped somewhere between acceptance and transfer; partial data with a pattern means it completed and was masked; data in the wrong place means it completed perfectly against a state model that was wrong. Those have nothing in common diagnostically, and the distinction costs one look at memory. A three-event model is what makes that question askable — without it, "the write failed" is a single undifferentiated symptom spanning the whole path.

10. Common Misconceptions

"WRITE opens the row it names." Wrong model: a write is self-contained. Why it is tempting: the same reason as for reads — it is how other memory interfaces behave, and a write naturally names its destination. Consequence: no activate before the write, so the data lands in whatever row is open — which may be an unrelated row from an earlier access. The write completes successfully, no error is reported, and data has been destroyed in a row nobody addressed. That is strictly worse than the read case, which only returns wrong data. Correct model: a column command carries no row address. The row must have been opened by a prior ACT, and only the controller's model can verify it is the right one. Prevention: ask what the command carries. No row field means the row is state the command depends on.

"Observed, accepted and completed are the same event." Wrong model: a command either happens or it does not. Why it is tempting: the command is the visible thing, and for many interfaces a request and its effect are effectively simultaneous. Consequence: each verification component attaches to the wrong event and fails differently. A monitor filtering to accepted commands cannot report protocol violations, because an illegal command is observed-and-not-accepted. A checker treating observation as effect has a state model that diverges the first time a command is refused. A scoreboard comparing at acceptance compares before the write happened. Correct model: three events, separated in time. Observed is a fact about wires, accepted a fact about state, completed a fact about outcome — and only the first is directly visible. Prevention: for each verification component, name the event it is responsible for. If they all name the command, at least two are wrong.

"A monitor should only report legal commands." Wrong model: illegal commands are noise to filter. Why it is tempting: a clean trace is easier to read, and illegal commands look like errors in the monitor. Consequence: the interesting events are removed from the trace. An illegal command is exactly what a protocol checker exists to find, and the device will not report it — Chapter 7.2 §1 established that a column command carries nothing the device could check against. Correct model: a monitor reports everything it decodes, and legality is a separate judgement made downstream with a state model. §7's P1 asserts this. Prevention: ask what happens to a protocol violation in your environment. If nothing sees it, the monitor is filtering.

"PRECHARGE writes the data back, so a write is not complete until it is precharged." Wrong model: a precharge is a flush. Why it is tempting: the row buffer resembles a cache, and closing a cache line does write it back. Consequence: a completion model that waits for a precharge that may never come, and a misunderstanding of what precharge is for. It also inverts the actual physics: Chapter 2.6 established that restoration happens as part of the access, not at close time. Correct model: a write's data is committed to the row as part of the write operation. Precharge closes the bank to prepare it for a different rowChapter 7.4's subject — and does not move data anywhere. Prevention: ask what precharge transfers. Nothing; it changes state.

11. Interview Reasoning

"Walk me through the difference between a command being observed, accepted and completed." Observed means it appeared on the interface at a sampling event, was qualified by chip select, and decoded to a semantic operation — a fact about the wires, and the only one of the three that is directly visible. Accepted means the decoded operation was legal in the device's current state, so the device will act on it — a fact about state, which requires a state model since no single command carries it. Completed means the operation finished; for a write, that the data was transferred and committed — a fact about outcome. They matter because each verification component belongs to a different one: a monitor to observation, a protocol checker to acceptance, a scoreboard to completion. Attaching them all to the command, which is the easiest event to see, gives three components that each look correct and collectively miss the protocol violations.

"Why should a monitor report illegal commands?" Because an illegal command is observed and not accepted, and it is precisely the event the rest of the environment needs. The device will not report it — a column command to a closed bank carries nothing the device could check against, so there is no error signal — which means the monitor is the only thing that can surface it. A monitor that filters to legal commands has removed the bugs from the trace and become an echo of what the scheduler intended. Legality is a separate downstream judgement made with a state model, not a filter on observation.

"Is WRA a different command from WR?" No — it is a WRITE with address bit A10 sampled high, exactly as RDA is a READ with A10 high. Same encoding, same operation, one operand different. The effect is that the device precharges the bank itself after the data is committed. What makes it worth understanding at the command level is that the state change happens at completion rather than at acceptance, and completion is not a bus event — so the bank closes with nothing on the bus announcing it. A monitor or controller tracking state from commands alone must schedule the implied change when it sees the operand and apply it later, which is the main thing separating a command model from a bus trace.

"A write was issued, no errors were reported, and the data is not in memory. How do you narrow it down?" By first looking at what is in memory, because the shape answers the question. Absent data means the operation stopped between acceptance and transfer — most likely the command was observed and not accepted because the bank was closed, which the device cannot report. Partial data with a pattern means it completed and some bytes were masked, which is the data mask working or its polarity inverted. Data present but in a different row means it completed perfectly against a state model that was wrong — the silent mismatch, and the write succeeded, just not where intended. Those three have essentially nothing in common diagnostically and the distinction costs one look at memory. It is also worth checking whether an earlier column command to that bank carried A10 high, because auto-precharge closes the bank invisibly and produces a rejected-for-closed-bank symptom downstream of the real cause.

"Why does a write command commit the controller to something?" Because the data does not accompany the command. The command fixes the direction and the timing of a future bus activity, and the controller must drive the data at the committed interval — it cannot decide later not to. That is why bus ownership is a scheduling problem rather than a reactive one: by the time the controller would notice it wants the bus for something else, the commands governing the next several data windows have already been issued. It is also why an accepted write with no corresponding data activity is a controller scheduling bug rather than a command bug, and the two look identical until you check whether data appeared.

12. Engineering Exercise

Educational cycle counts; verified operand behaviour; no timing parameters implied.

1. A WRITE is issued to a bank that is closed. Which of the three events occur? Observed only. It decoded, so it was observed; it is a state violation, so it was not accepted; and it never completes. The device reports nothing — the monitor is the only thing that can surface it.

2. A scoreboard compares memory contents on the cycle a write is accepted. What does it see, and is the scoreboard wrong? It sees memory before the write — the data has not been transferred yet. The scoreboard is attached to the wrong event: it belongs on completed. It is not a bug in its comparison logic, which is why this class of error survives review.

3. §4's block takes bank_open_in as an input rather than storing bank state. Give two reasons. First, it already existsChapter 5.2's table holds it, and duplicating it creates two models of one device that can disagree. Second, disagreement between them would be invisible: both would be internally consistent and would diverge silently, which is the failure this curriculum keeps tracing. Architecture reuse is the point, not code volume.

4. A write with A10 high is accepted. At which of the three events does the bank close, and what does a bus trace show? At completion, after the data is committed. A bus trace shows nothing — no command announces it. A monitor must schedule the state change when it observes the operand and apply it at modelled completion, which is §5's point.

5. §7's P4 asserts that completed and accepted are never simultaneous. Construct a design that passes P1–P3 and fails P4, and say what it has lost. A design that asserts both on acceptance — treating the write as instantaneous. It passes every legality and partition property and has collapsed the three events into two. What it loses is the ability to attach a scoreboard correctly, and it will report writes as complete before their data exists.

6. Memory contains the written data, in a row the requester did not ask for. Which event failed? None of them. Observed, accepted and completed all succeeded. The failure was in the state model — the bank was open with a different row than the controller believed, and Chapter 7.2 §1 established the command carries no row for the device to check. This is the case the three-event model does not catch, and it is why the controller's open-row table is a correctness structure rather than an optimisation.

13. Summary

WRITE says: accept data for column C of the row currently open in bank B. Like READ it carries no row address, so it opens nothing, requires a prior ACT, and the device cannot verify the row is the intended one. WRA is a WRITE with A10 high — same encoding, and the bank closes by itself afterwards.

The asymmetry is that a write's data is produced by the controller and arrives after the command, which makes three events impossible to conflate.

Observed — it appeared and decoded, a fact about the wires, and the only one directly visible. Accepted — it was legal in current state, a fact about state, requiring a model. Completed — the operation finished, a fact about outcome.

Each verification component belongs to a different one. A monitor to observation, and it must report illegal commands because those are observed-and-not-accepted and the device will never report them. A protocol checker to acceptance, with a state model. A scoreboard to completion — comparing at acceptance compares before the write happened.

And §4's block takes bank state as an input rather than storing it, because Chapter 5.2 already owns it and two models of one device diverge silently.

Auto-precharge makes completion invisible. The bank closes at completion, and completion is not a bus event — so a monitor must schedule the implied change when it observes the operand rather than reacting to a command that never comes.

And the diagnostic payoff is that "the write did not happen" is three different claims. Absent data means it stopped between acceptance and transfer; partial with a pattern means it completed and was masked; present elsewhere means it completed perfectly against a wrong state model. Those share no diagnostic steps, and one look at memory distinguishes them.

14. What Comes Next

Chapter 7.4 takes the command that undoes ACT, and it has two properties nothing so far has had.

Its scope is an operand. Verified: A10 high makes a precharge apply to all banks rather than one — the same bit that means auto-precharge on a column command, which is Chapter 7.2 §3's dual meaning arriving from the other side.

And it is the command a monitor most struggles with. An all-bank precharge changes state in banks it never names; an auto-precharge changes state with no command at all. 7.4 is therefore where this module builds its DV command monitor — the component that has to reconstruct device state from a command stream that does not fully describe it.

Return to Read for the decoder and the dual-purpose operand, Activate for the command model, Banks for the state acceptance is judged against, or DM and DQ for how a write's data is masked and owned. 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.