Skip to content
VLSI Mentor

DDR · Module 18

Mapping for Performance

The same mapping policy scores 93.8 percent row hits on one address stream and zero on another. Mapping drives hit rate completely — which is exactly why hit rate alone cannot be used to choose a mapping.

Chapter 18.1 established the mechanism: the field order decides what changes on the next access. This chapter measures the consequence, and the measurement produces a result that looks like a contradiction until you look closely.

Address mapping drives the row-hit rate completely. And that is precisely why the row-hit rate cannot be used to choose a mapping.

Both halves of that sentence are demonstrated below on one fixed set of address streams, and the second half is the one that matters in review meetings.

1. Three Streams, Two Policies

Keep 18.1's configuration and its two policies. Take three address streams, sixteen accesses each, all starting at 0x00000.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  EDUCATIONAL CONFIGURATION — NOT A JEDEC-MANDATED ADDRESS MAP

  S1   stride     64 B   sequential access granules
  S2   stride  1 KiB     2^10 — a plausible structure or tile stride
  S3   stride 16 KiB     2^14 — a larger power-of-two stride

Every access is classified against the target bank's currently open row using Chapter 9.3's taxonomy, with all banks starting closed and requests evaluated in program order — no reordering, no scheduler.

StreamPolicyHITCONFLICTCLOSEDhit ratedistinct banks
S1 seq 64 BR150193.8 %1 of 16
S1 seq 64 BB00160.0 %16 of 16
S2 stride 1 KiBR00160.0 %16 of 16
S2 stride 1 KiBB150193.8 %1 of 16
S3 stride 16 KiBR01510.0 %1 of 16
S3 stride 16 KiBB01510.0 %1 of 16

The last two rows are the more uncomfortable result. On S3, both policies collapse to one bank with fifteen row conflicts. Reordering the fields did not help, because a 16 KiB stride leaves every bit below 14 unchanged — and in both policies every middle field lives below bit 14. No permutation of those fields can fix a stride that never touches them. Chapter 18.3 is about what can.

2. The Two Quantities, Together

Chapter 16.3 proved that row-hit rate and bank-level parallelism are different quantities and can move in opposite directions. §1 is that proof restated at the mapping layer, and it forces a measurement discipline:

A mapping must be evaluated on at least two numbers at once, and a single scalar is always misleading.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                     high bank spread

              S1 / Policy B │  (the sweet spot lives here, and
              0 % hits      │   no mapping in this chapter
              16 banks      │   reaches it on these streams)

   low hit rate ◄───────────┼───────────► high hit rate

              S3 / both     │  S1 / Policy R
              0 % hits      │  93.8 % hits
              1 bank        │  1 bank
              15 conflicts  │

                     low bank spread

Three of the four quadrants are occupied by results from §1, and the fourth — high hits and high spread — is not reachable by any field ordering on these particular streams. That is worth stating plainly rather than implying it is achievable with a cleverer permutation: on a stream whose varying bits are narrow, the fields those bits select are the only fields in play.

3. Which Bits Does the Workload Vary?

§1's table invites a question it does not answer: what property of a stream decides which policy suits it? The answer is a single rule, and it makes the whole table predictable rather than surprising.

A stream exercises exactly the fields whose bit positions its varying address bits overlap.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  stream    varying bits   Policy R field there   Policy B field there
  ───────   ────────────   ────────────────────   ────────────────────
  S1  64 B     [9:6]           column                 bank + bank group
  S2   1 KiB   [13:10]         bank + bank group      column
  S3  16 KiB   [21:14]         row                    row

Every number in §1 follows from that table. S1 varies bits 6 through 9; under Policy R those are column bits, so the stream sweeps columns inside one row — 93.8 % hits, one bank. Under Policy B the same bits are bank and bank-group bits, so the stream sweeps banks — 0 % hits, sixteen banks. S2 varies bits 10 through 13, and the two policies swap roles exactly, which is why the two halves of the table mirror.

S3 varies only bits 14 and above. Both policies put the row field there, so both see a pure row sweep in whichever bank the untouched middle bits select — bank 0, since those bits are all zero. Fifteen conflicts, one bank, under either order.

