Skip to content
VLSI Mentor

DDR · Module 8

Column Address

A column command carries an incomplete address, and that is not an oversight. The row comes from state deposited by an earlier activate, the low bits never appear at all, and the location must be reconstructed from command history.

Chapter 8.1 extracted the row and handed it to ACTIVATE, and ended on a claim that should have felt slightly alarming: the row travels once and never again.

This chapter is where that claim is paid for. A column command carries a column and a bank, and:

Those operands do not identify a location.

Not incompletely — not at all. Column 2 of bank 1 is not an address. It becomes one only when combined with something the command does not contain, and that something lives in the device rather than on the wire. So the question is:

If a column command carries an incomplete address, what supplies the rest — and why was DRAM built this way?

The answer makes address interpretation temporal. That is the idea this module exists to deliver, and it is genuinely unlike anything in software addressing.

1. What a Column Address Selects

A column address selects which part of the currently open row reaches the interface.

Every word of that is load-bearing, and the contrast with Chapter 8.1 is exact:

Row addressColumn address
Selectswhich row enters the sense amplifierswhich part of the sensed row leaves the device
Carried byACTIVATEREAD / WRITE
Acts onthe arraythe row already held
Changes device stateyes — opens a rowno — the row stays open
Repeated?once per rowas often as you like

A column access does not touch the array. Chapter 3.3 established that the row is already sitting in the sense amplifiers; a column command selects from what is held there. That asymmetry is why a row is opened once and read many times, and it is the entire economic basis of the row buffer.

So the column is a coordinate within something. And that something is not named by the command.

2. Why One Command Cannot Carry the Whole Location

This is worth deriving rather than stating, because the derivation explains a structure that otherwise looks arbitrary.

Start with the array. A bank with 65,536 rows and 1,024 columns needs 16 bits to name a row and 10 to name a column: 26 bits of internal selection, plus bank and bank-group bits on top.

Now count the cost of carrying them all at once. Every bit of address presented simultaneously needs its own external pin. A pin is not free — it costs package area, board routing, a driver and receiver on both ends, and power on every transition. Chapter 6.3 and Chapter 6.6 already showed this pressure operating on the command interface: DDR spent years reducing the number of dedicated command pins, to the point where DDR4 multiplexed three former command pins into address bits.

An interface that spent 26+ pins on address would be spending them on a resource that is idle most of the time. Here is the asymmetry that makes the design work:

  • The row changes rarely. A workload with any locality reads many columns from one row.
  • The column changes constantly. Every access to the open row names a different one.

So the two fields have completely different duty cycles — and paying full pin cost for both is paying twice for something used once.

This is address multiplexing, and it is not a legacy compromise. It is worth saying plainly, because the usual framing is that multiplexing exists because old DRAM was slow and we are stuck with it. The economics have not changed. Pins are still expensive, the duty-cycle asymmetry between rows and columns is still real, and a modern device would make the same choice. What has changed is the encoding — Chapter 6.4 traced how the mechanism moved from dedicated strobes to a multiplexed command field — but the architecture is the same one, for the same reason.

And it has a consequence the rest of the chapter unpacks:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  effective location  =   bank operand        (from the column command)
                      +   open row in state   (from the earlier ACTIVATE)
                      +   column operand      (from the column command)

Two of the three come from the command. One comes from history.

An activate command carries a bank operand and a row operand. The row operand is deposited into the bank's state, where it is held. Later, a column command carries a bank operand and a column operand but no row. The effective location is assembled from the column command's bank and column operands together with the row held in state, so the complete location exists in neither command on its own.ACTIVATEbank + rowBank stateholds the rowEffective locationbank · row · columnColumn commandbank + columnNo row operandnowhere to put oneCommand historysupplies the rowdepositsrowneedscompletesbank/col12
Figure 1 — The effective location is assembled from two commands and the state between them.

3. Byte Offset Is Not a Column

Now the second thing a column command does not carry, and it catches people who have fully absorbed the first.

A column access does not move one byte. It moves a block.

Module 4 derived the size of that block twice, and the result is worth pausing on:

GenerationInterface widthBurstBytes per column access
DDR464-bit moduleBL864 bytes
DDR532-bit sub-channelBL1664 bytes

