Skip to content
VLSI Mentor

DDR · Module 8

Row Address

A row address is not a slice of the CPU's address that DDR defines. It is a field a controller chooses, extracted from a system address, and carried by exactly one command — which is why a read never repeats it.

Module 7 answered what operation is being requested. Every command it built carried operands, and Chapter 7.1 §5 established that an operand's meaning depends on the command carrying it.

This module asks the next question, and it is the one the operands themselves have been waiting for:

Once the operation is known, what location does it actually name?

That question is harder than it sounds, because a DDR device is not addressed the way memory is addressed in software. A program holds a byte address. A DRAM array holds rows and columns in banks. Nothing in the protocol converts one into the other for you, and — this is the part that surprises people — nothing in the protocol even specifies how the conversion should be done.

So the module starts with the row, and the chapter's question is:

Where does the row component of an address come from, and why does it travel with exactly one command and never again?

1. What a Row Address Selects

A row address selects one row within one bank. That is the whole of its meaning, and two words in that sentence do real work.

"One row"Chapter 3.2 established that a row is the unit an activate moves into the sense amplifiers. The row address chooses which one.

"Within one bank" — a row address is meaningless on its own. Row 0x1234 is not a location; row 0x1234 of bank 3 is. Every bank has its own row 0x1234, and Chapter 5.2 explained why: each bank owns an independent row decoder and its own set of sense amplifiers. Rows are numbered per bank, not per device.

So a row operand is half a coordinate. The bank operand is the other half, and the two travel together on the same command — which is the first hint that "the address" is not a single thing.

What a row address is not: it is not a byte address, not a cache-line address, and not a slice of the program's address that the DDR specification tells you where to find. It is a field a controller extracts, under a policy the controller chose.

That claim is the foundation of the whole module, so it deserves its own section.

2. Five Layers, Kept Apart

The single most common source of confusion in DDR addressing is collapsing five distinct things into one word. Keep them apart and the rest of the module is straightforward. Collapse them and nothing makes sense.

Five address layers from top to bottom. The system physical address is a byte address used by software, caches and the interconnect. The controller address map is a policy, chosen by the controller, that partitions those bits into fields. The command operands are the fields carried with one specific DDR command, and activate and read do not carry the same ones. The protocol encoding is how those operands appear on generation-specific command and address pins across defined cycles. The device internal location is the physical array resource finally selected. Only the controller address map layer is a free choice; the protocol encoding is fixed by the generation and the device internal location is fixed by the silicon.A — System physical addressA byte address · software, caches, the interconnectA byte address · software, caches, the interconnectB — Controller address mapA POLICY · the controller chooses this · not defined by DDRA POLICY · the controller chooses this · not defined by DDRC — Command operandsFields carried by ONE command · ACT and READ differFields carried by ONE command · ACT and READ differD — Protocol encodingPins and cycles · generation-specific · Module 6 and 7Pins and cycles · generation-specific · Module 6 and 7E — Device internal locationThe array resource selected · fixed by the siliconThe array resource selected · fixed by the silicon
Figure 1 — Five address layers. Each is a different representation of the same access, and only the middle one is chosen by the controller.

A — System physical address. A byte address. It is what a load instruction produces after translation, what a cache tags, and what an interconnect routes. It has no notion of rows or banks, and it is not what appears on the DDR address pins.

B — Controller address map. The policy that partitions and reorders those bits into channel, rank, bank group, bank, row, column and an offset within a transfer. This is controller architecture, not protocol. Two controllers attached to identical DRAM devices may map differently and both be correct.

C — Command operands. The fields carried with one specific command. ACTIVATE carries row-oriented operands; READ and WRITE carry column-oriented ones; PRECHARGE carries bank and scope; MRS carries fields that are not a memory address at all. The operands differ per command, which means "the address" has no single value at this layer either.

D — Protocol encoding. How those operands appear on real pins across real cycles. Module 6 established that this changed across generations, and Chapter 7.1 §4 built the encoder that performs the placement.

E — Device internal location. The physical resource finally selected. Module 3 covered it, and it is related to the operands but must never be casually equated with a flat linear address.

3. Why the Row Travels With ACTIVATE

Here is the structural fact that makes DDR addressing unusual: no single command carries a complete location.

