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 address | Column address | |
|---|---|---|
| Selects | which row enters the sense amplifiers | which part of the sensed row leaves the device |
| Carried by | ACTIVATE | READ / WRITE |
| Acts on | the array | the row already held |
| Changes device state | yes — opens a row | no — the row stays open |
| Repeated? | once per row | as 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:
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.
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:
| Generation | Interface width | Burst | Bytes per column access |
|---|---|---|---|
| DDR4 | 64-bit module | BL8 | 64 bytes |
| DDR5 | 32-bit sub-channel | BL16 | 64 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 itself — Chapter 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
// ─────────────────────────────────────────────────────────────────────────
// 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);
endmoduleState, 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:
| Field | Bits | Note |
|---|---|---|
| offset | 5:0 | 64 bytes per column access — reaches no operand |
| column | 9:6 | 16 columns, 64 bytes apart in the address |
| bank | 11:10 | 4 banks |
| row | 27:12 | 65,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:
0x00104480 | 0x00105480 | |
|---|---|---|
addr_row | 0x0104 | 0x0105 |
bank_operand | 1 | 1 |
col_operand | 2 | 2 |
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 1 | sys_addr | col_cmd_resolvable | bank_closed | row_needs_change | effective_row |
|---|---|---|---|---|---|
| closed | 0x00104480 | 0 | 1 | 0 | not known |
open, row 0x0104 | 0x00104480 | 1 | 0 | 0 | 0x0104 |
open, row 0x0104 | 0x001044C0 | 1 | 0 | 0 | 0x0104 |
open, row 0x0105 | 0x00104480 | 0 | 0 | 1 | 0x0105 |
open, row 0x0104 | 0x00104481 | 1 | 0 | 0 | 0x0104 |
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 cyclesCompare 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:
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 0Reading 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
// 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:
| Needed | Source | Failure if wrong |
|---|---|---|
| bank operand | the command | wrong bank in the reconstruction |
| column operand | the command | wrong column |
| the open row | its own model of activate history | wrong row — and nothing on the wire disagrees |
| the map | configuration | every field plausible, address wrong |
| the offset | unrecoverable | address 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.
- 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.
addr_rowis extracted from the wrong bits, so the activate opened the wrong row in the first place.- 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.
- 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.
- 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_rowfor 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 naming — Chapter 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 independence — Chapter 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
Related tutorials
- Related topic
Sequential Burst
A burst does not increment an address. It visits positions inside a burst-local window and wraps within it — which is why a burst starting partway through ends by visiting columns before its own starting point.
- Related topic
The Refresh Requirement
Leakage produces a rule about the passage of time rather than about any operation. What the maintenance operation actually does, why it costs device availability, and how a digital design tracks a deadline, arbitrates it against traffic, and proves it never silently drops the obligation.
- Related topic
Restore Operations
Sensing consumed the stored state, so something must put it back. What restoration drives, why it covers a whole row, why a restored row is then cheap to access again, and an educational control model that cannot skip a prerequisite the array is unable to enforce.
- Related topic
Rows
A DRAM row is not an address range. It is the group of cells one shared selection conductor connects at the same instant — and that physical fact is where row granularity, controller-visible row state and state-dependent access cost all come from.
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.