Verified, and Chapter 4.7 explained the convergence: DDR5 halved the channel width and doubled the burst precisely so that access granularity would stay at 64 bytes, because 64 bytes is a cache line and a device that moved 128 would waste half of every transfer.

So one column command moves 64 bytes. Which means:

The low 6 bits of a byte address select a byte inside a single transfer — and they are not part of any DDR operand. There is no command that can ask for byte 37 of a column access. The device moves all 64 bytes or none.

Where do the low bits go, then? Somewhere above DDR. A byte-granular write is handled by data masking — which Chapter 6.11 covered, and note what it is: a data-path mechanism, on the DQ side, not an address field. A byte-granular read is handled by the requester extracting what it wanted from the 64 bytes it received. Either way the resolution happens outside the address.

This is why Chapter 8.1's extractor reports offset_nonzero rather than folding it away: those bits carry real information that no DDR command can express, and a controller must answer for them somewhere.

4. RTL — Resolving the Effective Location

The engineering problem

A controller holds a system address it wants to read. Before issuing a column command it must answer three questions that Chapter 8.1's extractor cannot: which column does this address name, is the row it needs already open, and if it is, what location will the command actually reach?

The third question is the subtle one. The location a column command reaches is determined by state the command does not carry — so a controller that does not model that state cannot predict what its own command will do.

Why hardware needs it

This is the decision point for every access: issue a column command now, or activate first. It sits on the request path, and it must consume the bank state rather than keep its own, or it will drift from it.

Classification

SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.

The field layout is the same EDUCATIONAL ADDRESS MAP as Chapter 8.1 §4, continued deliberately so the two chapters compose. It is not a JEDEC-mandated universal DDR address map.

What it models

Extraction of a column command's operands from a system address, and resolution of the effective location by combining those operands with bank state supplied from outside.

What it does NOT model

Bank state itselfChapter 5.2's ddr_bank_state_table owns it and is the source of the bank_open_in and open_row_in ports. This block stores nothing, which is the point: a second copy of that state would drift from the first, and Chapter 7.4 built a monitor whose whole purpose was to expose exactly that. Also not modelled: the burst (Modules 10 to 12), the data path, scheduling or reordering (Module 17), and timing legality (Modules 13 and 14) — this block says a column command is resolvable, never that it is legal to issue right now.