ACTIVATE carries bank and row. READ carries bank and column. Neither carries all four fields, and a read does not repeat the row.

Why not? Because the row is not a parameter of a read. It is a property of the bank's current state.

Chapter 7.1 established what ACTIVATE does: it opens a row into the bank's sense amplifiers, and the bank then holds that row. Chapter 5.2's state table is the controller-side model of exactly that. So by the time a column command arrives, the row is already selected — not by the column command, but by the activate that preceded it.

The row component of the address is deposited into device state by ACTIVATE, and every later column command inherits it.

That is why re-sending the row with each read would be not merely wasteful but meaningless: there is nowhere for a second row number to go. The sense amplifiers hold one row. A read that named a different row would be naming something the bank does not currently have.

This is the module's central idea, and it arrives in the very first chapter. The full location of a column access is distributed across command history, not carried by any one command. Chapter 8.2 develops it in full; here it is enough to see why the row is the piece that goes first.

4. RTL — Extracting the Row Field

The engineering problem

A controller receives a system physical address. Before it can issue an activate it must answer: which bits are the row, and which bank does that row belong to? Those are two constant bit-slices, and getting either one wrong produces a command that is perfectly legal and reads the wrong place.

Why hardware needs it

This sits on the request path of every memory access, so it must be wiring rather than computation. It is also the point where a structurally illegal configuration must be caught — at elaboration, not at runtime.

Classification

SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.

What it models

Extraction of ACTIVATE's operands — row and bank — from a system physical address, under a declared field layout, with the structural legality of that layout checked at elaboration.

What it does NOT model

The array (Module 3 owns rows). Row activation (Module 9). The encoding that places the operand on pins (Chapter 7.1 §4 owns the encoder). Bank state (Chapter 5.2's ddr_bank_state_table owns the open row, and this block deliberately stores nothing). Timing (Modules 13 and 14).

Interface and parameter contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// row_field_extract
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// THIS IS ONE EXAMPLE CONTROLLER ADDRESS-MAPPING POLICY. IT IS NOT A
// JEDEC-MANDATED UNIVERSAL DDR ADDRESS MAP. No DDR specification fixes
// which system address bits become the row -- that choice belongs to the
// controller. Chapter 8.3 shows a different layout; 8.6 parameterises it.
//
// MODELS: extraction of ACTIVATE's operands (row, bank) from a system
// physical address under a declared field layout.
//
// DOES NOT MODEL: the array (Module 3), row activation (Module 9), the
// command encoding that carries the operand onto pins (Chapter 7.1),
// bank state (Chapter 5.2 owns the open row -- nothing is stored here),
// or timing (Modules 13, 14).
//
// GENERATION: GENERATION-NEUTRAL CONTROLLER MODEL. A system address and a
// field layout exist in every generation; widths differ by device.
// ─────────────────────────────────────────────────────────────────────────
module row_field_extract #(
  parameter int ADDR_W   = 32,

  // Bytes moved by ONE column access, as a power of two. 6 gives 64 bytes,
  // which Module 4 derived twice over: DDR4's 64-bit bus with BL8 and
  // DDR5's 32-bit sub-channel with BL16 both move 64 bytes per column
  // access. Bits below this select a byte INSIDE one transfer, so they are
  // not part of any DDR operand.
  parameter int OFFSET_W = 6,

  parameter int COLUMN_W = 4,
  parameter int BANK_W   = 2,
  parameter int ROW_W    = 16
) (
  // ── Layer A: the system physical address (Section 2).
  input  logic [ADDR_W-1:0] sys_addr,

  // ── Layer C: ACTIVATE's operands, and ONLY those. A column command's
  //    operand is Chapter 8.2's, and it is a different field.
  output logic [ROW_W-1:0]  row_operand,
  output logic [BANK_W-1:0] bank_operand,

  // The low OFFSET_W bits are non-zero, so this address names a byte
  // INSIDE a column access rather than the start of one. NOT an error, and
  // it does NOT invalidate the row -- reported because a controller that
  // silently drops those bits has narrowed the request without saying so.
  output logic              offset_nonzero,

  // A bit at or above the mapped region is set, so the address lies
  // outside the device this layout describes. Only possible when the
  // fields occupy fewer bits than ADDR_W. REPORTED, NOT REPAIRED:
  // truncating it would read a real location while believing it read the
  // one that was asked for.
  output logic              addr_above_device,

  output logic              row_operand_valid
);

  // ── Field positions. THE CHAPTER IS THESE FIVE LINES.
  //    Low to high: offset, column, bank, row. Each LSB is the previous
  //    LSB plus the previous width, which is the only construction that
  //    cannot leave a gap or an overlap.
  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-time legality. A layout that does not fit is a
  //    STRUCTURAL error: there is no correct runtime behaviour to fall
  //    back on, so it must stop elaboration rather than raise a flag.
  if (ADDR_W < 1) begin : g_aw
    initial $fatal(1, "row_field_extract: ADDR_W must be >= 1");
  end
  if (OFFSET_W < 1) begin : g_ow
    initial $fatal(1, "row_field_extract: OFFSET_W must be >= 1");
  end
  if (COLUMN_W < 1) begin : g_cw
    initial $fatal(1, "row_field_extract: COLUMN_W must be >= 1");
  end
  if (BANK_W < 1) begin : g_bw
    initial $fatal(1, "row_field_extract: BANK_W must be >= 1");
  end
  if (ROW_W < 1) begin : g_rw
    initial $fatal(1, "row_field_extract: ROW_W must be >= 1");
  end
  if (MAP_W > ADDR_W) begin : g_fit
    initial $fatal(1, "row_field_extract: fields need more bits than ADDR_W");
  end

  // ── Extraction. Every LSB is a localparam, so each part-select is
  //    constant at elaboration and synthesises to wiring: no logic, no
  //    delay, no arithmetic.
  assign row_operand  = sys_addr[ROW_LSB  +: ROW_W];
  assign bank_operand = sys_addr[BANK_LSB +: BANK_W];

  assign offset_nonzero = |sys_addr[OFF_LSB +: OFFSET_W];

  // ── The region above the map. Guarded because when the fields consume
  //    every address bit, sys_addr[ADDR_W-1:MAP_W] would be an illegal
  //    reversed part-select rather than an empty one.
  if (MAP_W < ADDR_W) begin : g_hi
    assign addr_above_device = |sys_addr[ADDR_W-1 : MAP_W];
  end else begin : g_nohi
    // Every address bit is mapped, so no address can fall outside.
    assign addr_above_device = 1'b0;
  end

  // NOTE WHAT DOES *NOT* APPEAR HERE: offset_nonzero. A byte inside a
  // column access belongs to the same row as the start of it, so
  // alignment has no bearing on whether the row operand is usable.
  assign row_operand_valid = !addr_above_device;