Two consequences worth stating before the instrument is built.

Real workloads vary many bits at once. S1 to S3 are single-stride streams chosen so each isolates one field. A real stream mixes strides, and the useful question becomes which bits vary most often — the low varying bits dominate, because they change on nearly every access while high bits change rarely.

A stream with few varying bits is the dangerous case. If only four bits vary, only the fields at those four positions are ever exercised, and every other resource is unreachable by that stream however the fields are ordered. S3 is the extreme: its varying bits sit entirely in the row field, so no reordering of the middle can reach it. Chapter 18.3 is where that limit gets addressed.

4. One Trace, End to End

§1 counted outcomes. This traces the causal chain 18.1 §2's diagram drew, for a stream more realistic than a single stride: two regions accessed alternately, which is what a copy loop or a two-buffer producer-consumer pattern produces.

The stream interleaves accesses near 0x00000 and near 0x04000 — one row span apart in this configuration.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  EDUCATIONAL — classification and command sequence only. No cycle
  counts and no timing claim; Modules 13 and 14 own the magnitudes,
  Module 17 owns what a scheduler does with these opportunities.

  POLICY R
  #  PA        row  bg ba  bank  col  prior open   relation   next command
  -  -------   ---  -- --  ----  ---  ----------   --------   -----------------
  0  0x00000    0    0  0    0     0  closed       CLOSED     ACT -> RD
  1  0x00040    0    0  0    0     1  row 0        HIT        RD
  2  0x00080    0    0  0    0     2  row 0        HIT        RD
  3  0x000C0    0    0  0    0     3  row 0        HIT        RD
  4  0x04000    1    0  0    0     0  row 0        CONFLICT   PRE -> ACT -> RD
  5  0x04040    1    0  0    0     1  row 1        HIT        RD
  6  0x00100    0    0  0    0     4  row 1        CONFLICT   PRE -> ACT -> RD
  7  0x04080    1    0  0    0     2  row 0        CONFLICT   PRE -> ACT -> RD

     totals: 4 HIT, 3 CONFLICT, 1 CLOSED    banks touched: 1

  POLICY B
  #  PA        row  bg ba  bank  col  prior open   relation   next command
  -  -------   ---  -- --  ----  ---  ----------   --------   -----------------
  0  0x00000    0    0  0    0     0  closed       CLOSED     ACT -> RD
  1  0x00040    0    0  1    1     0  closed       CLOSED     ACT -> RD
  2  0x00080    0    0  2    2     0  closed       CLOSED     ACT -> RD
  3  0x000C0    0    0  3    3     0  closed       CLOSED     ACT -> RD
  4  0x04000    1    0  0    0     0  row 0        CONFLICT   PRE -> ACT -> RD
  5  0x04040    1    0  1    1     0  row 0        CONFLICT   PRE -> ACT -> RD
  6  0x00100    0    1  0    4     0  closed       CLOSED     ACT -> RD
  7  0x04080    1    0  2    2     0  row 0        CONFLICT   PRE -> ACT -> RD

     totals: 0 HIT, 3 CONFLICT, 5 CLOSED    banks touched: 5

Both policies produce exactly three conflicts. A hit-rate comparison would call Policy R the clear winner at 50 % against 0 %.

Where this hands off. The right-hand column of both tables is the input to Chapter 17.1: outstanding requests, each with a next required command derived from current bank state. The scheduler decides which issues. The mapping decided what the set looks like — and on this stream Policy R handed the scheduler a set with no parallelism to find, while Policy B handed it five banks to work with.

5. What the Profile Measures, and What It Cannot

Before building the instrument, be precise about its claim, because the boundary is what keeps this chapter out of Module 23.

What it measures. Given a decoded address stream, in program order, with a modelled per-bank open row: the class of every access, the totals, and the set of banks touched. Every number is a property of (stream × mapping) and of nothing else.