Interface and parameter contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// col_operand_resolve
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// THE FIELD LAYOUT IS AN EDUCATIONAL ADDRESS MAP, CONTINUED FROM CHAPTER
// 8.1. IT IS NOT A JEDEC-MANDATED UNIVERSAL DDR ADDRESS MAP.
//
// MODELS: a column command's operands extracted from a system address, and
// the EFFECTIVE LOCATION that command would reach -- which depends on state
// the command does not carry.
//
// DOES NOT MODEL: bank state (Chapter 5.2's ddr_bank_state_table owns it
// and drives the inputs here -- nothing is stored), the burst (Modules
// 10-12), the data path (Chapter 6.9), scheduling (Module 17), or timing
// legality (Modules 13, 14). "Resolvable" NEVER means "legal to issue now".
//
// GENERATION: GENERATION-NEUTRAL CONTROLLER MODEL.
// ─────────────────────────────────────────────────────────────────────────
module col_operand_resolve #(
  parameter int ADDR_W    = 32,

  // Bytes moved by ONE column access (Section 3). 6 gives 64 bytes, the
  // verified DDR4 and DDR5 figure. Bits below this reach no DDR operand.
  parameter int OFFSET_W  = 6,

  // COUNTS, not widths -- so the power-of-two requirement below becomes a
  // checkable condition rather than an unstated assumption.
  parameter int NUM_COLS  = 16,
  parameter int NUM_BANKS = 4,

  parameter int ROW_W     = 16,

  parameter int COLUMN_W = (NUM_COLS  <= 1) ? 1 : $clog2(NUM_COLS),
  parameter int BANK_W   = (NUM_BANKS <= 1) ? 1 : $clog2(NUM_BANKS)
) (
  input  logic                   req_valid,
  input  logic [ADDR_W-1:0]      sys_addr,

  // ── State from Chapter 5.2's table. INPUTS. Read, never written.
  input  logic [NUM_BANKS-1:0]   bank_open_in,
  input  logic [ROW_W-1:0]       open_row_in [NUM_BANKS],

  // ── Layer C: what a column command would carry. Note the absence of a
  //    row output here -- a column command has no row operand, and
  //    inventing one would be the chapter's central misconception in RTL.
  output logic [COLUMN_W-1:0]    col_operand,
  output logic [BANK_W-1:0]      bank_operand,

  // ── The row the ADDRESS wants. Derived from the map, NOT sent with a
  //    column command. It exists only inside the controller.
  output logic [ROW_W-1:0]       addr_row,

  // ── The row the DEVICE would use: whatever the bank currently holds.
  //    THIS IS THE CHAPTER. It comes from state, not from the command.
  output logic [ROW_W-1:0]       effective_row,
  output logic                   effective_row_known,

  // ── Why a column command cannot be resolved yet, reported separately
  //    because the two cases need different responses: a closed bank needs
  //    an activate, a wrong row needs a precharge first.
  output logic                   bank_closed,
  output logic                   row_needs_change,
  output logic                   col_cmd_resolvable,

  // Low-order address bits that no DDR operand can express (Section 3).
  output logic                   offset_dropped
);

  // ── Field positions, low to high: offset, column, bank, row.
  localparam int OFF_LSB  = 0;
  localparam int COL_LSB  = OFF_LSB  + OFFSET_W;
  localparam int BANK_LSB = COL_LSB  + COLUMN_W;
  localparam int ROW_LSB  = BANK_LSB + BANK_W;
  localparam int MAP_W    = ROW_LSB  + ROW_W;

  // ── Elaboration legality.
  if (OFFSET_W < 1) begin : g_ow
    initial $fatal(1, "col_operand_resolve: OFFSET_W must be >= 1");
  end
  if (ROW_W < 1) begin : g_rw
    initial $fatal(1, "col_operand_resolve: ROW_W must be >= 1");
  end
  if (MAP_W > ADDR_W) begin : g_fit
    initial $fatal(1, "col_operand_resolve: fields need more bits than ADDR_W");
  end

  // ── THE POWER-OF-TWO REQUIREMENT, and why it is fatal rather than a
  //    runtime flag. A bit-field map is a bijection between an address
  //    range and a field tuple. With 12 columns, a 4-bit field has 4
  //    unreachable encodings: either some addresses name columns that do
  //    not exist, or the map is not a bijection and two addresses collide.
  //    Neither is repairable at runtime, so neither may elaborate.
  if (NUM_COLS != (1 << COLUMN_W)) begin : g_cpow
    initial $fatal(1, "col_operand_resolve: NUM_COLS must be a power of two for a bit-field map");
  end
  if (NUM_BANKS != (1 << BANK_W)) begin : g_bpow
    initial $fatal(1, "col_operand_resolve: NUM_BANKS must be a power of two for a bit-field map");
  end

  // ── Extraction. Constant part-selects: wiring.
  assign col_operand  = sys_addr[COL_LSB  +: COLUMN_W];
  assign bank_operand = sys_addr[BANK_LSB +: BANK_W];
  assign addr_row     = sys_addr[ROW_LSB  +: ROW_W];

  assign offset_dropped = req_valid && (|sys_addr[OFF_LSB +: OFFSET_W]);

  // ── Resolution against state. One indexed read of each state array.
  //    bank_operand is guaranteed in range by the power-of-two guard
  //    above: the field is exactly wide enough and no wider.
  logic sel_open;
  logic [ROW_W-1:0] sel_row;

  always_comb begin
    sel_open = bank_open_in[bank_operand];
    sel_row  = open_row_in[bank_operand];
  end

  // The effective row is the HELD row whenever one is held. It is NOT
  // addr_row, and the difference between them is the whole hazard.
  assign effective_row       = sel_row;
  assign effective_row_known = req_valid && sel_open;

  assign bank_closed      = req_valid && !sel_open;
  assign row_needs_change = req_valid && sel_open && (sel_row != addr_row);

  // Resolvable: the bank holds the row this address wants, so a column
  // command issued now would reach the intended location. NOT a statement
  // about timing -- Modules 13 and 14 own whether it may be issued yet.
  assign col_cmd_resolvable = req_valid && sel_open && (sel_row == addr_row);

endmodule

State, and where it actually lives