endmodule

State, and the absence of it

There is none, and that is a design decision rather than an omission. Extraction is a function of the address alone. The moment a block like this holds state it starts to disagree with Chapter 5.2's state table, and two models of the same thing is exactly the hazard Chapter 7.4 built a monitor to expose.

Combinational behaviour

Four constant slices and two reductions. row_operand and bank_operand are wires onto sys_addr. offset_nonzero is an OR over the low field. addr_above_device is an OR over whatever lies above the map, or a hard zero when nothing does.

Sequential behaviour

None. No clock, no reset — see Reset behaviour below for why that is the right answer rather than a gap.

Bit-level derivation

At the default parameters:

FieldLSBWidthMSBSelects
offset065byte within one 64-byte column access
column649one of 16 columns
bank10211one of 4 banks
row121627one of 65,536 rows
above map28431nothing — must be zero

MAP_W is 28, so bits 31:28 are unmapped and addr_above_device reports them.

Read the boundaries, not the widths. Every addressing bug in this chapter is a boundary that moved: a row starting at bit 11 instead of 12 steals the bank's top bit, and both fields then look plausible.

How to simulate, and expected output

Drive sys_addr and check the four outputs. The addresses worth driving are not random:

sys_addrrowbankoffset_nonzeroaddr_above_device
0x000000000x0000000
0x0FFFFFFF0xFFFF310
0x0AB32C400xAB32300
0x000010000x0001000
0x00000C000x0000300
0x000000400x0000000
0x000000010x0000010
0x100000000x0000001

