Skip to content
VLSI Mentor

DDR · Module 8

Physical Mapping

Five chapters of fields assembled into one map, and then the two questions none of them could ask alone: is the decomposition lossless, and can a monitor invert it from what it actually observed?

Five chapters have each taken one field. Column and row name a location inside a bank, bank and bank group name the resource, and rank names the devices — by a different mechanism entirely.

This chapter assembles them, and then asks the two questions that no single-field chapter could:

Is the decomposition lossless — and can it be inverted from what a monitor actually observes?

The first is a structural question with a clean answer: reassemble the fields and demand the original address back. One property catches a dropped bit, an overlapping slice, a gap and a wrong concatenation order, which is a better return than any amount of per-field inspection.

The second is the one this module has been building toward since Chapter 8.1 §8, and its answer is more interesting than yes or no: partly, and the part that is missing is missing permanently.

1. The Whole Map at Once

Assembled, with every field this module has introduced:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ┌─────┬──────┬──────────────┬─────┬─────┬────────┬──────────┐
  │ chan│ rank │     row      │ ba  │ bg  │ column │  offset  │
  └─────┴──────┴──────────────┴─────┴─────┴────────┴──────────┘
    29     28      27      14  13 12 11 10  9    6  5       0

        POLICY 0 — MAP_INTERLEAVED (bank index just above column)

And the four destinations from Chapter 8.5 §2, applied field by field:

FieldBitsDestinationReaches a DDR pin as
offset5:0nowherenothing at all
column9:6operandCA bits, on a column command
bank group11:10operandCA bits, generation-dependent
bank13:12operandCA bits
row27:14operandCA bits, on an activate only
rank28qualificationa chip-select assertion
channel29pre-interfacenothing — it selects the interface

Read the right-hand column and the module's thesis is visible in one place. Of 30 mapped bits, 24 become operands on the command/address bus. One becomes a chip-select. One selects which bus exists. Six reach nothing. And of the 24, fourteen travel on a different command from the other tenChapter 8.2's temporal split.

Capacity check, which is how you confirm a map rather than by reading slices:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    64 B per column access
  × 16 columns          =     1 KB   per row, per bank
  × 16,384 rows         =    16 MB   per bank
  × 4 groups × 4 banks  =   256 MB   per rank
  × 2 ranks             =   512 MB   per channel
  × 2 channels          =     1 GB   = 2^30   ✓ = MAP_W

If that product does not equal 2^MAP_W, the map has a gap or an overlap — and the arithmetic finds it faster than the bit positions do.

2. Losslessness — What a Round Trip Actually Proves

A bit-field map should be a bijection between an aligned address and a field tuple. Chapter 8.2 §4 established why the counts must be powers of two for that to hold; this section is about proving the construction achieves it.

The test is reassembly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  aligned address  ──decompose──►  fields  ──reconstruct──►  address'

  demand:  address' == aligned address,  for every address

This one property catches four distinct bug classes, which is why it is worth more than inspecting slices:

BugWhy per-field inspection misses itHow the round trip catches it
dropped bitevery field looks plausiblethe bit is absent from the reassembly
overlapping fieldsboth fields have sensible valuesthe shared bit is contributed twice
gap between fieldsall fields correctthe gap's bits are never restored
wrong concat orderall fields correctthe assembled value is a permutation

And note what makes it strong: it is a whole-map property. Every other check in this module has been per field. A dropped bit between the row and the rank is invisible to any property about the row or about the rank, and immediately visible to a reassembly.

3. RTL — The Complete Mapper

The engineering problem

One block that partitions a system address into every field, under a selectable policy, with all legality checked at elaboration — and that reports its own reach so a consumer can verify the map against the device rather than trusting it.

Why hardware needs it

This is the front of a controller's request path. Every access passes through it, and it is the single point where the system's view of memory becomes the device's view.

Classification

SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.

What it models

Complete decomposition of a system address into channel, rank, row, bank, bank group, column and offset; the presence or absence of generation- and count-dependent fields; per-field strides; and the map's total reach.

What it does NOT model

Everything downstream: encoding, chip-select generation, bank state, request classification, scheduling (Module 17), timing (Modules 13, 14), hashing, and any judgement of policy quality (Module 18).