None here. All of it in Chapter 5.2. This is the §11 discipline of Module 7 applied again, and it is worth being explicit about why it matters more here than anywhere else in the module: this block's entire subject is state it does not own. If it kept a copy, the copy could disagree with the table, and a controller with two opinions about which row is open will eventually act on the wrong one.

Combinational behaviour

Three constant slices, one OR reduction, two indexed array reads, and three comparisons. The indexed reads synthesise to multiplexers — NUM_BANKS-to-1, which at realistic bank counts is small.

Sequential behaviour

None. The time dimension enters through the state inputs, not through anything this block holds — which is exactly the right place for it, because the sequencing belongs to whoever issues the commands.

Bit-level derivation

At the defaults (OFFSET_W = 6, NUM_COLS = 16, NUM_BANKS = 4, ROW_W = 16), the layout is Chapter 8.1's:

FieldBitsNote
offset5:064 bytes per column access — reaches no operand
column9:616 columns, 64 bytes apart in the address
bank11:104 banks
row27:1265,536 rows

One row in one bank therefore spans 1 KB of system address: 16 columns × 64 bytes. That number is worth remembering because it is what makes §6's boundary arithmetic work.

Cycle example

Take two system addresses that produce identical column-command operands:

0x001044800x00105480
addr_row0x01040x0105
bank_operand11
col_operand22

The column commands for these two addresses are indistinguishable. Same bank, same column, and neither carries a row. What separates them is the activate that came first — and if the wrong one came first, the command is still perfectly legal and reads the wrong place. §5 puts that on a waveform.

How to simulate, and expected output

Drive bank_open_in and open_row_in to represent a state, then present addresses:

State of bank 1sys_addrcol_cmd_resolvablebank_closedrow_needs_changeeffective_row
closed0x00104480010not known
open, row 0x01040x001044801000x0104
open, row 0x01040x001044C01000x0104
open, row 0x01050x001044800010x0105
open, row 0x01040x001044811000x0104

Row 3 and row 5 are the two that teach. Row 3 changes the column and resolves identically — the row-reuse case. Row 5 changes the address by one byte, resolves identically, and asserts offset_dropped: the same column command, for a different requested byte.

Synthesis implication

Slices and comparators, plus two NUM_BANKS-to-1 multiplexers reading the state arrays. The comparator on ROW_W bits is the widest element. In a real controller this resolution is usually computed a cycle ahead of the issue decision, because it feeds arbitration rather than a pin.

Parameter corner cases

NUM_BANKS == 1 gives BANK_W == 1 through the guard and elaborates: a single-bank device whose bank field has one legal value. NUM_COLS == 1 likewise. Non-power-of-two counts do not elaborate — see the guard's comment; this is the one place in the module where a non-power-of-two count is not merely awkward but structurally impossible, because a bit-field map has to be a bijection. MAP_W > ADDR_W does not elaborate. And note the guard makes bank_open_in[bank_operand] safe by construction: the field is exactly wide enough to index the array, so there is no out-of-range case to report.

Reset behaviour

No state, no reset. But the state feeding it has one, and it matters here: Chapter 7.4 §4 established that after reset a monitor should treat banks as unknown rather than closed. A controller's own table may legitimately start at closed, because it knows it has issued nothing. The same signal means different things to the two components, and this block simply believes whatever it is told.

Debugging clues

col_cmd_resolvable never asserting for a whole region means addr_row disagrees with the row actually activated — a map mismatch between whatever issued the activate and this block. row_needs_change asserting on traffic you expected to be sequential means the map is putting less address space in a row than you thought. effective_row differing from addr_row at the moment a command is issued is the precise signature of the bug in §9.

Limitations

One layout. No bank-group field (that is Chapter 8.4's). No rank or channel (Chapter 8.5's). It resolves one address at a time and expresses no opinion about ordering. And it cannot detect a wrong map — it will resolve confidently against whatever layout it was compiled with, which is why Chapter 8.6 adds a checker that compares two.

5. Temporal Reconstruction, in Cycles

col_operand_resolve — the same command, two locations