0x00001000 and 0x00000C00 are the pair that matters. The first is the lowest address whose row is 1; the second is the highest address whose row is still 0. An off-by-one in ROW_LSB breaks exactly this pair and nothing else in a random test.

Synthesis implication

Zero gates. Constant part-selects are wiring; the two reductions are OR trees of 6 and 4 inputs. This is the cheapest block in the module and it is on the critical request path, which is the point: addressing decisions cost nothing at runtime because they were made at design time.

Parameter corner cases

ROW_W == 1 is legal and gives a two-row device — degenerate but structurally sound, and it is the case that catches code assuming a multi-bit field. MAP_W == ADDR_W removes the unmapped region entirely, and the g_nohi branch exists precisely so that configuration elaborates instead of producing a reversed part-select. MAP_W > ADDR_W does not elaborate. Any width of zero does not elaborate, because a zero-width part-select is illegal and a runtime flag cannot repair a structural mistake. Non-power-of-two counts are not expressible here — the module takes widths, not counts, so it cannot describe a 12-row device; Chapter 3.3's decomposition takes counts and reports the resulting out-of-range condition, and Chapter 8.6 returns to the question.

Reset behaviour

There is no reset, because there is no state. Adding one would be a mistake worth naming: a registered address extractor delays every request by a cycle to no purpose, and gives the block an opinion about a reset value for something that is a pure function of its input.

Debugging clues

addr_above_device asserting on addresses software believes are valid means the map describes a smaller device than the system thinks it has. Row and bank both wrong in a correlated way points at a shifted boundary between them rather than two independent bugs. A row that changes when it should not is a ROW_LSB that is too low.

Limitations