What it cannot measure. Latency. Bandwidth. Throughput. Anything a scheduler would change — and a scheduler changes a great deal, because it may reorder requests, overlap activations, and turn a pessimistic program-order classification into a much better outcome. The profile deliberately has no queue, so its classification is the program-order one.

Why that restriction is a feature. If the instrument contained a scheduler, every result would be a joint property of the mapping and the scheduling policy, and no conclusion could be attributed to the mapping alone. Chapter 16.3 measures the post-scheduler reality from the command stream; this measures the pre-scheduler opportunity from the address stream. Both are needed, and they are not the same measurement.

So the honest formulation of every result in §1 is: this mapping presents this class distribution and this bank spread to the controller. What the controller then achieves is Module 17's and Module 23's business.

6. The Measurement Path

The measurement path this chapter builds, and the two other instruments it sits between. An address stream enters on the left and passes through the mapper of chapter eight point six, which produces decoded bank and row fields. Those feed the stream profile block of this chapter, which models a per-bank open row in program order with no scheduler, and publishes two quantities side by side: the class distribution of hits, closed banks and conflicts, and the set of distinct banks touched. Below, two existing instruments are shown for contrast. Chapter nine point six's row class counters measure the class distribution of the request stream, and chapter sixteen point three's bank concurrency observer measures the command stream after the scheduler has run. This chapter's block is distinguished by measuring before any scheduler exists, so that every result is attributable to the mapping alone.Address streamprogram orderMapper8.6 — reusedStream profilethis chapterClass distributionhit / closed / conflictBanks touchedmask + popcountNO schedulerattributable to mappingrow_class_counters9.6 — request streambank_concurrency_observer16.3 — after scheduler12

The bottom row is the boundary drawn as a picture: two instruments already exist, and both sit downstream of a scheduler. Neither can answer “what did the mapping do?” because by the time they observe, the scheduler has already changed the order.