10 cycles
Ten cycles. An activate opens row 0x0104 in bank 1, and the bank state input then holds that row. Two column commands follow, both resolving against the held row, the first at column 2 and the second at column 3. A second activate then opens row 0x0105 in the same bank, changing the held row. A column command at column 2 follows, carrying operands identical to the earlier one at cycle 2, but the effective row is now 0x0105 so the location reached is different. A final column command targets bank 2, which is closed, so it is not resolvable and the bank-closed output asserts.row 0104 heldrow 0104 heldrow 0105 heldrow 0105 heldcol 2 of row 0104col 2 of row 0104col 2 of row 0105col 2 of row 0105bank 2 closedbank 2 closedCKcommandACT--RDRD--ACTRD--RD--bank_operand1--11--11--2--col_operand----23----2--5--b1 open (5.2)b1 row (5.2)----010401040104010401040105010501050105effective_row----0104010401040104010401050105--------resolvablebank_closedt0t1t2t3t4t5t6t7t8t9
Figure 2 — Identical column operands at cycles 2 and 6, reaching different rows.

Compare cycle 2 and cycle 6 and read only the command rows. command is RD, bank_operand is 1, col_operand is 2. They are identical. Nothing on those three rows distinguishes them.

Now read effective_row. At cycle 2 it is 0x0104; at cycle 6 it is 0x0105. Two identical commands, two different locations — and the only thing that separates them happened at cycle 5.

This is what "the address is distributed across command history" means in practice, and it has three consequences that the rest of the module keeps returning to:

A monitor cannot interpret a column command in isolation. It must have watched the activates. A monitor attached at cycle 6 with no history knows the column and the bank and cannot know the row at all.

An assertion on the column command cannot catch a wrong row. Both commands are well-formed. There is no property of the command itself that distinguishes the correct case from the incorrect one, because the difference is not in the command.

Cycles 3 and 4 are the payoff. The row stays open across a column command, so cycle 3's read reuses it. That reuse is the entire reason for the architecture in §2, and Module 9 is where its cost and value are quantified.

Cycle 8 shows the other failure. Bank 2 was never activated, so there is no row to resolve against and effective_row is not known. A real device receiving a column command here does something the specification does not promise — which is why the controller must never send one.

Representative educational cycles. The one-cycle spacing between commands implies no timing requirement whatsoever; real minimum intervals are Modules 13 and 14'.

6. Why Adjacent Addresses May Not Increment the Column

Two separate reasons, and they are often confused with each other.

Reason one: granularity. From §3, consecutive columns are 64 bytes apart in the address. Adding 1 to an address usually changes no column at all. This has nothing to do with the map — it is a consequence of how much data one column command moves.

Reason two: field rollover. When the column field saturates, the next address moves a different field — and which one depends entirely on the map. Under this chapter's layout, with 16 columns of 64 bytes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  0x...0400   column 0, bank 1     ← column field just rolled over
  0x...03C0   column 15, bank 0    ← last column of bank 0's row
  0x...0380   column 14, bank 0

Reading upward: 1 KB of contiguous address fills all 16 columns of one row in bank 0, and then the bank field increments, because in this layout bank sits directly above column.

So a sequential 1 KB walk stays in one row; the next byte is in a different bank. Under a different map, that same boundary could move to a different row in the same bank instead. Chapter 8.3 builds exactly that comparison, and it is the reason field order is a design decision rather than a detail.

7. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1 -- the effective row comes from STATE, never from the address. This
// is the chapter as a property, and it fails the moment someone "helpfully"
// wires effective_row to addr_row to make a test pass.
property p_effective_row_is_the_held_row;
  @(posedge clk)
    (req_valid && bank_open_in[bank_operand])
      |-> (effective_row == open_row_in[bank_operand]);
endproperty
assert property (p_effective_row_is_the_held_row);

// P2 -- resolvable means the held row is the wanted row. The contract an
// issuer relies on: if this asserts, a column command reaches the location
// the address named. Note what it does NOT say -- nothing about timing.
property p_resolvable_means_row_matches;
  @(posedge clk)
    col_cmd_resolvable
      |-> bank_open_in[bank_operand]
          && (open_row_in[bank_operand] == addr_row);
endproperty
assert property (p_resolvable_means_row_matches);

// P3 -- the three outcomes are mutually exclusive and cover every valid
// request. Exhaustiveness matters because a consumer chooses its next
// action from these three, and a request in none of them would stall
// silently while every individual signal looked reasonable.
property p_outcomes_partition_the_request;
  @(posedge clk)
    req_valid |-> $onehot({col_cmd_resolvable, bank_closed, row_needs_change});