One layout, hard-coded field order, widths rather than counts, no channel or rank field (those are Chapter 8.5's), and no opinion about whether this layout is a good one — which is Module 18's subject and is not a correctness question at all.

5. A Worked Binary Example

Take sys_addr = 0x0AB32C40 and do it by hand. In binary, grouped by the fields above:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    0000  1010 1011 0011 0010  11    0001    000000
    ├──┘  ├────────────────┘   ├─┘   ├──┘    ├────┘
    above       row[15:0]      bank  column   offset
    map          0xAB32          3      1        0
   (31:28)       (27:12)       (11:10)  (9:6)   (5:0)

Check it by reassembly, which is the only check worth trusting:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  row    0xAB32 = 43826   × 2^12 = 179,511,296
  bank        3           × 2^10 =       3,072
  column      1           × 2^6  =          64
  offset      0                  =           0
                                  ───────────────
                                   179,514,432  = 0x0AB32C40  ✓

So this address becomes an ACTIVATE of bank 3, row 0xAB32 — and, later, a column command to column 1 of that same bank. Two commands, one address, and Chapter 8.2 picks up the second one.

Now walk a stride and watch which fields move.

row_field_extract — which field a stride actually moves

8 cycles
Eight cycles, one system address per cycle. The first four addresses step by one kilobyte and produce bank zero, one, two and three in turn while the row operand stays at 0xAB32, because in this layout the bank field sits below the row field. The fifth address rolls the bank field over and the row operand increments to 0xAB33. The last three addresses step by 64 bytes and 4 bytes, moving the column field and then setting the offset-non-zero flag without changing the row or the bank.same row, four bankssame row, four bankssame row, same banksame row, same bankstride = 1 KBstride = 1 KBrow changes oncerow changes oncebyte inside a transferbyte inside a transferCKsys_addr0AB320000AB324000AB328000AB32C000AB330000AB330400AB330800AB33084row_operandAB32AB32AB32AB32AB33AB33AB33AB33bank_operand01230000col (8.2)00000122offset_nzt0t1t2t3t4t5t6t7
Figure 2 — A 1 KB stride in this layout. The row operand is unchanged across four different addresses, then changes once.

Cycles 0 to 3 are the lesson. Four different addresses, four different banks, and one unchanging row operand. In this layout the bank field sits below the row field, so a 1 KB stride walks the banks before it ever touches the row. Sixteen columns of 64 bytes fill 1 KB, four banks fill 4 KB, and only then does the row increment — which is what happens at cycle 4.

Cycles 5 and 6 step by 64 bytes: the column field moves and nothing else does.

Cycle 7 steps by 4 bytes. No DDR operand changes at all — the access still names column 2 of row 0xAB33 in bank 0 — and offset_nonzero asserts to say the requested byte is inside that transfer. This is where the system address carries information no DDR command can express, and a controller must handle it some other way.

Representative educational cycles. One address per cycle is a convenience for reading the trace; nothing here implies a request rate or any timing requirement.

6. Where the Row Field Sits

The layout above put the row at the top. That was a choice, and it is worth seeing what the choice buys.

Putting the row high means contiguous addresses stay in one row for a long stretch — in this layout, 4 KB of address space shares a row number. A sequential access pattern therefore produces long runs of column commands against an already-open row.

Putting the row low would mean the opposite: adjacent addresses would land in different rows of the same bank, and a sequential stream would close and reopen a row for nearly every access. Module 9 covers what that costs; it is enough here to see that the cost is created at layer B, by the map, not by the device.

What is universal is the structure, not the order: the fields are disjoint, they cover a contiguous region of the address, and their order is a controller's decision. Any DDR device will accept operands derived under any legal layout, because the device never sees the system address at all. It sees row 0xAB32 of bank 3, and it has no way to know, and no reason to care, which address bits produced them.

7. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1 -- the row is insensitive to every bit below it. This is the
// row-locality property of this layout stated as a contract, and it is
// what a shifted ROW_LSB breaks: if the row field starts one bit too low,
// a change confined to the bank field leaks into the row.
property p_row_ignores_lower_bits;
  @(posedge clk)
    ((sys_addr >> ROW_LSB) == ($past(sys_addr) >> ROW_LSB))
      |-> (row_operand == $past(row_operand));
endproperty
assert property (p_row_ignores_lower_bits);

// P2 -- and the converse direction for the bank field, which together
// with P1 says the two fields do not share a bit. A change confined to
// the bank field must move the bank and leave the row alone.
property p_bank_field_is_disjoint;
  @(posedge clk)
    (((sys_addr ^ $past(sys_addr)) >> ROW_LSB) == '0
      && ((sys_addr ^ $past(sys_addr)) >> BANK_LSB) != '0)
      |-> (bank_operand != $past(bank_operand))
          && (row_operand == $past(row_operand));
endproperty
assert property (p_bank_field_is_disjoint);

// P3 -- a legality contract for the consumer. When the block says the row
// operand is usable, the address really did lie inside the mapped device.
// This is the property an activate issuer is entitled to rely on.
property p_valid_means_inside_device;
  @(posedge clk)
    row_operand_valid |-> ((sys_addr >> MAP_W) == '0);
endproperty
assert property (p_valid_means_inside_device);

// P4 -- alignment is reported exactly, in both directions. The "only if"
// half matters as much as the "if": a block that over-reports alignment
// problems trains its consumer to ignore the flag.
property p_offset_reported_exactly;
  @(posedge clk)
    offset_nonzero == (sys_addr[OFFSET_W-1:0] != '0);
endproperty
assert property (p_offset_reported_exactly);

What these prove. P1 and P2 together prove the row and bank fields are disjoint and correctly positioned — the single most valuable thing to prove about a bit-slicing block, because a misplaced boundary produces two plausible operands and no error. P3 gives the activate issuer a contract. P4 makes the alignment report trustworthy in both directions.

What they do not prove. Nothing here says this layout is a good one, that it suits any workload, or that the resulting activate is legal to issue — legality against bank state is Chapter 7.3 §4's, and legality against timing is Modules 13 and 14'. Nor do they prove anything about the device: an assertion on a controller block cannot observe DRAM. And P1 is specific to this layout; under Chapter 8.3's alternative it would still hold, but it would be proving something less interesting, because the bank field would no longer be the thing below the row.

8. DV — Reconstructing the Row

A verification component watching the command interface sees an activate carrying bank 3 and row 0xAB32. It does not see 0x0AB32C40, and it cannot recover it, because it is missing two things: the column, which has not been sent yet, and the map, which is not on any wire.

This produces a division of labour that DV engineers must make explicit, and that Chapter 7.4 §7 began:

ComponentWorks in terms ofNeeds
CA decodersampled pin valuesthe generation's encoding
Command monitorsemantic command + operandsthe decoder
Bank-state modelopen row per bankactivate and precharge history
Address reconstructorsystem addressall of the above, plus the map
Scoreboardsystem address and datathe reconstructor

The reconstructor is the new component in this module, and the row is the first field it can populate. Note where it sits: a scoreboard that compares data against a system address lives two layers above the command monitor, and the mapping knowledge that bridges them has to come from configuration, because nothing on the interface reveals it.

Which produces the failure mode worth planning for. If the reference model's map and the controller's map disagree, every command is legal, every assertion on the command interface passes, and the scoreboard reports data corruption. The bug is in neither the commands nor the data — it is a configuration mismatch two layers up. Chapter 8.6 §8 builds the checker for it.

9. Debugging — Right Command, Wrong Row

Symptom. A workload reads back data that belongs to a different address. Commands look correct on the interface: activates and reads are well-formed and legally ordered.

Candidate mechanisms.

  1. The row field is extracted from the wrong bit position.
  2. The row field is the right width but the bank field is wrong, so the right row of the wrong bank is opened.
  3. The map is correct and the open-row state has drifted — Chapter 5.2 §4's hazard.
  4. The data path is at fault and addressing is innocent.

Evidence to collect. For each failing access, log the system address, the extracted operands, and the activate that preceded the failing read. Then sort the failures by address and look at the intervals between them.

Discriminator. The interval is the discriminator, and this is the technique worth keeping:

  • Failures at a power-of-two interval in the address mean an address bit is not reaching the field it should. The interval is the bit: failures every 4096 bytes implicate bit 12, which in this layout is the row's LSB.
  • Correct row, wrong bank means the boundary between them moved, not that two fields are independently broken. One shifted boundary is far more likely than two coincident bugs.
  • Failures independent of address, hitting the same locations regardless of pattern, exonerate extraction entirely and point at mechanism 3 or 4.
  • If reconstructing the address from the observed operands yields the address the request actually asked for, extraction is correct and the fault is downstream. This is the single most useful check, and it is the round-trip Chapter 8.6 automates.

Responsible layer. A power-of-two interval or a shifted boundary is layer B — the controller's map. Address-independent failures are the device, the data path, or state tracking. A legal command stream tells you nothing either way, which is why this bug class survives assertion suites that only watch the interface.

Fix. Correct the boundary, then re-run with the round-trip property enabled so the same class cannot return silently.

10. Common Misconceptions

"A DDR address is a flat memory address."

Why it is tempting: every other memory the learner has met is flat, and DRAM is presented as a large array.

Concrete failure: an engineer writes a reference model that treats the device as mem[addr] and cannot explain why a read returns data from another row when the commands are legal.

Correct model: a DDR location is a coordinate — bank, row, column — reached by a sequence of commands. Flatness is an abstraction the controller manufactures.

Prevention: build the reference model in coordinates and map into them, never the reverse.

"The CPU physical address appears on the DDR address pins."

Why it is tempting: the bits look similar, and the word "address" appears at both ends.

Concrete failure: a debug session spent looking for bit 12 of the physical address on a pin named A12, which carries a row bit that may be any address bit the controller chose.

Correct model: layers A and D are separated by a policy (layer B) and a command context (layer C). The pins carry operands, not addresses.

Prevention: when reading a waveform, name what you see as an operand, and resolve it to an address only with the map in hand.

"DDR defines one universal row/bank/column bit mapping."

Why it is tempting: DDR specifies so much else precisely that the map feels like it must be in there too.

Concrete failure: a reference model is written against a mapping from a vendor note, matches on one platform, and reports corruption on the next — with no bug in either.

Correct model: the map is a controller choice. Two correct controllers on identical devices may map differently.

Prevention: treat the map as configuration, never as a constant, and make the reference model read it.

"The row address is sent again with every READ."

Why it is tempting: it is how a flat address would work, and it feels safer to be explicit.

Concrete failure: a monitor is written expecting a row field on column commands, finds bits there, and interprets auto-precharge as part of a row number.

Correct model: the row is deposited in state by ACTIVATE. A column command has nowhere to put a second one.

Prevention: Chapter 8.2.

"Row bits and bank bits are fixed across controllers."

Why it is tempting: they are fixed in any one system, and most engineers only ever see one.

Concrete failure: an address-mapping constant compiled into verification IP silently invalidates every reuse of that IP.

Correct model: fixed per controller, not per protocol.

Prevention: parameterise; Chapter 8.6 shows how.

"An address extractor models DRAM internals."

Why it is tempting: it emits rows and banks, which sound like silicon.

Concrete failure: an engineer expects the block to catch an illegal activate and is surprised when it does not — it cannot, because it does not know what is open.

Correct model: it is pure layer-B arithmetic. State legality is Chapter 7.3's and array behaviour is Module 3'.

Prevention: keep the classification comment at the top of every block, and read it before assigning blame.

11. Interview Reasoning

"Is the mapping from physical address to row, bank and column defined by the DDR specification?"

No — and that is the answer that separates people who have built a controller from people who have read about one. The specification defines what operands a command carries and how they are encoded. Which system address bits become those operands is a controller decision, made to suit expected traffic. Two controllers on identical devices may choose differently and both be correct, which is why verification IP must take the map as configuration.

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

Because the row is not a parameter of a read — it is the bank's current state, deposited by the activate that opened it. The sense amplifiers hold one row; there is nowhere for a second row number to go. Architecturally this is also what lets a column command be cheap: it carries only the field that varies between accesses to the same row.

"You see an activate on the interface carrying bank 3, row 0xAB32. What system address produced it?"

Unanswerable from that information, and saying so is the right answer. You need the map, which is not on any wire, and the column, which has not been sent yet. What you can say is that the address lies within a known contiguous 4 KB region under the map from §4 — which is exactly the reasoning a reconstructing monitor performs.

"A workload fails every 4096 bytes. Where do you look first?"

At the address bit that 4096 corresponds to — bit 12 — and then at which field claims it. A power-of-two failure interval is a dropped or mis-routed address bit, and the interval names the bit. That reasoning gets you to the right boundary in one step, without a waveform.

12. Engineering Exercise

Use the §4 layout: OFFSET_W = 6, COLUMN_W = 4, BANK_W = 2, ROW_W = 16, ADDR_W = 32.

1. Decompose 0x00104080 into offset, column, bank, row, and verify by reassembly.

2. Which of 0x00104080 and 0x001040C0 share a row? Share a bank? Do they need one activate or two?

3. How many distinct system addresses map to bank 1, row 5?

4. A colleague moves the row field down to ROW_LSB = 11. What is the largest stride that still keeps sequential traffic in one row, and which other field is now broken?

5. Write the SVA property that would have caught question 4's change.

6. An engineer proposes registering row_operand to improve timing. Argue for or against.

13. Summary

A row address selects one row within one bank, and it is a field a controller chose — not a slice of the system address that DDR defines.

Five layers, kept apart: the system physical address; the controller's map, which is policy; the command operands, which differ per command; the protocol encoding, which is generation-specific; and the device's internal location. Layer B is a choice; layer D is a specification. Conflating them sends address bugs to the wrong team.

The row travels with ACTIVATE and never again, because the row is not a parameter of a column access — it is the bank's state, deposited by the activate. The full location of a column access is therefore distributed across command history, which is the idea the rest of this module is built on.

Extraction is wiring. Constant part-selects, zero gates, no state, no reset. The whole design is in the five lines that compute the field boundaries, and every bug in this chapter is a boundary that moved.

Prove disjointness, not extraction. That a field equals a slice is a tautology. That a change below the row never moves the row, and that a change in the bank field never moves the row, are the properties a shifted boundary actually breaks.

And the failure interval names the bit. Failures at a power-of-two address interval mean an address bit is not reaching the field it belongs to, and the interval tells you which one — before you open a waveform.

14. What Comes Next

Chapter 8.2 — Column Address takes up the half of the coordinate this chapter deferred, and it is where the module's central idea becomes concrete. A column command carries a column and a bank — and that is not enough to identify a location. The row comes from state, the low address bits do not appear on the interface at all, and the effective location has to be reconstructed from a command history rather than read from a command.

That is a different kind of addressing than software ever does, and it is why an open-row model is part of address interpretation rather than bookkeeping.


Return to Activate for the command model and the encoder that carries this chapter's operand, Banks for the state the row is deposited into, Rows for what a row physically is, and Columns for the array-internal decomposition this chapter's system-address map sits above.

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.