Interface and parameter contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// sys_addr_mapper
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// AN EXAMPLE CONTROLLER ADDRESS-MAPPING POLICY. NOT A JEDEC-MANDATED
// UNIVERSAL DDR ADDRESS MAP. Two policies teach the mechanism; real
// controllers use more fields and hash bits to break periodicity
// (Chapter 8.3 Section 6), and Module 18 owns which map suits which
// workload.
//
// MODELS: complete decomposition of a system address into the fields of
// Chapters 8.1-8.5, with generation- and count-dependent field presence,
// per-field strides, and the map's total reach.
//
// DOES NOT MODEL: encoding (Chapter 7.1), chip-select generation (Chapter
// 6.2), bank state (Chapter 5.2), request classification (Chapter 5.3),
// scheduling (Module 17), timing (Modules 13, 14), hashing, or policy
// quality (Module 18).
// ─────────────────────────────────────────────────────────────────────────
module sys_addr_mapper #(
  parameter int ADDR_W   = 32,
  parameter int OFFSET_W = 6,
  parameter int NUM_COLS = 16,

  // Chapter 8.4's structural generation parameter. 3 = no bank groups.
  parameter int GEN             = 4,
  parameter int NUM_GROUPS      = 4,
  parameter int BANKS_PER_GROUP = 4,

  parameter int ROW_W        = 14,
  parameter int NUM_RANKS    = 2,
  parameter int NUM_CHANNELS = 2,

  // 0 = MAP_INTERLEAVED : offset | column | bg | ba | row | rank | chan
  // 1 = MAP_ISOLATED    : offset | column | row | bg | ba | rank | chan
  //
  // Chapter 8.3's BANK_LOW / BANK_HIGH, extended to the full field set.
  // The bg-below-ba sub-choice is fixed here at Chapter 8.4's BG_LOW --
  // that chapter owns the sub-choice, and re-opening it would add a
  // parameter without adding a lesson.
  parameter int POLICY = 0,

  parameter int COLUMN_W = (NUM_COLS        <= 1) ? 1 : $clog2(NUM_COLS),
  parameter int BG_W     = (NUM_GROUPS      <= 1) ? 1 : $clog2(NUM_GROUPS),
  parameter int BA_W     = (BANKS_PER_GROUP <= 1) ? 1 : $clog2(BANKS_PER_GROUP),
  parameter int RK_W     = (NUM_RANKS       <= 1) ? 1 : $clog2(NUM_RANKS),
  parameter int CH_W     = (NUM_CHANNELS    <= 1) ? 1 : $clog2(NUM_CHANNELS),

  // Address bits each optional field OCCUPIES -- Chapter 8.4's distinction
  // between a signal's width and its footprint in the map.
  parameter int BG_MAP_W = (GEN == 3)           ? 0 : BG_W,
  parameter int RK_MAP_W = (NUM_RANKS    <= 1)  ? 0 : RK_W,
  parameter int CH_MAP_W = (NUM_CHANNELS <= 1)  ? 0 : CH_W,

  parameter int FLAT_W = BG_MAP_W + BA_W
) (
  input  logic [ADDR_W-1:0]   sys_addr,

  output logic [COLUMN_W-1:0] col_operand,
  output logic [BG_W-1:0]     bg_operand,
  output logic [BA_W-1:0]     ba_operand,
  output logic [ROW_W-1:0]    row_operand,
  output logic [RK_W-1:0]     rank_index,
  output logic [CH_W-1:0]     channel_index,

  output logic [FLAT_W-1:0]   flat_bank_index,

  output logic                bg_field_present,
  output logic                rank_field_present,
  output logic                channel_field_present,

  // Low-order bits that reach nothing (Chapter 8.2 Section 3). The ALIGNED
  // address is what a round trip can restore -- see addr_roundtrip_check.
  output logic                offset_nonzero,
  output logic [ADDR_W-1:0]   aligned_addr,

  output logic                addr_above_device,

  // The map's reach, so a consumer can check the map against the DEVICE
  // rather than trusting it. Reported as a bit count because the byte
  // count would overflow ADDR_W at exactly the interesting boundary.
  output logic [7:0]          map_bits
);

  localparam int POL_INTERLEAVED = 0;
  localparam int POL_ISOLATED    = 1;

  // ── Field positions. Low to high, with the policy choosing whether the
  //    bank index or the row sits directly above the column.
  localparam int COL_LSB = OFFSET_W;

  localparam int BG_LSB  = (POLICY == POL_INTERLEAVED)
                           ? (COL_LSB + COLUMN_W)
                           : (COL_LSB + COLUMN_W + ROW_W);
  localparam int BA_LSB  = BG_LSB + BG_MAP_W;

  localparam int ROW_LSB = (POLICY == POL_INTERLEAVED)
                           ? (COL_LSB + COLUMN_W + BG_MAP_W + BA_W)
                           : (COL_LSB + COLUMN_W);

  localparam int RANK_LSB = COL_LSB + COLUMN_W + BG_MAP_W + BA_W + ROW_W;
  localparam int CH_LSB   = RANK_LSB + RK_MAP_W;
  localparam int MAP_W    = CH_LSB + CH_MAP_W;

  // ── Elaboration legality. Structural errors stop elaboration; there is
  //    no correct runtime behaviour for a malformed map.
  if ((POLICY != POL_INTERLEAVED) && (POLICY != POL_ISOLATED)) begin : g_pol
    initial $fatal(1, "sys_addr_mapper: POLICY must be 0 or 1");
  end
  if ((GEN != 3) && (GEN != 4) && (GEN != 5)) begin : g_gen
    initial $fatal(1, "sys_addr_mapper: GEN must be 3, 4 or 5");
  end
  if ((GEN == 3) && (NUM_GROUPS != 1)) begin : g_gen3
    initial $fatal(1, "sys_addr_mapper: GEN 3 has no bank groups -- NUM_GROUPS must be 1");
  end
  if ((GEN != 3) && (NUM_GROUPS < 2)) begin : g_gen45
    initial $fatal(1, "sys_addr_mapper: GEN 4/5 needs NUM_GROUPS >= 2");
  end
  if (OFFSET_W < 1) begin : g_ow
    initial $fatal(1, "sys_addr_mapper: OFFSET_W must be >= 1");
  end
  if (ROW_W < 1) begin : g_rw
    initial $fatal(1, "sys_addr_mapper: ROW_W must be >= 1");
  end
  if (MAP_W > ADDR_W) begin : g_fit
    initial $fatal(1, "sys_addr_mapper: fields need more bits than ADDR_W");
  end
  // map_bits is 8 bits wide; a wider map would truncate it silently.
  if (MAP_W > 255) begin : g_mapbits
    initial $fatal(1, "sys_addr_mapper: MAP_W > 255 would truncate map_bits");
  end
  // Bijection requirements. Skipped for fields that occupy no bits.
  if (NUM_COLS != (1 << COLUMN_W)) begin : g_cpow
    initial $fatal(1, "sys_addr_mapper: NUM_COLS must be a power of two");
  end
  if (BANKS_PER_GROUP != (1 << BA_W)) begin : g_bapow
    initial $fatal(1, "sys_addr_mapper: BANKS_PER_GROUP must be a power of two");
  end
  if ((GEN != 3) && (NUM_GROUPS != (1 << BG_W))) begin : g_bgpow
    initial $fatal(1, "sys_addr_mapper: NUM_GROUPS must be a power of two");
  end
  if ((NUM_RANKS > 1) && (NUM_RANKS != (1 << RK_W))) begin : g_rkpow
    initial $fatal(1, "sys_addr_mapper: NUM_RANKS must be a power of two");
  end
  if ((NUM_CHANNELS > 1) && (NUM_CHANNELS != (1 << CH_W))) begin : g_chpow
    initial $fatal(1, "sys_addr_mapper: NUM_CHANNELS must be a power of two");
  end

  // ── Always-present fields.
  assign col_operand = sys_addr[COL_LSB +: COLUMN_W];
  assign ba_operand  = sys_addr[BA_LSB  +: BA_W];
  assign row_operand = sys_addr[ROW_LSB +: ROW_W];

  // ── Fields that may occupy no address bits.
  if (GEN == 3) begin : g_nobg
    assign bg_operand       = '0;
    assign bg_field_present = 1'b0;
  end else begin : g_bg
    assign bg_operand       = sys_addr[BG_LSB +: BG_W];
    assign bg_field_present = 1'b1;
  end

  if (NUM_RANKS <= 1) begin : g_norank
    assign rank_index         = '0;
    assign rank_field_present = 1'b0;
  end else begin : g_rank
    assign rank_index         = sys_addr[RANK_LSB +: RK_W];
    assign rank_field_present = 1'b1;
  end

  if (NUM_CHANNELS <= 1) begin : g_nochan
    assign channel_index         = '0;
    assign channel_field_present = 1'b0;
  end else begin : g_chan
    assign channel_index         = sys_addr[CH_LSB +: CH_W];
    assign channel_field_present = 1'b1;
  end

  // ── Flat index for Chapter 5.2's per-rank state table. Group above
  //    bank, matching Chapter 8.4's declared order -- the order is
  //    arbitrary but MUST be single-sourced, which is why it lives here.
  if (GEN == 3) begin : g_flat3
    assign flat_bank_index = ba_operand;
  end else begin : g_flat45
    assign flat_bank_index = {bg_operand, ba_operand};
  end

  // ── The aligned address: the address with the bits that reach nothing
  //    cleared. THIS is the value a round trip can restore, and saying so
  //    here rather than in the checker keeps the definition in one place.
  assign offset_nonzero = |sys_addr[0 +: OFFSET_W];
  assign aligned_addr   = {sys_addr[ADDR_W-1 : OFFSET_W], {OFFSET_W{1'b0}}};

  if (MAP_W < ADDR_W) begin : g_hi
    assign addr_above_device = |sys_addr[ADDR_W-1 : MAP_W];
  end else begin : g_nohi
    assign addr_above_device = 1'b0;
  end

  assign map_bits = 8'(MAP_W);

endmodule

State, sequential behaviour, reset

None of the three. Seven constant part-selects, two reductions, one concatenation, one masked copy.

Bit-level derivation

At the defaults, both policies:

FieldPOLICY 0 — INTERLEAVEDPOLICY 1 — ISOLATED
offset5:05:0
column9:69:6
bank group11:1025:24
bank13:1227:26
row27:1423:10
rank2828
channel2929
MAP_W3030

MAP_W is 30 for both, matching §1's capacity product of 1 GB. Column, rank and channel occupy identical positions under both policies — the first because it is below everything the policy moves, the other two because they are above everything it moves. Only the three middle fields relocate, which is exactly the fingerprint §9 uses.

Cycle example — one address, two policies

sys_addr = 0x248D2540:

POLICY 0POLICY 1
offset00
column55
bank group10
bank21
row0x12340x2349
rank00
channel11
flat bank index{1,2} = 6{0,1} = 1

Verify POLICY 0 by reassembly, which is the §2 discipline applied by hand:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  channel 1  × 2^29  =  536,870,912
  rank    0  × 2^28  =            0
  row 0x1234 × 2^14  =   76,349,440
  bank    2  × 2^12  =        8,192
  group   1  × 2^10  =        1,024
  column  5  × 2^6   =          320
  offset  0          =            0
                        ────────────
                         613,229,888  = 0x248D2540  ✓

Three fields agree and three differ, and both answers are correct. The address names one byte of memory; the two policies reach it through different resources.

How to simulate, and expected output

Elaborate both policies plus a DDR3 and a single-rank configuration, and drive the boundary addresses:

sys_addrP0: row / ba / bgP1: row / ba / bgnote
0x000000000 / 0 / 00 / 0 / 0all zero
0x000004000 / 0 / 11 / 0 / 01 KB: group vs row
0x000040001 / 0 / 016 / 0 / 016 KB
0x3FFFFFC00x3FFF / 3 / 30x3FFF / 3 / 3max mapped
0x400000000 / 0 / 00 / 0 / 0addr_above_device

Row 2 shows the policies diverging at the smallest step. Under POLICY 0 the bank group sits directly above the column, so a 1 KB step moves the group and not the bank — the bank field moves at 4 KB, which is Chapter 8.4 §3's BG_LOW placement inherited here. Under POLICY 1 the same step moves the row.

Row 4 is the one to get right. 0x3FFFFFC0 is the highest aligned address inside the map, and under both policies every field is at maximum — which is the single best test of whether the fields tile the space exactly. Row 5 is one byte past the map's reach and must be reported, not wrapped.

Synthesis implication

Zero gates, for the sixth time in this module — and the cumulative point is worth stating: the entire address-mapping decision, across all seven fields and both policies, costs nothing at run time. It is wiring chosen at design time. Which is why a mapping decision is never an implementation trade-off, only a traffic one.

Parameter corner cases

GEN == 3 removes the group field's footprint and narrows the map, and the group bijection check is skipped because there is no index to make bijective. NUM_RANKS == 1 and NUM_CHANNELS == 1 behave the same way — and note that all three optional fields can be absent simultaneously, giving a single-channel, single-rank DDR3 map, which is the configuration most likely to be skipped in a regression and the one the g_no* branches exist for. MAP_W == ADDR_W exercises g_nohi; the defaults leave 2 bits spare and exercise g_hi, so both branches need a configuration in the regression. Any non-power-of-two count for a present field does not elaborate. MAP_W > ADDR_W does not elaborate.

Debugging clues

map_bits disagreeing with the capacity product of §1 means a field width is wrong. addr_above_device asserting on addresses the system considers valid means the map describes a smaller device than is installed. Three fields differing while column, rank and channel agree is a policy mismatch — §9.

Limitations

Two policies, no hashing, one channel placement, the bg/ba sub-order fixed, and no opinion about quality. It also cannot detect disagreement with another component's map, which is what the next block is for.

4. RTL — Round Trip and Monitor Inversion

The engineering problem

Two related questions. Forward: does reassembling the fields return the aligned address? Backward: given what a monitor actually observed — operands from commands, a rank from chip-selects, a row from its own state model, a channel from configuration — what system address was that?

The second is not the inverse of the first, and the difference is the lesson.

Why hardware needs it — and where it does not

The forward check is synthesisable and cheap enough to leave in a design as a self-check on the request path. The backward reconstruction is a verification component: it consumes observations, not requests, and some of its inputs exist only in a monitor.

Classification

MIXED, AND THE SPLIT IS DELIBERATE. The round-trip comparison is SYNTHESIZABLE EDUCATIONAL RTL. The inversion is VERIFICATION-ONLY: it takes a tracked open row as an input, which on real hardware no controller-side block can observe — Chapter 7.4 established that distinction and built the monitor that owns such state.

What it models

Reassembly of the field tuple into an aligned address and comparison against the original; and reconstruction of a system address from observed command operands plus qualification plus tracked state plus configuration, with the irrecoverable part reported rather than guessed.

What it does NOT model

The state model itself — the tracked row is an input, from Chapter 5.2's table instantiated per rank as Chapter 8.5 §4 requires. Command decode (Chapter 7.2). CS# sampling (Chapter 6.2). And it does not model the offset, because the offset cannot be modelled — it never crossed the interface.

Interface and parameter contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// addr_roundtrip_check
//
// Classification: MIXED.
//   - The FORWARD round trip is SYNTHESIZABLE EDUCATIONAL RTL.
//   - The BACKWARD inversion is VERIFICATION-ONLY. It consumes a tracked
//     open row, which no controller-side block can observe on real
//     hardware (Chapter 7.4). It is not intended for synthesis.
//
// MODELS: reassembly of a field tuple into an aligned address and
// comparison with the original; and reconstruction of a system address
// from observed operands + qualification + tracked state + configuration,
// WITH THE IRRECOVERABLE PART REPORTED RATHER THAN GUESSED.
//
// DOES NOT MODEL: the state model (Chapter 5.2, per rank), command decode
// (Chapter 7.2), CS# sampling (Chapter 6.2), or the transfer offset --
// which is not a modelling gap but an information one: those bits never
// crossed the interface (Chapter 8.2 Section 3).
// ─────────────────────────────────────────────────────────────────────────
module addr_roundtrip_check #(
  parameter int ADDR_W   = 32,
  parameter int OFFSET_W = 6,
  parameter int COLUMN_W = 4,
  parameter int BG_MAP_W = 2,
  parameter int BA_W     = 2,
  parameter int ROW_W    = 14,
  parameter int RK_MAP_W = 1,
  parameter int CH_MAP_W = 1,

  // Field LSBs, passed in so this block cannot invent a second map. THE
  // WHOLE POINT: a checker that computes its own positions is a second
  // map, and two maps is the bug it exists to catch.
  parameter int COL_LSB  = 6,
  parameter int BG_LSB   = 10,
  parameter int BA_LSB   = 12,
  parameter int ROW_LSB  = 14,
  parameter int RANK_LSB = 28,
  parameter int CH_LSB   = 29,

  parameter int BG_W = (BG_MAP_W <= 0) ? 1 : BG_MAP_W,
  parameter int RK_W = (RK_MAP_W <= 0) ? 1 : RK_MAP_W,
  parameter int CH_W = (CH_MAP_W <= 0) ? 1 : CH_MAP_W
) (
  // ══ FORWARD: the round trip. Synthesisable.
  input  logic [ADDR_W-1:0]   aligned_addr,

  input  logic [COLUMN_W-1:0] col_operand,
  input  logic [BG_W-1:0]     bg_operand,
  input  logic [BA_W-1:0]     ba_operand,
  input  logic [ROW_W-1:0]    row_operand,
  input  logic [RK_W-1:0]     rank_index,
  input  logic [CH_W-1:0]     channel_index,

  output logic [ADDR_W-1:0]   reassembled_addr,
  output logic                roundtrip_ok,

  // ══ BACKWARD: monitor inversion. VERIFICATION-ONLY.
  input  logic                obs_col_cmd,
  input  logic [COLUMN_W-1:0] obs_col,
  input  logic [BG_W-1:0]     obs_bg,
  input  logic [BA_W-1:0]     obs_ba,
  // From CS#, NOT from the CA bus (Chapter 8.5).
  input  logic [RK_W-1:0]     obs_rank,
  // From the monitor's own per-rank state model. A column command carries
  // no row (Chapter 8.2), so this is reconstructed, not observed.
  input  logic [ROW_W-1:0]    tracked_row,
  input  logic                tracked_row_known,
  // Configuration. Nothing observable identifies a channel (Chapter 8.5).
  input  logic [CH_W-1:0]     cfg_channel,

  output logic [ADDR_W-1:0]   recon_addr,
  output logic                recon_valid,
  // The reconstruction is exact only to this many bytes. Always 2^OFFSET_W
  // and never 1: those bits never crossed the interface.
  output logic [ADDR_W-1:0]   recon_granularity_bytes,
  output logic                offset_unrecoverable
);

  if (OFFSET_W < 1) begin : g_ow
    initial $fatal(1, "addr_roundtrip_check: OFFSET_W must be >= 1");
  end
  if (ROW_LSB + ROW_W > ADDR_W) begin : g_fit
    initial $fatal(1, "addr_roundtrip_check: row field does not fit in ADDR_W");
  end

  // ── A field that occupies no address bits must contribute nothing to a
  //    reassembly. Masking by footprint rather than by signal width is the
  //    same distinction Chapter 8.4 drew, and getting it wrong here would
  //    make the round trip fail on every DDR3 configuration.
  logic [ADDR_W-1:0] part_col, part_bg, part_ba, part_row, part_rk, part_ch;

  assign part_col = ADDR_W'(col_operand) << COL_LSB;
  assign part_ba  = ADDR_W'(ba_operand)  << BA_LSB;
  assign part_row = ADDR_W'(row_operand) << ROW_LSB;

  if (BG_MAP_W <= 0) begin : g_nobg
    assign part_bg = '0;
  end else begin : g_bg
    assign part_bg = ADDR_W'(bg_operand) << BG_LSB;
  end

  if (RK_MAP_W <= 0) begin : g_nork
    assign part_rk = '0;
  end else begin : g_rk
    assign part_rk = ADDR_W'(rank_index) << RANK_LSB;
  end

  if (CH_MAP_W <= 0) begin : g_noch
    assign part_ch = '0;
  end else begin : g_ch
    assign part_ch = ADDR_W'(channel_index) << CH_LSB;
  end

  // ── OR, not addition. If the fields are genuinely disjoint the two are
  //    identical -- and where they are NOT disjoint, OR silently merges
  //    while addition carries into a neighbour. Addition is therefore the
  //    better choice for a CHECKER, because it makes an overlap produce a
  //    visibly wrong address instead of a plausible one.
  assign reassembled_addr = part_col + part_bg + part_ba
                          + part_row + part_rk + part_ch;

  assign roundtrip_ok = (reassembled_addr == aligned_addr);

  // ── BACKWARD. Note which sources the three hard fields come from.
  logic [ADDR_W-1:0] rec_col, rec_bg, rec_ba, rec_row, rec_rk, rec_ch;

  assign rec_col = ADDR_W'(obs_col) << COL_LSB;
  assign rec_ba  = ADDR_W'(obs_ba)  << BA_LSB;
  assign rec_row = ADDR_W'(tracked_row) << ROW_LSB;   // from STATE

  if (BG_MAP_W <= 0) begin : g_rnobg
    assign rec_bg = '0;
  end else begin : g_rbg
    assign rec_bg = ADDR_W'(obs_bg) << BG_LSB;
  end

  if (RK_MAP_W <= 0) begin : g_rnork
    assign rec_rk = '0;
  end else begin : g_rrk
    assign rec_rk = ADDR_W'(obs_rank) << RANK_LSB;    // from CS#
  end

  if (CH_MAP_W <= 0) begin : g_rnoch
    assign rec_ch = '0;
  end else begin : g_rch
    assign rec_ch = ADDR_W'(cfg_channel) << CH_LSB;   // from CONFIG
  end

  // The offset contributes ZERO -- not because it is zero, but because it
  // is unknown. The reconstruction names a 2^OFFSET_W-byte block, and
  // recon_granularity_bytes says so rather than letting a consumer
  // mistake the value for a byte address.
  assign recon_addr = rec_col + rec_bg + rec_ba + rec_row + rec_rk + rec_ch;

  // Reconstruction requires a KNOWN row. Without one the address is not
  // partially known, it is unknown: the row is the widest field.
  assign recon_valid = obs_col_cmd && tracked_row_known;

  assign recon_granularity_bytes = ADDR_W'(1) << OFFSET_W;
  assign offset_unrecoverable    = 1'b1;

endmodule

Combinational behaviour

Twelve shifts by constants — free — and two adder trees. The adders are the only real logic in the module, and there is a reason the forward path uses addition rather than a concatenation: a concatenation of the fields would be correct by construction and therefore prove nothing. Shifting each field to its declared position and summing is the construction that can be wrong, which is what makes the comparison meaningful.

Bit-level derivation and worked round trip

Take §3's address and its POLICY 0 fields:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  aligned_addr  =  0x248D2540

  part_ch   =  1      << 29  =  0x20000000
  part_rk   =  0      << 28  =  0x00000000
  part_row  =  0x1234 << 14  =  0x048D0000
  part_ba   =  2      << 12  =  0x00002000
  part_bg   =  1      << 10  =  0x00000400
  part_col  =  5      << 6   =  0x00000140
                                ──────────
  reassembled_addr           =  0x248D2540   → roundtrip_ok = 1  ✓

Now break it deliberately, which is the only way to know a checker works. Suppose ROW_LSB were 13 instead of 14 in the mapper but correct here — the row extracted would be 0x2469 (one bit shifted in from the bank field), and part_row would be 0x2469 << 14 = 0x091A4000, giving a reassembly of 0x291A6540. Not equal, so the round trip fails — and note it fails loudly, by a large margin, rather than by the one bit that was actually wrong. That amplification is a feature: a small slicing error produces a large address error, which is easy to see and hard to dismiss.

Cycle example — the backward direction

A monitor observes a column command: obs_col = 5, obs_bg = 1, obs_ba = 2, obs_rank = 0 from the chip-selects, cfg_channel = 1, and its per-rank state model holds tracked_row = 0x1234 for that bank.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  recon_addr               =  0x248D2500
  recon_granularity_bytes  =  64
  offset_unrecoverable     =  1

Compare with the original: 0x248D2540. The reconstruction is 0x248D2500, and the difference is 0x40 — inside the 64-byte block the reconstruction names. The monitor is not wrong; it is exact to its granularity. A consumer that treats recon_addr as a byte address is the thing that would be wrong, and recon_granularity_bytes exists to stop it.

How to simulate, and expected output

Forward: drive the mapper's outputs straight into the checker and sweep. Exhaustive is achievable for small parameters and is strictly better than random — Chapter 3.3 §7 made this argument for a two-field decomposition and it applies with more force to seven fields. Then perturb one *_LSB parameter and confirm roundtrip_ok drops.

Backward: the cases worth directing are the ones where the state model is the weak link. tracked_row_known low must give recon_valid low — not a partial address, because the row is the widest field and a reconstruction missing it is not an approximation. A stale tracked row must produce a confidently wrong address, which is the behaviour to observe rather than prevent: it is Chapter 8.2 §9's mechanism 3 made visible.

Synthesis implication

The forward path synthesises to two adder trees and a comparator — cheap enough to leave enabled on a request path as a self-check, and that is a real design option worth naming: a controller can carry its own round-trip check in silicon for the cost of an adder. The backward path must not be synthesised: tracked_row has no hardware source.

Parameter corner cases

Every optional field at zero footprint simultaneouslyBG_MAP_W = RK_MAP_W = CH_MAP_W = 0 — is the DDR3, single-rank, single-channel case, and it is the configuration where masking by footprint rather than signal width matters: a reassembly that included a 1-bit bg_operand would fail on every address. OFFSET_W is required to be at least 1, so offset_unrecoverable is unconditionally true — which is honest rather than lazy: there is no legal configuration of a DDR map in which one column command moves one byte.

Reset behaviour

No state, no reset. The state that matters is upstream, and Chapter 7.4 §4's rule governs it: after reset a monitor's tracked rows must be unknown, not zero. A model that initialises tracked_row = 0 with tracked_row_known = 1 will reconstruct addresses in row 0 for every access until the first activate — confidently, and wrongly, which is the worst combination.

Debugging clues

roundtrip_ok low with a large address difference points at a shifted field boundary; the difference divided by the field's stride tells you which field and by how much. roundtrip_ok low with a difference that is a single power of two points at a dropped bit at that position. recon_valid never asserting means the state model is not being updated — check that activates are reaching it, and that they are being attributed to the right rank.

Limitations

It checks a map against itself. It cannot tell you the map is right, only that it is self-consistent — §2's list. Cross-model agreement needs two maps and is §8. And the backward path is only as good as the state model feeding it, which is the module's recurring point.

5. The Same Address Under Two Policies

Before the waveform, the static comparison in one place — because it is the module's whole argument compressed into a table:

POLICY 0POLICY 1moved?
column55no — below the policy
bank group10yes
bank21yes
row0x12340x2349yes
rank00no — above the policy
channel11no — above the policy

Both are lossless. Both round-trip. Both describe 0x248D2540. And they disagree about three fields out of seven.

Which is the fact §2's warning list exists for: a round trip proves a map is internally coherent and says nothing whatever about whether it is the same map as the one in the component you are comparing against.

6. Reconstruction in Cycles

addr_roundtrip_check — inversion from observed commands

8 cycles
Eight cycles of monitor observation. An activate to rank zero carries row 0x1234, which the monitor records in its per-rank state model. Two subsequent column commands to the same bank in rank zero are reconstructed into system addresses 0x248D2500 and 0x248D2540, using the row from state rather than from the command. An activate to rank one then carries row 0x5678, recorded in that rank's separate state entry, and a column command to rank one reconstructs to 0x459E2500, a different address from the rank zero reads despite identical command operands. A final column command targets a bank whose row has never been observed, so the tracked row is unknown and no reconstruction is produced.rank 0 reconstructionsrank 0reconstructionsrank 1rank 1row → staterow → staterow from historyrow from historyrank 1 · other rowrank 1 · other rowunknown row — no addressunknown row — no addressCKobs commandACTRDRD--ACTRDRD--rank (CS#)000--111--bg/ba (CA)1/21/21/2--1/21/23/0--col (CA)--45----44--tracked_row----12341234123412345678????????recon_addr--------248D2500248D2540----------------459E2500----------------recon_validt0t1t2t3t4t5t6t7
Figure 1 — A monitor reconstructing addresses. The row comes from history, the rank from chip-selects, and the last read cannot be reconstructed at all.

Cycle 0 is the only cycle that carries a row, and it is an activate. The monitor records 0x1234 against {rank 0, bank {1,2}}.

Cycles 1 and 2 reconstruct. The column commands carry a bank group, a bank and a column; the row comes from the state written at cycle 0, the rank from the chip-selects, and the channel from configuration. Two addresses fall out: 0x248D2500 and 0x248D2540, 64 bytes apart — consecutive columns, as Chapter 8.2 §3 established.

Cycle 5 is the one that would defeat a careless monitor. Its command operands are bg 1 / ba 2 / col 4identical to cycle 1's. The reconstruction is 0x459E2500, not 0x248D2500, for two independent reasons: the rank bit differs, and rank 1's state model holds a different row. A monitor with a single shared bank table would have produced cycle 1's answer here, which is Chapter 8.5 §12's exercise appearing in a trace.

Cycle 6 is the honest failure. The command targets bg 3 / ba 0, a bank whose row the monitor has never seen — no activate to it has been observed, perhaps because monitoring started mid-stream. tracked_row is unknown, so recon_valid is low and no address is produced. The alternative — reconstructing with a zero or a stale row — would emit a plausible address that is wrong, and a scoreboard would then report corruption against a location nobody accessed.

Which is the rule the whole module has been arguing toward: an address reconstruction is a modelling result, not a measurement, and a model that cannot support one must say so.

Representative educational cycles. No interval here corresponds to any timing parameter, and the one-cycle command spacing is a reading convenience.

7. Five Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1 -- THE round trip. One property, four bug classes (Section 2). This
// is the highest-value assertion in the module: a dropped bit, an
// overlapping slice, a gap and a permuted concatenation all fail it, and
// none of them is visible to a per-field check.
property p_map_is_lossless;
  @(posedge clk)
    !addr_above_device |-> (reassembled_addr == aligned_addr);
endproperty
assert property (p_map_is_lossless);

// P2 -- injectivity, stated as the contrapositive of the round trip and
// checked across two transactions. Two DIFFERENT aligned addresses must
// not produce the same field tuple. P1 catches most ways to break this,
// but an overlap that happens to cancel on a given address does not fail
// P1 and does fail here.
property p_distinct_addresses_distinct_fields;
  @(posedge clk)
    (aligned_addr != $past(aligned_addr))
      |-> ({channel_index, rank_index, row_operand, ba_operand,
            bg_operand, col_operand}
           != $past({channel_index, rank_index, row_operand, ba_operand,
                     bg_operand, col_operand}));
endproperty
assert property (p_distinct_addresses_distinct_fields);

// P3 -- the offset is excluded from the aligned address, which is what
// makes P1's target well defined. Without this, a round trip could "pass"
// against an original that had already been corrupted.
property p_aligned_addr_clears_offset;
  @(posedge clk)
    (aligned_addr[OFFSET_W-1:0] == '0)
      && (aligned_addr[ADDR_W-1:OFFSET_W] == sys_addr[ADDR_W-1:OFFSET_W]);
endproperty
assert property (p_aligned_addr_clears_offset);

// P4 -- reconstruction requires a known row, and produces no address
// without one. The property that keeps a monitor from emitting confident
// nonsense, and the one Chapter 7.4's known-bits exist to support.
property p_no_reconstruction_without_state;
  @(posedge clk)
    !tracked_row_known |-> !recon_valid;
endproperty
assert property (p_no_reconstruction_without_state);

// P5 -- CROSS-MODEL AGREEMENT. Two independently parameterised mappers
// fed the same address must produce the same tuple. NOTE THE SCOPE: unlike
// every other property here, this one lives where BOTH instances are
// visible -- a testbench or a bind into a common parent -- because it is
// the one claim that cannot be made about a single map. This is the property
// five chapters have deferred to this one, and note what it requires: a
// SECOND map. No property over a single map can express it.
property p_two_maps_agree;
  @(posedge clk)
    (dut_map.channel_index == ref_map.channel_index)
      && (dut_map.rank_index  == ref_map.rank_index)
      && (dut_map.row_operand == ref_map.row_operand)
      && (dut_map.ba_operand  == ref_map.ba_operand)
      && (dut_map.bg_operand  == ref_map.bg_operand)
      && (dut_map.col_operand == ref_map.col_operand);
endproperty
assert property (p_two_maps_agree);

What these prove. P1 is the module's best single property. P2 closes the gap P1 leaves — an overlap can cancel for particular addresses, and injectivity across a transition catches what a single-address reassembly does not. P3 makes P1's comparison target well defined. P4 is the monitor's honesty contract. P5 is the only property here that spans two components, and it is the one that catches the failure mode this module has described five times: two lossless maps that disagree.

What they do not prove. None of them says the map is right for the device. P1 to P3 would all pass for a map describing 32 banks on a 16-bank part. None says the map is goodModule 18. None says a command is legal to issueChapter 7.3 for state, Modules 13 and 14 for timing. P4 proves the monitor refuses to guess, not that its state model is correct: a confidently stale row satisfies P4 and produces a wrong address, which is exactly why §6's cycle 5 is in the trace. And P5 proves two maps agree with each other, not that either agrees with the silicon — that is settled at integration, by reading and writing real memory.

8. DV — Two Maps, One System

Every chapter in this module has ended up here, so it is worth stating the problem completely.

A DDR verification environment contains at least two address maps, and usually three. The DUT's, the reference model's, and — the one that causes the most trouble — an implicit third inside the test, wherever a test computes an expected address itself.

All three can be lossless. All three can pass every property in §7 except P5. And any two of them disagreeing produces the same symptom: data corruption with a clean protocol trace.

The structural fix, in order of value:

Single-source the map. One parameter set, one module, instantiated wherever a decomposition is needed. The DUT's instance and the reference model's instance should be the same module with the same parameters, and if that is impossible for methodology reasons, then P5 must exist. This is the whole of the advice, and everything below is mitigation for not having done it.

Make the parameters loud at elaboration. Print map_bits, every field's LSB, and the capacity product at time zero of every simulation. A mismatch then appears as two differing banners rather than as corruption a thousand transactions later.

Never let a test compute an address. If a test needs an expected coordinate, it should ask the map, not recompute it. The recomputed version is a third map, written once and never reviewed.

Cover the configuration space, not just the address space. A regression that elaborates one policy, one generation, one rank count and one channel count has verified one map. §3's g_no* branches — DDR3, single rank, single channel — are the least-tested code in this module and the most likely to be wrong, because they are the paths a well-equipped development board never exercises.

Check reconstruction against the request, transaction by transaction. This is the highest-value check in a DDR environment: take the request's system address, take the monitor's recon_addr, and compare to reconstruction granularity. If they agree for every transaction, every layer between them is behaving — map, encoder, interface, decoder and state model. If they diverge, the divergence pattern localises the fault, which is §9.

And check the granularity, not the address. A comparison of recon_addr against a byte address will fail on every unaligned request for no reason at all. Compare recon_addr against aligned_addr, and treat recon_granularity_bytes as part of the contract.

9. Debugging — Aliasing at a Power of Two

Symptom. Data corruption with a structured signature: writes to addresses X and X + 2^N overwrite each other. Reads return the other one's data. The protocol trace is clean and every command is well-formed.

That signature is a gift, and recognising it is the most transferable skill in this module: structured aliasing at a power-of-two interval means an address bit is not reaching the field it belongs to. The interval names the bit.

Candidate mechanisms.

  1. A field is too narrow, so its top bit is never used — the map's reach is smaller than the memory installed, and the unreachable half aliases onto the reachable half.
  2. A bit is dropped between two fields — a gap — so addresses differing only in that bit produce identical tuples.
  3. Two fields overlap, so one bit contributes to both and the map is not injective.
  4. The DUT and the reference model use different maps, and the aliasing is an artefact of the comparison rather than of the hardware.
  5. addr_above_device is being ignored, so addresses beyond the map's reach are silently truncated into it.

Evidence to collect. The aliasing interval, expressed as a power of two. Both models' full field tuples for one aliasing pair. map_bits and the capacity product. roundtrip_ok and addr_above_device for the failing addresses. The interval and map_bits are enough to reach a hypothesis before any waveform is opened.

Discriminator.

  • Compute 2^map_bits and compare it with the installed memory size. If the map is smaller, mechanism 1 or 5, and the aliasing interval will equal 2^map_bits for mechanism 5 specifically — the classic "top of memory wraps to the bottom."
  • Check roundtrip_ok on an aliasing pair. Low means the map is internally broken — mechanism 2 or 3, and the reassembly's difference from the original distinguishes them: a gap loses the bit, so the reassembly is smaller by exactly 2^N; an overlap contributes it twice, so the reassembly is larger, often carrying into a neighbouring field.
  • If roundtrip_ok is high for both addresses, the map is self-consistent — so mechanism 4. Compare the two models' tuples and apply §5's fingerprint: column, rank and channel agreeing while the middle fields differ is a policy mismatch.
  • Locate the bit. The aliasing interval is 2^N; find which field's range contains bit N, and check that field's LSB and width against the declared layout. For mechanism 2, bit N will be in no field's range, which is the definition of a gap and is immediately obvious once you look for it.
  • Check addr_above_device on the higher address of the pair. Asserting means the map is correctly reporting that the address is out of reach and something downstream ignored it — mechanism 5, and the fault is the consumer, not the map.

Responsible layer. Mechanisms 1, 2, 3 and 5 are layer B, in this chapter's blocks or their parameters. Mechanism 4 is layer B duplicated — two components, each internally correct. None is a DDR protocol defect, and the clean trace is the positive evidence for that: the interface did exactly what it was told.

Fix. Correct the field layout or the parameters; then enable P1 and P5, print the banner from §8, and add a directed aliasing test at the discovered interval so that a reintroduction fails on the first transaction instead of as corruption.

10. Common Misconceptions

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

Why it is tempting: DDR specifies so much else precisely, and every platform's documentation presents its own map as the map.

Concrete failure: a reference model written against one platform's published map reports corruption on the next platform with no bug in either.

Correct model: the map is a controller policy. §5 shows two correct maps disagreeing about three fields for the same address.

Prevention: treat the map as configuration, single-sourced, with P5 to catch divergence.

"A lossless mapping is a correct mapping."

Why it is tempting: the round trip is a strong, satisfying check, and passing it feels conclusive.

Concrete failure: a map describing 32 banks on a 16-bank device passes every round trip and addresses banks that do not exist. Or two lossless maps disagree and corruption appears with a clean trace.

Correct model: losslessness is internal coherence. §2's list enumerates the five things it does not prove.

Prevention: check the capacity product against the device, and cross-check against a second map.

"An address-mapper RTL block models DRAM internals."

Why it is tempting: it outputs rows, banks and columns, which are physical things.

Concrete failure: an engineer expects the mapper to reject an illegal access or to know what row is open, and is surprised that it does neither — it cannot, because it holds no state and sees no device.

Correct model: it is layer-B arithmetic. Module 3 owns the array, Chapter 5.2 owns state, Chapter 7.3 owns legality.

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

"A monitor can reconstruct the exact system address of every access."

Why it is tempting: it can reconstruct most of it, and the result looks like a complete address.

Concrete failure: a scoreboard compares byte addresses, and every unaligned request appears to fail. Or worse, the comparison is made to pass by masking, and a genuine bug is masked with it.

Correct model: reconstruction is exact to one transfer — 64 bytes on the generations discussed — because the low bits never crossed the interface. recon_granularity_bytes is part of the result.

Prevention: compare at reconstruction granularity, and get byte-level intent from the requester side if you need it.

"If the commands are legal, the addressing is correct."

Why it is tempting: protocol checkers are thorough and their passing feels like broad reassurance.

Concrete failure: the recurring one in this module. Every command legal, every operand correctly encoded and decoded, the state model coherent — and the data in the wrong place because the map was wrong.

Correct model: protocol correctness is layer D. The map is layer B, and no observation of the interface can validate it, because the interface never sees a system address.

Prevention: reconstruct and compare against the request. That is the only check that spans the layers.

"The offset bits are just discarded, so they do not matter."

Why it is tempting: they reach no DDR operand, so dropping them seems harmless.

Concrete failure: a byte write becomes a 64-byte write, destroying 63 bytes of a neighbour's data — and the DDR commands are all legal.

Correct model: those bits carry real intent that DDR cannot express, so something must answer for them: data masking on writes, extraction at the requester on reads. Reaching no operand is not the same as being irrelevant.

Prevention: offset_nonzero is an output for this reason. A consumer must handle it, not ignore it.

11. Interview Reasoning

"How would you verify an address mapper?"

With a round trip, and then with a second map. The round trip — decompose an aligned address, reassemble it from the fields, demand the original — is one property that catches a dropped bit, an overlapping slice, a gap and a permuted concatenation, none of which is visible to per-field inspection. Reassemble by shifting and adding rather than concatenating, because a concatenation is correct by construction and proves nothing. Then check the capacity product against the actual device, because a lossless map can still describe the wrong part. And then the part people miss: cross-check against an independently parameterised reference map, because two different lossless maps are both lossless and their disagreement is the failure mode that presents as corruption with a clean protocol trace.

"How would you detect a dropped address bit?"

By the aliasing interval. A dropped bit makes addresses differing only in that bit map to the same coordinate, so writes to X and X + 2^N overwrite each other — and the interval names the bit. Then check which field's declared range contains bit N: for a dropped bit, none does, which is the definition of a gap. A round-trip check finds it automatically, and the reassembly comes out smaller than the original by exactly 2^N, which distinguishes a gap from an overlap — an overlap contributes a bit twice and comes out larger.

"What information does a monitor need to reconstruct a system address, and what can it never have?"

Four sources and one permanent gap. From the command: the column, bank and bank-group operands. From the chip-selects, not the command: the rank. From its own per-rank state model: the row, because a column command carries none. From configuration: the channel, because nothing observable identifies it. And it can never have the low-order offset bits, because they reach no DDR mechanism at all — so reconstruction is exact to one transfer, 64 bytes, and a scoreboard must compare at that granularity.

"Two controllers with the same DRAM use different maps. Is one wrong?"

No. The device never sees a system address — it receives operands and cannot know which address bits produced them. Both maps are correct, and they may perform very differently on the same workload, which is a separate question from correctness and is settled by measurement. What is wrong is two components of one system using different maps, and that is a verification and configuration problem rather than a design one.

"Your scoreboard reports corruption and every protocol check passes. Walk me through it."

The clean protocol trace is evidence, not a mystery: it says the interface behaved, so the fault is above it. I would reconstruct the address from the observed commands — operands, rank from CS#, row from the state model, channel from configuration — and compare it with the request's aligned address. If they agree, addressing is exonerated and I look at the data path. If they disagree, the pattern of disagreement localises it: aliasing at a power of two means a bit is not reaching its field; column, rank and channel agreeing while row, bank and group differ means two components were compiled with different policies; and correct operands with a wrong row means the state model has drifted, which is not an addressing bug at all.

12. Engineering Exercise

Config: ADDR_W = 32, OFFSET_W = 6, NUM_COLS = 16, GEN = 4, NUM_GROUPS = 4, BANKS_PER_GROUP = 4, ROW_W = 14, NUM_RANKS = 2, NUM_CHANNELS = 2.

1. Give every field's position under both policies and confirm MAP_W by capacity.

2. Decompose 0x1A94_3680 under POLICY 0 and verify by reassembly.

3. A colleague sets ROW_W = 15 and changes nothing else. What is MAP_W, and what now fails?

4. A map has COL_LSB = 6, COLUMN_W = 4, and BG_LSB = 11. What bug is this, at which bit, and what aliasing interval would you observe?

5. A monitor observes RD rank 1, bg 0, ba 3, col 9 and its state model holds row 0x0A5C for {rank 1, bank {0,3}}, with cfg_channel = 0. Reconstruct the address and state its granularity.

6. Reconfigure for DDR3, single rank, single channel, 8 banks, and give the full layout. Which RTL branches does this configuration exercise that the default does not?

13. Summary

The map is seven fields with four destinations. Twenty-four of thirty mapped bits become operands on the command/address bus; one becomes a chip-select; one selects which interface exists; six reach nothing at all. And of the twenty-four, the row travels on a different command from the rest.

Confirm a map by capacity, not by reading slices. The product of bytes per column access, columns, rows, banks, ranks and channels must equal 2^MAP_W. If it does not, there is a gap or an overlap, and the arithmetic finds it faster than inspection.

One round trip catches four bug classes. Decompose, reassemble by shifting and adding, demand the aligned address back — and a dropped bit, an overlapping slice, a gap and a permuted order all fail it. Reassemble with addition rather than concatenation, because a concatenation is correct by construction and proves nothing.

Losslessness is internal coherence and nothing more. A lossless map can be wrong for the device, terrible for the workload, in disagreement with another component, or a perfect description of the wrong thing. Two different lossless maps are both lossless — which is why the one property that matters most spans two maps, not one.

The inverse is partial, and permanently so. A monitor reconstructs an address from operands, a rank taken from chip-selects, a row taken from its own state model, and a channel taken from configuration — and it can never recover the transfer offset, because those bits never crossed the interface. Reconstruction is exact to one transfer, and a model that cannot support one must produce no address at all rather than a confident guess.

And the aliasing interval names the bit. Structured corruption at a power-of-two interval means an address bit is not reaching the field it belongs to; the interval identifies the bit, and whether the reassembly comes out smaller or larger distinguishes a gap from an overlap. That single technique will localise more addressing bugs than any waveform.

14. What Comes Next

Module 8 is complete, and the module's claim is worth stating in its final form.

Module 5 asked what structures exist. Module 6 asked what crosses the interface. Module 7 asked what operation is requested. This module asked what location that operation targets, and the answer was not a number.

An address in a DDR system is transformed — by a controller policy nothing in the protocol specifies — into fields whose meaning depends on the command carrying them, sent across two different commands and three different kinds of wire, and partially stored in the device rather than transmitted at all. There is no single value that is "the DDR address," and the closest thing to one exists only inside a controller.

Module 9 — Activate and Precharge takes the two commands this module has leaned on hardest and makes them physical. Chapter 8.1 said the row is deposited into state by an activate and Chapter 8.2 said a column command inherits it — Module 9 asks what that actually costs. Row opening, the row buffer as a cache of one row, and the hit, miss and conflict cases that make the address map from Chapter 8.3 matter in measurable ways.

And the mapping question deferred throughout — which map is better — is Module 18's, where it is answered the only way it can be: against real traffic.


Return to Row Address for the five layers, Column Address for the temporal split, Bank Address and Bank-Group Address for the placement choices, Rank Selection for the four destinations, Banks for the per-rank state this chapter's inversion depends on, and Columns for the two-field decomposition this chapter generalises.

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.