endproperty
assert property (p_outcomes_partition_the_request);

// P4 -- the column operand is insensitive to the offset bits. A column
// field placed one bit too low would overlap the offset, and then two
// addresses inside ONE transfer would name two different columns.
property p_column_ignores_offset_bits;
  @(posedge clk)
    ((sys_addr >> OFFSET_W) == ($past(sys_addr) >> OFFSET_W))
      |-> (col_operand == $past(col_operand));
endproperty
assert property (p_column_ignores_offset_bits);

What these prove. P1 is the one worth the most: it fixes the direction of the dependency, and it is the property that a well-meaning shortcut breaks. P2 gives the issuer a contract. P3 proves the outcome set is a genuine partition, which is what makes it safe to build a decision on. P4 catches the specific slicing error that §3's misconception produces in RTL.

What they do not prove. Nothing here proves the map is right. Every one of these properties holds under a completely wrong layout, because they check internal consistency, not correspondence with whatever the rest of the system believes. That check requires a second, independent model — which is Chapter 8.6 §4's round trip. They also prove nothing about the device: open_row_in is an input, so P1 proves this block uses the state it was given, not that the state is true. And no property here is about legality of issue. Timing is Modules 13 and 14'; state legality on the device side is Chapter 7.3 §4's.

8. DV — The Reconstruction Problem

Chapter 8.1 §8 introduced the address reconstructor. This chapter is where it becomes genuinely hard, because reconstruction now requires memory.

A monitor observing a column command has, on the wire: the operation, a bank, a column, and flags. To produce a system address it needs:

NeededSourceFailure if wrong
bank operandthe commandwrong bank in the reconstruction
column operandthe commandwrong column
the open rowits own model of activate historywrong row — and nothing on the wire disagrees
the mapconfigurationevery field plausible, address wrong
the offsetunrecoverableaddress known only to 64-byte granularity

Two rows of that table deserve attention.

The open row is reconstructed, not observed. Which means the monitor's bank model is part of the address decode path, not a separate checker. If it is wrong — because it missed an activate, or mis-decoded one, or started without knowing the initial state — then every subsequent column command is attributed to the wrong row, and no assertion on the command interface will fire, because the commands are all well-formed. Chapter 7.4 built the monitor whose known bits exist precisely for this, and this is the consequence those bits were protecting against.

The offset is genuinely unrecoverable. A monitor can reconstruct an address only to the granularity of one column access — 64 bytes. It cannot know which byte the requester originally wanted, because that information never crossed the interface. A scoreboard must therefore compare at transfer granularity, or obtain the byte-level intent from the requester side rather than from DDR.

Which gives the practical rule: attach the scoreboard where the information is. Comparing 64-byte blocks against reconstructed block addresses is sound. Comparing single bytes against reconstructed byte addresses is not, because half of that address was invented.

9. Debugging — Right Column, Wrong Row

Symptom. Reads return data belonging to a different row. The commands are legal, well-formed, and in a legal order. Column numbers are correct. The bank is correct.

Candidate mechanisms.

  1. The activate that preceded the read opened a different row than the read's address wanted — an ordering or scheduling defect, not an addressing one.
  2. addr_row is extracted from the wrong bits, so the activate opened the wrong row in the first place.
  3. The controller's bank-state model has drifted from the device, so it believed a row was open that was not — Chapter 5.2 §4's hazard.
  4. An auto-precharge closed the row earlier than the controller expected, so a later column command met a closed bank or a newly opened different row.
  5. The monitor is wrong and the hardware is right.

Evidence to collect. For each failing read: the read's operands, the most recent activate to that bank and its row operand, the controller's believed state at issue, and the system address the request came from. The activate is the critical item, and it is the one a trace filtered to "failing transactions" will not contain.