7. The Profile Block

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────
// mapping_stream_profile
//
// CLASSIFICATION
//   Synthesizable educational RTL. Sequential. One responsibility:
//   given a DECODED address stream in PROGRAM ORDER, report the class
//   distribution and the bank spread that a mapping produces.
//
//   It is a MAPPING INSTRUMENT, not a controller component. Its value
//   is that it contains no scheduler, so every number it reports is a
//   property of (stream x mapping) and of nothing else.
//
// WHAT IT DOES NOT MODEL
//   - No scheduler, queue, arbiter or reordering. Module 17 owns those,
//     and including any of them would make the output a joint property
//     of mapping AND policy, which is the measurement error this block
//     exists to avoid (§5).
//   - No address decode. Chapter 8.6's sys_addr_mapper feeds it.
//   - No timing. No tRCD, tRP, tRAS, tCCD; it counts classes, not
//     cycles, and makes no latency or bandwidth claim.
//   - Not a replacement for Chapter 9.6's row_class_counters, which
//     instruments the REQUEST stream, nor for Chapter 16.3's
//     bank_concurrency_observer, which instruments the COMMAND stream
//     after scheduling. Different vantage point, different question.
//   - No page policy. Banks stay open until something else opens that
//     bank; closed-page behaviour would be a different instrument.
// ─────────────────────────────────────────────────────────────────────
module mapping_stream_profile #(
  parameter int NUM_BANKS = 16,
  parameter int ROW_W     = 8,
  // Report-only width. Nothing compares against these counters, so a
  // saturation can understate a total but cannot cause a wrong class.
  parameter int CNT_W     = 16,
  parameter int BK_W  = (NUM_BANKS <= 1) ? 1 : $clog2(NUM_BANKS),
  // SPREAD IS A COUNT, NOT AN INDEX. Touching all NUM_BANKS banks must
  // be representable, so the counter needs NUM_BANKS+1 distinct values.
  // Sizing it $clog2(NUM_BANKS) truncates "all banks" to zero for every
  // power-of-two bank count -- which is every real bank count.
  parameter int SPR_W = $clog2(NUM_BANKS + 1)
) (
  input  logic                    clk,
  input  logic                    rst_n,

  // ── One decoded access per cycle, in PROGRAM ORDER.
  input  logic                    acc_valid,
  input  logic [BK_W-1:0]         acc_bank,
  input  logic [ROW_W-1:0]        acc_row,

  // ── Restart the measurement window without a full reset, so two
  //    policies can be profiled back to back in one simulation.
  input  logic                    window_restart,

  // ── Per-access classification, as one-cycle pulses. Chapter 9.3's
  //    taxonomy, applied -- not redefined.
  output logic                    acc_hit,
  output logic                    acc_closed,
  output logic                    acc_conflict,

  // ── Quantity one: the class distribution.
  output logic [CNT_W-1:0]        hit_count,
  output logic [CNT_W-1:0]        closed_count,
  output logic [CNT_W-1:0]        conflict_count,
  output logic [CNT_W-1:0]        total_count,

  // ── Quantity two: the bank spread. Published as BOTH a mask and a
  //    count, because §2's discipline is that one number is always
  //    misleading and the mask is what a debugger actually wants.
  output logic [NUM_BANKS-1:0]    banks_touched_mask,
  output logic [SPR_W-1:0]        banks_touched_count,

  // ── The §1 pathology as a flag: many accesses, one bank.
  output logic                    concentrated,

  // ── Design error: a decoded bank outside the configured device.
  output logic                    err_bank_out_of_range
);

  if (NUM_BANKS < 1) $fatal(1, "mapping_stream_profile: NUM_BANKS must be >= 1");
  if (ROW_W     < 1) $fatal(1, "mapping_stream_profile: ROW_W must be >= 1");
  if (CNT_W     < 2) $fatal(1, "mapping_stream_profile: CNT_W must be >= 2");

  // ── The modelled device state. Deliberately minimal: an open flag
  //    and an open row per bank. Chapter 5.2's ddr_bank_state_table is
  //    the real model; this is the subset a classification needs, and
  //    reusing the full table would drag timing in with it.
  logic [NUM_BANKS-1:0]            bank_open;
  logic [NUM_BANKS-1:0][ROW_W-1:0] bank_row;

  logic in_range;
  assign in_range = (int'(acc_bank) < NUM_BANKS);
  assign err_bank_out_of_range = acc_valid && !in_range;

  // ── Classification. Combinational, so a waveform lines the class up
  //    with the access that caused it.
  always_comb begin
    acc_hit      = 1'b0;
    acc_closed   = 1'b0;
    acc_conflict = 1'b0;
    if (acc_valid && in_range) begin
      if (!bank_open[acc_bank])                 acc_closed   = 1'b1;
      else if (bank_row[acc_bank] == acc_row)   acc_hit      = 1'b1;
      else                                      acc_conflict = 1'b1;
    end
  end

  // ── Spread as a popcount over the touched mask. Recomputed rather
  //    than incremented so the two representations cannot drift; §9's
  //    P3 asserts they agree, which would be vacuous if one were
  //    derived from the other by construction at the same instant.
  always_comb begin
    banks_touched_count = '0;
    for (int b = 0; b < NUM_BANKS; b++)
      if (banks_touched_mask[b])
        banks_touched_count = banks_touched_count + SPR_W'(1);
  end

  // ── The pathology flag. Guarded on a minimum sample so a window that
  //    has seen one access does not report itself as concentrated.
  assign concentrated = (total_count > CNT_W'(4))
                     && (banks_touched_count == SPR_W'(1));

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      bank_open          <= '0;
      banks_touched_mask <= '0;
      hit_count          <= '0;
      closed_count       <= '0;
      conflict_count     <= '0;
      total_count        <= '0;
      for (int b = 0; b < NUM_BANKS; b++) bank_row[b] <= '0;
    end else if (window_restart) begin
      // Counters and the touched mask clear; THE MODELLED DEVICE STATE
      // DOES NOT. A restart begins a new measurement window on a device
      // that is still in whatever state the previous window left it,
      // which is what makes back-to-back profiling honest. Clearing
      // bank_open here would fabricate a cold device and inflate the
      // CLOSED count of every window after the first.
      banks_touched_mask <= '0;
      hit_count          <= '0;
      closed_count       <= '0;
      conflict_count     <= '0;
      total_count        <= '0;
    end else if (acc_valid && in_range) begin
      bank_open[acc_bank]          <= 1'b1;
      bank_row[acc_bank]           <= acc_row;
      banks_touched_mask[acc_bank] <= 1'b1;

      if (!(&total_count))    total_count    <= total_count + CNT_W'(1);
      if (acc_hit      && !(&hit_count))      hit_count      <= hit_count      + CNT_W'(1);
      if (acc_closed   && !(&closed_count))   closed_count   <= closed_count   + CNT_W'(1);
      if (acc_conflict && !(&conflict_count)) conflict_count <= conflict_count + CNT_W'(1);
    end
  end

endmodule

Simulating it. Drive acc_valid with one decoded access per cycle from 8.6's mapper, run S1's sixteen addresses, read the counters, pulse window_restart, reparameterise the mapper to the other policy, and run the same sixteen again. The expected output is §1's table, row by row — and the fact that the device state survives the restart is why the second window's CLOSED count is honest rather than inflated.

Synthesis. bank_open and bank_row are NUM_BANKS × (1 + ROW_W) flops — 144 for the chapter's configuration. The popcount is the only structure that grows awkwardly: an adder tree of depth log2(NUM_BANKS), recomputed every cycle. For an instrument that is fine; in a design where it mattered, the count would be maintained incrementally on the mask's rising edges, at the cost of the independence §9's P3 relies on.

8. The Trap, in Cycles

The most valuable single output of the instrument, shown against S2 under Policy B — the row that scored 93.8 %.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  EDUCATIONAL — classification only, no timing implied.

  cyc  PA       bank  row  col  class      hits  banks_mask          spread
  ───  ───────  ────  ───  ───  ─────────  ────  ──────────────────  ──────
   0   0x00000    0    0    0   CLOSED       0   0000000000000001      1
   1   0x00400    0    0    1   HIT          1   0000000000000001      1
   2   0x00800    0    0    2   HIT          2   0000000000000001      1
   3   0x00C00    0    0    3   HIT          3   0000000000000001      1
   4   0x01000    0    0    4   HIT          4   0000000000000001      1
   5   0x01400    0    0    5   HIT          5   0000000000000001      1
   6   0x01800    0    0    6   HIT          6   0000000000000001      1
   7   0x01C00    0    0    7   HIT          7   0000000000000001      1
  ...
  15   0x03C00    0    0   15   HIT         15   0000000000000001      1

  FINAL   hit 15   closed 1   conflict 0   total 16   spread 1
          hit rate 93.8 %     concentrated = 1

concentrated and a 93.8 % hit rate assert on the same window. A dashboard reporting only the hit rate would show the best number in this entire chapter; the mask shows a single bit set.

That single bit is the finding. Fifteen row hits in one bank are fifteen column commands to one bank, spaced by tCCD_L and strictly serialised — Chapter 16.4 verified that the same-group column spacing reaches twice the different-group spacing at the fastest DDR4 grade it examined. Meanwhile fifteen of sixteen banks are idle.

9. What the Assertions Prove

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── P1. Exactly one class per valid, in-range access. The taxonomy is
//    a partition: an access cannot be both a hit and a conflict, and
//    it cannot be unclassified. Catches a classifier whose conditions
//    overlap or leave a gap.
property p_one_class_per_access;
  @(posedge clk) disable iff (!rst_n)
    (acc_valid && int'(acc_bank) < NUM_BANKS)
      |-> ($countones({acc_hit, acc_closed, acc_conflict}) == 1);
endproperty
a_one_class_per_access: assert property (p_one_class_per_access);

// ── P2. The totals account for every access. If the three class
//    counters stop summing to the total, a class was dropped or
//    double-counted -- and every percentage in §1 becomes wrong while
//    still looking plausible.
property p_counts_sum;
  @(posedge clk) disable iff (!rst_n)
    total_count == (hit_count + closed_count + conflict_count);
endproperty
a_counts_sum: assert property (p_counts_sum);

// ── P3. The two representations of spread agree. The mask is the
//    ground truth and the count is the summary; §7 recomputes the
//    count from the mask each cycle rather than incrementing it, so
//    this catches a popcount that saturates or a SPR_W sized as an
//    index instead of a count.
property p_spread_matches_mask;
  @(posedge clk) disable iff (!rst_n)
    banks_touched_count == SPR_W'($countones(banks_touched_mask));
endproperty
a_spread_matches_mask: assert property (p_spread_matches_mask);

// ── P4a. An unopened bank must classify as CLOSED.
//    NOTE ON THE SHAPE. The antecedent keys on bank_open, NOT on
//    banks_touched_mask. A window_restart clears the mask by design
//    while deliberately preserving bank_open (§7), so a "first touch
//    implies CLOSED" property would fire on the first access of every
//    window after the first -- on entirely correct behaviour.
property p_unopened_is_closed;
  @(posedge clk) disable iff (!rst_n)
    (acc_valid && int'(acc_bank) < NUM_BANKS
     && !bank_open[acc_bank])
      |-> acc_closed;
endproperty
a_unopened_is_closed: assert property (p_unopened_is_closed);

// ── P4b. The measurement window may restart, but the MODELLED DEVICE
//    STATE must survive it. This is §7's comment made checkable, and
//    it is what actually catches a restart that wrongly zeroes the
//    device -- the failure that inflates every later window's CLOSED
//    count while looking entirely plausible.
property p_device_state_survives_restart;
  @(posedge clk) disable iff (!rst_n)
    window_restart |=> $stable(bank_open);
endproperty
a_device_state_survives_restart: assert property (p_device_state_survives_restart);

// ── P5. The touched mask is monotone within a window: banks are added,
//    never removed, except by an explicit restart.
property p_mask_monotone;
  @(posedge clk) disable iff (!rst_n)
    !window_restart
      |=> (banks_touched_mask & $past(banks_touched_mask, 1))
          == $past(banks_touched_mask, 1);
endproperty
a_mask_monotone: assert property (p_mask_monotone);

// ── Covers. §1's three interesting outcomes must actually occur, or
//    the properties above are checking an empty space.
c_full_spread:   cover property (@(posedge clk) disable iff (!rst_n)
                   banks_touched_count == SPR_W'(NUM_BANKS));
c_concentrated:  cover property (@(posedge clk) disable iff (!rst_n)
                   concentrated);
c_conflict_run:  cover property (@(posedge clk) disable iff (!rst_n)
                   acc_conflict [*4]);

What they prove. That the instrument counts what it claims: the taxonomy partitions, the totals balance, the two spread representations agree, an unopened bank cannot be reported as a hit, and a window restart does not silently reset the device being measured.

What they do not prove. Nothing here says the mapping is good — P1 through P5 pass identically on every row of §1's table, including the pathological ones, which is the correct behaviour for an instrument. Nothing says the classification matches what the device would see under a real controller: the profile is program-order by construction, and a scheduler would legitimately produce a different and usually better distribution (§5). And nothing establishes any timing or throughput consequence whatsoever.

c_concentrated is the cover that matters. If it never hits, the test suite has never run a stream that aliases with the bank field — and §1's entire lesson is unexercised. c_conflict_run plays the same role for S3.

10. DV — Two Maps, One Stream

The independent model must not reuse the DUT's classification. The mapping side is already covered by Chapter 8.6 §8's rule; the addition here concerns the profile.

Build the reference as a dictionary from bank index to open row, in a scripting sense rather than as an array with a valid mask — a genuinely different representation, so an indexing error in the RTL cannot be mirrored. Replay the same address stream, classify each access by dictionary lookup, and accumulate into plain integers. Then compare four numbers and one mask.

The comparison that earns its place is not a single run but the same stream under both policies:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  MAPPING PROFILE COMPARISON          stream S2, 16 accesses, stride 1 KiB
    policy            R                    B
    hit                0                  15
    closed            16                   1
    conflict           0                   0
    total             16                  16
    hit rate         0.0 %              93.8 %
    banks touched     16                   1
    touched mask      1111111111111111   0000000000000001
    concentrated       0                   1

    model agrees with DUT on both runs : yes
    verdict : both maps are LOSSLESS and CORRECT (8.6 round trip passes
              for each). They are not interchangeable. On this stream
              R offers 16-way bank parallelism with no row reuse, and
              B offers maximal row reuse inside one bank.
    caution : neither line is a performance result. Module 23 owns that.

The verdict and caution lines are what make this a report rather than a data dump. A comparison that ends in a winner has overstepped; a comparison that ends in a characterisation is usable.

Directed cases worth running beyond the three streams: a stream that visits every bank exactly once, to pin banks_touched_count at NUM_BANKS and confirm SPR_W is sized as a count; a stream confined to one bank alternating between two rows, which drives conflict_count and nothing else; an empty window, where all four counters must read zero and concentrated must be low; and a window_restart between two streams, to confirm the device state survives it.

11. Corner Cases

SituationCorrect behaviourFailure if mishandled
NUM_BANKS = 1BK_W guarded to 1; SPR_W is 1zero-width index
all NUM_BANKS banks touchedbanks_touched_count reads NUM_BANKSSPR_W sized as an index truncates full spread to zero
decoded bank outside the deviceignored; err_bank_out_of_range assertsan out-of-range array write corrupting the modelled state
empty windowall four counters zero, concentrated lowa divide-by-zero hit rate, or a spurious concentration report
window_restart between policiescounters clear, device state persiststhe second window's CLOSED count inflated by a fabricated cold device
first access to an unopened bankclassified CLOSEDa hit fabricated against an unopened bank (P4a)
counters saturatetotals stop advancing; classes stay correcta wrapped total makes the hit rate exceed 100 %
a single access in a windowconcentrated stays low — the total_count > 4 guardevery window reports itself concentrated on its first access
two accesses, same bank, same rowone CLOSED, one HIT, spread 1spread counted per access rather than per distinct bank

The window_restart row produces plausible-looking wrong data rather than an obvious failure, which is why §9's P4b checks it structurally instead of leaving a reviewer to notice an odd CLOSED count.

12. Debugging

Symptom: the hit rate is excellent and bandwidth is poor. §8. Read banks_touched_mask — not the count, the mask. One bit set means the stream is concentrated, and the high hit rate is a symptom of the concentration rather than a compensation for it. Then XOR consecutive addresses in the stream and compare the varying bits against 18.1 §6's matrix.

Symptom: the hit rate is zero and bandwidth is fine. The mirror, and not a bug. S1 under Policy B looks alarming on a class dashboard and is the healthiest row in §1's table. Check the spread before investigating: 16 of 16 banks with an all-CLOSED distribution is a well-distributed cold stream, not a failure.

Symptom: conflict count is high. This one is a problem, and it is distinguishable from the other two by class rather than by rate. A high conflict_count with a low spread is §2's bottom-left quadrant: one bank, alternating rows, every access paying a full row cycle. Find the stride; compare it against the row field's LSB. If the stride is a multiple of the row span, no field reordering will help and 18.3 is the chapter you need.

Symptom: the profile disagrees with the controller's own telemetry. Expected, and it is not necessarily a bug in either. This instrument is program-order and scheduler-free; 16.3's observer sees the post-scheduler command stream. The controller's hit rate should be at least as good as the profile's, because reordering can convert conflicts into hits and cannot legitimately do the reverse. If the controller's is worse, that is a genuine finding and it belongs to Module 17, not here.

Symptom: the second policy's window shows an implausibly high CLOSED count. The window_restart cleared the modelled device state. §7's comment names this; P4 catches it.

13. Misconceptions

“More row hits means better performance.” §1 — the high-hit-rate row in every pair is the single-bank row. Consequence: a mapping chosen on hit rate alone selects for concentration. Replacement: evaluate class distribution and spread together. Clue: an excellent hit rate with idle banks.

“A zero hit rate identifies a problem.” S1 under Policy B is 0 % and is the best-distributed result in the table. Clue: an alert threshold on hit rate with no spread term.

CLOSED and CONFLICT are both just misses.” §2's callout — one needs ACT, the other needs PRE then ACT and cannot start until tRAS elapses. Collapsing them hides the only class that indicates a real pathology. Clue: telemetry with a single "miss" counter.

“Spreading across banks always helps.” S1 under Policy B spreads perfectly and pays sixteen activations, bounded by tFAW. Spread is an opportunity, not a result. Clue: a mapping justified by spread alone with no reference to activation limits.

“A better field order fixes any bad stream.” S3 collapses under both policies, because its stride touches no middle bit. Clue: a remapping proposal with no analysis of which bits the workload actually varies.

“The profile predicts what the controller will achieve.” It reports the opportunity presented in program order; a scheduler changes the outcome. Clue: a mapping decision justified by profile numbers presented as throughput.

“One number can rank two mappings.” §1 has six rows and no scalar ordering that survives all three streams. Clue: a review slide with a single mapping score.

“If both mappings round-trip, they perform identically.” Both round-trip on every row of §1. Clue: mapping verification that stops at losslessness.

14. Interview Reasoning

“Does address mapping drive the row-hit rate?” Completely — the same policy scores 93.8 % and 0 % on two streams over the same device. The follow-up that separates understanding from recall: so should you choose a mapping by hit rate? No, and the reason is that a high hit rate on a short stream is made of concentration.

“Your mapping gets 95 % row hits. Is that good?” Unanswerable without the bank spread. Ask for the distribution, not the summary.

“Two mappings, one stream. How do you characterise the difference without claiming a winner?” Report the class distribution and the spread for each, and state what each offers the controller. §10's report format is the answer.

“Why would you measure a mapping without a scheduler in the loop?” So the result is attributable. With a scheduler, every number is a joint property of two policies and neither can be isolated.

“Which is worse: a high CLOSED count or a high CONFLICT count?” Conflict, and the reason is the command sequence and the tRAS floor before the PRE may even issue — not a vague sense that conflicts are bad.

“Your profile says 60 % hits and the controller reports 75 %. Which is broken?” Neither, probably. The profile is program-order; the scheduler reorders and can convert conflicts into hits. The direction is the check: a controller reporting worse than program order is the case worth investigating.

15. Exercises

1. Under Policy R, construct a sixteen-access stream that touches exactly four distinct banks with a 75 % hit rate. Give the stride.

2. §1 shows S1/R and S2/B with identical numbers. Explain structurally why the two must be identical, referring to the bit positions of each policy's bank field.

3. SPR_W is $clog2(NUM_BANKS + 1). For NUM_BANKS = 16, what does banks_touched_count read after all sixteen banks are touched if SPR_W were sized $clog2(NUM_BANKS) instead? Which assertion catches it?

4. window_restart deliberately preserves bank_open. Construct a two-window stimulus where clearing it would change the reported hit rate of the second window, and give both numbers.

5. Write the property asserting that the controller's measured hit rate is never worse than the profile's for the same stream. State the assumption it needs and explain why it cannot be asserted inside this block.

6. concentrated is guarded on total_count > 4. Remove the guard and describe the false report that appears at the start of every window.

7. Design a third policy for this configuration that reaches §2's empty upper-right quadrant on stream S1. Prove it is impossible, or give the field order.

8. A colleague adds a scheduler to the profile block to make it "more realistic". Name the measurement property that is lost and the specific conclusion in §1 that could no longer be drawn.

16. Where This Goes

Mapping drives the class distribution completely, and the class distribution alone cannot choose a mapping. Both halves are now demonstrated rather than asserted.

What §1 also exposed is a case no field ordering fixes: stream S3 collapsed to one bank under both policies, because its stride left every middle bit unchanged. That is not a bad choice of order — it is a structural interaction between the workload's bit pattern and the map's field positions.

Chapter 18.3 is about that interaction: which strides alias with which fields, why the aliasing is a property of powers of two rather than of bad luck, and what a controller can do about it when reordering fields is not enough. 18.4 then asks how any of this is discoverable on a system whose mapping you were never given.

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.