Discriminator.

  • Does addr_row for the failing address equal the row operand of the preceding activate? If yes, extraction is correct and the fault is ordering or state — mechanisms 1, 3, 4. If no, extraction is the fault — mechanism 2.
  • If extraction is correct, compare the controller's believed open row against the last activate actually observed on the interface. A disagreement is mechanism 3 and is a state-tracking bug. Agreement points at mechanism 1: the right row was believed open, and the wrong activate had established it.
  • Check whether the failing reads follow a command carrying auto-precharge. Mechanism 4 has a signature: failures cluster after a command whose effect was scheduled rather than sent, which is Chapter 7.4 §5's invisible state change.
  • Reconstruct the address from the observed operands plus the observed activate history and compare against the request. If that reconstruction matches the request, everything in the address path is right and mechanism 5 or the data path is left.

Responsible layer. Mechanism 2 is layer B, in this chapter. Mechanisms 1, 3 and 4 are not addressing bugs at all — they are state tracking and scheduling, and they belong to Module 9 and Module 17. That distinction is the value of the discriminator, because all four present identically at the scoreboard.

Fix. For mechanism 2, correct the slice and enable Chapter 8.6's round trip. For the others, the fix is outside this chapter — but arriving there quickly is this chapter's contribution.

10. Common Misconceptions

"A READ command contains the complete address."

Why it is tempting: every addressed operation the learner has met carries its full address, and the command does contain bits in address positions.

Concrete failure: a monitor is written to decode a location from a single column command. It produces a row number from whatever bits sit in those positions — which on a DDR4 read includes the auto-precharge flag — and reports confident nonsense.

Correct model: a column command carries bank and column. The row is state. §5's waveform shows two identical commands reaching different locations.

Prevention: make the monitor's bank model a declared dependency of its address decode, not an optional add-on.

"Byte offset and DDR column are interchangeable."

Why it is tempting: both are "the low part of the address," and both are small numbers.

Concrete failure: a map is built with the column field at bit 0. Sixty-four consecutive addresses then name 64 different columns, the field overlaps what should be the offset, and the device is asked to move 64 bytes per column when the map assumed 1. Data aliases at a 64-byte interval.

Correct model: one column access moves 64 bytes on both DDR4 and DDR5. The offset selects a byte inside a transfer and reaches no operand.

Prevention: derive the column field's LSB from the access granularity — bytes per column access — rather than choosing it.

"Address multiplexing exists only because old DRAM was slow."

Why it is tempting: it is presented historically, alongside genuinely obsolete things.

Concrete failure: an engineer expects a modern generation to have "fixed" it, and designs a model in which a column command carries a full address. It works against no real device.

Correct model: multiplexing is a pin-economics answer to a duty-cycle asymmetry, and both still hold. §2 derives it from current constraints, not history.

Prevention: re-derive the pin cost for a modern device — it comes out the same way.

"The row address is sent again with every column command."

Why it is tempting: it would be simpler, and it is how a flat address behaves.

Concrete failure: a controller model re-sends the row and cannot explain why a column command has no field for it — then appropriates the auto-precharge bit to carry one.

Correct model: the sense amplifiers hold one row. A second row number has nowhere to go and no meaning.

Prevention: §2's derivation. Once the reason is clear the structure stops feeling arbitrary.

"Sequential addresses stay in the same column region."

Why it is tempting: sequential access feels like it should walk one field at a time.

Concrete failure: a performance model assumes a sequential stream produces long runs of column commands to one bank. Under this chapter's layout it produces 1 KB runs and then switches bank — a different command mix entirely.

Correct model: the stride at which sequential traffic leaves a row is NUM_COLS × bytes-per-column-access, and what it moves to next depends on the map.

Prevention: compute the number. Chapter 8.3 shows it changing.

"A correct column operand means a correct access."

Why it is tempting: the column is the field the command actually carries, so checking it feels like checking the address.

Concrete failure: an assertion suite verifies every column operand against the request and passes, while reads return data from the wrong row for a whole workload.

Correct model: the operand is one third of a location. The other two thirds are the bank and the state, and only one of those is on the wire.

Prevention: assert on the reconstructed effective location, not on the operand. That is what P1 and P2 in §7 are for.

11. Interview Reasoning

"Why doesn't a READ command carry a row address?"

Because the row is not a parameter of a read — it is the bank's state, deposited by the activate that opened it, and the sense amplifiers hold exactly one. Architecturally it is a duty-cycle argument: the row changes rarely and the column changes constantly, so the interface sends the rare field once and stores it in the device, and spends its per-command bandwidth only on the field that varies. Paying pin cost for both simultaneously would be paying twice for something used once.

"Two column commands on a waveform are bit-identical. Do they access the same location?"

Not necessarily, and you cannot tell from the commands. You need the activate history for that bank. If an activate between them changed the open row, identical commands reach different rows. This is the single most important consequence of multiplexing for a verification engineer, because it means no property of a column command alone can validate its location.

"What information does a monitor need to reconstruct a system address from a column command?"

Four things, and one it can never have. It needs the operands from the command, its own model of activate history for that bank, and the controller's address map from configuration. It cannot recover the low-order offset bits, because they never cross the interface — so reconstruction is exact only to the granularity of one column access, 64 bytes on the generations we have discussed. A scoreboard designed around byte-exact reconstructed addresses is designed around information that does not exist.

"An engineer puts the column field at bit 0 of the address. What happens?"

Data aliases at a 64-byte interval, and the failure is structured rather than random. Each column command moves 64 bytes, so the map has made 64 distinct addresses name 64 distinct columns when they should all name one. The column field overlaps the region that should have been the transfer offset, so the map is no longer a bijection. The diagnostic is the interval: failures every 64 bytes point straight at a field whose LSB should have been 6.

"Is address multiplexing a legacy artefact?"

No. It is a live answer to a live constraint. Pins still cost package area, routing and power; rows still change far less often than columns. A device designed today with a fresh interface would reach the same structure — and in fact DDR4 moved further in that direction by multiplexing former command pins into the address field. What is legacy is the namingChapter 6.4 covered why the old strobe mnemonics outlived the mechanism they described.

12. Engineering Exercise

Layout from §4: OFFSET_W = 6, NUM_COLS = 16, NUM_BANKS = 4, ROW_W = 16, ADDR_W = 32.

1. How much contiguous system address does one row of one bank cover?

2. A request stream reads 0x00104480, 0x001044C0, 0x00104500, 0x00104540. How many activates are needed, and how many column commands?

3. The stream continues to 0x001047C0, then 0x00104800. What changes at that step?

4. A monitor sees RD bank 1, column 2 and has observed exactly one activate to bank 1, carrying row 0x0104. What system addresses could this read be serving?

5. An engineer sets OFFSET_W = 0 to "support byte addressing." What breaks?

6. Write the property that catches a column field placed at bit 5 instead of bit 6.

13. Summary

A column address selects which part of the open row reaches the interface — and a column command does not identify a location. It carries a bank and a column. The row comes from state.

Multiplexing is an economic argument, not a legacy one. Rows change rarely and columns change constantly, and pins cost money, so the interface sends the rare field once and stores it in the device. The same rule Module 7 closed with — frequency and independence decide representation — applied to address fields.

The effective location is bank operand + held row + column operand. Two thirds from the command, one third from history. Which makes address interpretation temporal, and makes a controller's open-row model part of the address path rather than bookkeeping.

A byte offset is not a column. One column access moves 64 bytes on both DDR4 and DDR5, so consecutive columns are 64 bytes apart and the low 6 bits reach no DDR operand at all. Byte granularity is resolved on the data path or at the requester.

One row of one bank spans NUM_COLS × bytes-per-column-access — 1 KB in this chapter's layout. Every locality claim in DDR is a comparison between an access stride and that number.

And two bit-identical column commands can reach different locations. No assertion on the command can distinguish them, no monitor without history can interpret them, and a scoreboard can only ever reconstruct the address to transfer granularity. That is the reconstruction problem, and it is the reason DV treats addressing as a modelling task rather than a decode task.

14. What Comes Next

Chapters 8.1 and 8.2 have used one field layout throughout and repeatedly flagged it as a choice. Chapter 8.3 — Bank Address is where the choice becomes the subject.

The bank field is the most consequential position in the map, because bank is the axis of independenceChapter 5.2 established that banks hold independent rows and can be busy simultaneously. So where the bank bits sit decides how quickly a sequential access stream spreads across independent resources, and two defensible layouts give opposite answers for the same address.

That chapter builds a mapper with both policies compiled in, so the same address can be decomposed twice and compared.


Return to Row Address for the five layers and the row operand, Banks for the state that supplies the effective row, Read and Write for the commands that carry a column, CAS# for why the column-command interval is the number everyone quotes, and Columns for what a column is inside the array.

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.