DDR · Module 8
Bank Address
A bank address does not select a location — it selects an independent resource. Which is why the position of the bank field is the most consequential choice in an address map, and why two defensible layouts give opposite answers.
Chapters 8.1 and 8.2 used one field layout and said, repeatedly, that it was a choice rather than a specification. This chapter makes the choice the subject.
The bank field is the right place to do it, because bank is the only field in the map that selects independence rather than position.
Chapter 5.2 established what a bank is: an independent state domain, with its own row decoder and its own sense amplifiers, able to hold an open row while other banks hold theirs. Selecting a bank is not selecting a location — it is selecting which independent resource will do the work.
Which gives this chapter its question:
Where in the system address should the bank bits sit, and what does that decision do to the traffic that flows through the map?
There is no universal answer, and the reason there is no universal answer is itself the lesson.
1. What a Bank Address Selects
A bank address selects which independent state domain the command applies to.
Compare the three fields the module has now introduced:
| Field | Selects | If you change it |
|---|---|---|
| column | which part of the held row leaves | a different transfer from the same open row |
| row | which row is sensed into the amplifiers | a different row — requires an activate |
| bank | which resource holds a row at all | a different, independently busy resource |
Only the bank field changes who does the work. Change the column and the same bank serves you differently; change the row and the same bank serves you after a delay; change the bank and a different bank serves you, possibly at the same time as the first one is still serving someone else.
That is the property the whole chapter turns on, and it is worth stating in the form a controller architect uses:
Two accesses to different banks can overlap. Two accesses to the same bank cannot.
Chapter 5.2 derived why — one row decoder and one set of sense amplifiers per bank, so one open row per bank, so a second row in the same bank must wait for the first to close. This chapter's contribution is the consequence: the address map decides which accesses land in the same bank, and therefore which accesses can overlap. The map is upstream of every parallelism argument anyone will ever make about a DDR system.
2. The Bank Field's Position Is the Decision
Chapter 8.1 §6 noted that the row field's position determines how much contiguous address shares a row. The same reasoning applies to the bank field and produces a sharper result, because of a general property of bit-field maps:
A field at bit position p changes every 2^p bytes of address.
That single sentence is the mechanism. It is not a DDR fact — it is arithmetic — but every consequence in this chapter follows from it:
field at bit 6 → changes every 64 B
field at bit 10 → changes every 1 KB
field at bit 12 → changes every 4 KB
field at bit 26 → changes every 64 MBSo placing the bank field is choosing the address stride at which traffic moves to a different bank. Place it low and sequential traffic changes bank almost immediately. Place it high and a large contiguous region belongs entirely to one bank.
3. Two Policies, Derived
Take the layout from Chapters 8.1 and 8.2 and the one obvious alternative: swap the bank and row fields. Everything else is identical.
POLICY A -- BANK_LOW (bank immediately above column)
┌──────────┬──────────┬──────────┬──────────────────────┐
│ row │ bank │ column │ offset │
└──────────┴──────────┴──────────┴──────────────────────┘
27 12 11 10 9 6 5 0
POLICY B -- BANK_HIGH (bank above row)
┌──────────┬──────────────────────┬──────────┬──────────┐
│ bank │ row │ column │ offset │
└──────────┴──────────────────────┴──────────┴──────────┘
27 26 25 10 9 6 5 0Both are legal. Both are bijections. Both produce operands any DDR device will accept, because — as Chapter 8.1 §6 noted — the device never sees the system address and cannot tell which policy produced its operands.
Now derive what each does, using §2's rule and the config from Chapter 8.2 §4 (16 columns, 64-byte column access, 4 banks, 65,536 rows):
| A — BANK_LOW | B — BANK_HIGH | |
|---|---|---|
| bank changes every | 1 KB | 64 MB |
| row changes every | 4 KB | 1 KB |
| one bank holds | 1 KB out of every 4 KB | one contiguous 64 MB region |
Now walk 4 KB of sequential traffic through each.
Under Policy A, the 4 KB spans four banks — 1 KB each — and one row number. It needs four activates, one per bank, and those four banks are independent, so the activates and the column accesses that follow them can overlap.
Under Policy B, the 4 KB stays in bank 0 and spans four consecutive rows. It also needs four activates — but all four are to the same bank, so they cannot overlap: each row must close before the next opens, and the accesses serialise.
Same address range. Same number of activates. Opposite overlap behaviour.
What this chapter establishes is narrower and more durable: the map decides which accesses share a bank, sharing a bank prevents overlap, and therefore the address map is a first-order determinant of how much of the device's independence a workload can actually use. No amount of scheduling cleverness downstream can recover parallelism the map did not expose.
4. RTL — One Datapath, Two Policies
The engineering problem
Build a decomposition whose field order is a compile-time parameter, so the same address can be mapped under either policy and the difference observed directly — and so that the choice is visibly a constant rather than logic.
Why hardware needs it
A real controller compiles one policy. The value of parameterising it is that the policy becomes a reviewable, testable object instead of a set of magic numbers scattered through slices — and that a verification environment can be built against the same parameter the design used.
Classification
SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
What it models
Field extraction under a selectable field order, plus the stride at which each field changes — which turns §2's rule into an output rather than a claim.
What it does NOT model
Bank state (Chapter 5.2). Request classification as same-bank, same-group or different-group — Chapter 5.3's bank_group_classifier already does that and consumes this block's outputs. Scheduling, queueing or arbitration (Module 17). Any judgement about which policy is better (Module 18). Bank groups (Chapter 8.4). Timing (Modules 13 and 14).
Interface and parameter contract
// ─────────────────────────────────────────────────────────────────────────
// bank_field_policy
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// TWO EXAMPLE CONTROLLER ADDRESS-MAPPING POLICIES. NEITHER IS A
// JEDEC-MANDATED UNIVERSAL DDR ADDRESS MAP, AND NEITHER IS PRESENTED AS
// CORRECT. Module 18 owns which map suits which workload.
//
// MODELS: field extraction under a selectable field ORDER, and the address
// stride at which each field changes.
//
// DOES NOT MODEL: bank state (Chapter 5.2), request classification
// (Chapter 5.3's bank_group_classifier consumes these outputs and must not
// be reimplemented here), scheduling (Module 17), bank groups (Chapter
// 8.4), or timing (Modules 13, 14).
//
// GENERATION: GENERATION-NEUTRAL CONTROLLER MODEL.
// ─────────────────────────────────────────────────────────────────────────
module bank_field_policy #(
parameter int ADDR_W = 32,
parameter int OFFSET_W = 6,
parameter int NUM_COLS = 16,
parameter int NUM_BANKS = 4,
parameter int ROW_W = 16,
// THE PARAMETER OF INTEREST.
// 0 = BANK_LOW : offset | column | bank | row
// 1 = BANK_HIGH : offset | column | row | bank
// Nothing else in the module changes between them -- see below.
parameter int POLICY = 0,
parameter int COLUMN_W = (NUM_COLS <= 1) ? 1 : $clog2(NUM_COLS),
parameter int BANK_W = (NUM_BANKS <= 1) ? 1 : $clog2(NUM_BANKS)
) (
input logic [ADDR_W-1:0] sys_addr,
output logic [COLUMN_W-1:0] col_operand,
output logic [BANK_W-1:0] bank_operand,
output logic [ROW_W-1:0] row_operand,
output logic addr_above_device,
// Section 2's rule as hardware output rather than prose: the address
// delta that moves each field. Constants -- the consequence of a policy
// is fixed at elaboration, which is exactly the point being made.
output logic [ADDR_W-1:0] bank_stride_bytes,
output logic [ADDR_W-1:0] row_stride_bytes
);
localparam int POL_BANK_LOW = 0;
localparam int POL_BANK_HIGH = 1;
// ── Field positions. THE ENTIRE POLICY IS THESE TWO EXPRESSIONS.
// Everything below is identical for both, which is the structural
// claim worth seeing: a mapping policy costs no logic, because it is
// a choice about constants.
localparam int COL_LSB = OFFSET_W;
localparam int BANK_LSB = (POLICY == POL_BANK_LOW)
? (COL_LSB + COLUMN_W)
: (COL_LSB + COLUMN_W + ROW_W);
localparam int ROW_LSB = (POLICY == POL_BANK_LOW)
? (COL_LSB + COLUMN_W + BANK_W)
: (COL_LSB + COLUMN_W);
localparam int MAP_W = COL_LSB + COLUMN_W + BANK_W + ROW_W;
// ── Elaboration legality.
if ((POLICY != POL_BANK_LOW) && (POLICY != POL_BANK_HIGH)) begin : g_pol
initial $fatal(1, "bank_field_policy: POLICY must be 0 (BANK_LOW) or 1 (BANK_HIGH)");
end
if (OFFSET_W < 1) begin : g_ow
initial $fatal(1, "bank_field_policy: OFFSET_W must be >= 1");
end
if (ROW_W < 1) begin : g_rw
initial $fatal(1, "bank_field_policy: ROW_W must be >= 1");
end
if (MAP_W > ADDR_W) begin : g_fit
initial $fatal(1, "bank_field_policy: fields need more bits than ADDR_W");
end
// A bit-field map must be a bijection -- see Chapter 8.2 Section 4.
if (NUM_COLS != (1 << COLUMN_W)) begin : g_cpow
initial $fatal(1, "bank_field_policy: NUM_COLS must be a power of two");
end
if (NUM_BANKS != (1 << BANK_W)) begin : g_bpow
initial $fatal(1, "bank_field_policy: NUM_BANKS must be a power of two");
end
// ── Extraction. IDENTICAL FOR BOTH POLICIES. Constant part-selects, so
// still wiring: swapping the policy moves wires, it does not add
// gates or delay.
assign col_operand = sys_addr[COL_LSB +: COLUMN_W];
assign bank_operand = sys_addr[BANK_LSB +: BANK_W];
assign row_operand = sys_addr[ROW_LSB +: ROW_W];
// ── The unmapped region above the fields. Guarded so that a map
// consuming every address bit does not produce a reversed
// part-select.
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
// ── Section 2's rule. A field at bit p changes every 2^p bytes. Both
// shifts are in range because both LSBs are below MAP_W <= ADDR_W.
assign bank_stride_bytes = ADDR_W'(1) << BANK_LSB;
assign row_stride_bytes = ADDR_W'(1) << ROW_LSB;
endmoduleState and sequential behaviour
Neither. As in Chapter 8.1, extraction is a function of the address, and the policy is a function of the parameters.
Combinational behaviour
Three constant slices, one OR reduction, two constant shifts. The two stride outputs are elaboration-time constants — a synthesiser will fold them to literals. That is deliberate: a policy's consequence is not computed at run time, it is decided at design time, and the RTL says so by making the consequence a constant.
Bit-level derivation
At the defaults, the two policies produce:
| COL_LSB | BANK_LSB | ROW_LSB | MAP_W | bank_stride_bytes | row_stride_bytes | |
|---|---|---|---|---|---|---|
| BANK_LOW | 6 | 10 | 12 | 28 | 1,024 | 4,096 |
| BANK_HIGH | 6 | 26 | 10 | 28 | 67,108,864 | 1,024 |
MAP_W is 28 in both cases, because the same fields occupy the same total width regardless of order. Only the boundaries move — which is why the policy is free, and also why getting it wrong is invisible to any check that only verifies total width.
Cycle example
Address 0x00104840, under both policies:
| BANK_LOW | BANK_HIGH | |
|---|---|---|
| column (9:6) | 1 | 1 |
| bank | (11:10) = 2 | (27:26) = 0 |
| row | (27:12) = 0x0104 | (25:10) = 0x0412 |
One address, two completely different coordinates, both correct. The column agrees because the column field did not move. And note that neither result is more true than the other — they describe the same byte, reached through different resources.
How to simulate, and expected output
Elaborate two instances, one per policy, drive the same address into both, and compare. The addresses worth driving are the boundaries:
sys_addr | A: bank | A: row | B: bank | B: row |
|---|---|---|---|---|
0x00000000 | 0 | 0 | 0 | 0 |
0x00000400 | 1 | 0 | 0 | 1 |
0x00001000 | 0 | 1 | 0 | 4 |
0x04000000 | 0 | 0x4000 | 1 | 0 |
Row 2 is the chapter in one line. A 1 KB step changes the bank under Policy A and the row under Policy B. Row 4 shows the converse: the step that changes Policy B's bank is 64 MB, and it barely registers as a row change under A.
Synthesis implication
Zero gates for the extraction, zero for the strides. Changing POLICY changes which wires connect where and nothing else — no area, no delay, no power difference. This is the most important practical fact in the chapter: because the choice is free in hardware, it is never justified by implementation cost, only by traffic behaviour.
Parameter corner cases
NUM_BANKS == 1 gives BANK_W == 1 through the guard, and both policies then produce identical output — with one bank there is nothing to interleave, and the degenerate case correctly collapses. POLICY outside {0, 1} does not elaborate, which matters because a typo'd policy silently defaulting would be the worst possible failure here. Non-power-of-two counts do not elaborate, for Chapter 8.2 §4's bijection reason. ROW_W == 1 is legal and makes Policy B's bank stride 2 × 1 KB — a good sanity case, because it shows the stride following the row width rather than a hard-coded number.
Reset behaviour
No state, no reset. But there is a reset-adjacent hazard worth naming: if a controller's policy is a runtime register rather than a parameter, then its value after reset determines how every address is interpreted, and a mismatch between that value and the verification environment's assumption produces Chapter 8.1 §8's configuration-mismatch failure. Compiling the policy in, as here, removes that failure mode entirely.
Debugging clues
Two models disagreeing about the bank while agreeing about the column means they were compiled with different policies — the column field is the tell, because it is the one field the policy does not move. A stream you expected to interleave that stays in one bank means bank_stride_bytes is larger than you assumed; read the output rather than the slice, since that is what it is for.
Limitations
Two policies, no bank-group field, no rank or channel, no bit hashing, and no opinion about which policy suits a workload. It also cannot tell you what policy the other side of your system uses — Chapter 8.6 builds the check for that.
5. The Same Address, Twice
bank_field_policy — the same stream under both policies
8 cyclesRead the A: rows and the B: rows as two different machines being handed the same requests.
Policy A spreads eight sequential 1 KB blocks across four banks, twice. Each bank sees two accesses, to one row each. Four independent resources are in play, so the activates can overlap.
Policy B sends all eight to bank 0, walking eight consecutive rows. One resource is in play, and because a bank holds one open row, each of those eight rows must close before the next opens. The accesses serialise.
Neither trace contains an error. Both are correct decompositions of the same addresses, and a DDR device would accept either command stream without complaint. The difference is entirely in how much of the device's independence each stream managed to reach — and that was decided by two localparam expressions.
What the trace deliberately does not show is how long any of this takes. Overlap is an opportunity created by the map; whether a controller exploits it is Module 17's, and what it is worth is Modules 16 and 23'.
Representative educational cycles. One address per cycle is a reading convenience and implies no request rate or timing.
6. Crossing a Bank Boundary
The arithmetic here is small and worth being able to do in your head, because it converts vague locality talk into numbers.
The bank stride is 2^BANK_LSB. Under Policy A that is 2^10 = 1 KB; under Policy B, 2^26 = 64 MB.
So for an access stream with stride s:
s >= bank stride → every access lands in a different bank
(or the same one, if s is a multiple of
bank stride × NUM_BANKS -- see below)
s < bank stride → (bank stride / s) consecutive accesses
share a bank before moving onThe parenthesis is the trap. A stride that is an exact multiple of bank stride × NUM_BANKS returns to the same bank every time. Under Policy A that is 1 KB × 4 = 4 KB:
stride 4 KB, Policy A:
0x0000 → bank 0
0x1000 → bank 0
0x2000 → bank 0 ← every access, one bank, no parallelism at allA 4 KB-strided stream under Policy A uses one bank out of four, which is the worst case for that policy, and it is not an exotic stride — it is a page size. Under Policy B the same stream changes rows within one bank, which is also bad, but for a different reason.
7. Four Assertions Worth Writing
// P1 -- the bank field changes exactly at its stride. This is Section 2's
// rule as a contract, and it is what a mis-stated stride output breaks:
// two addresses whose difference is below the bank stride, and which do
// not cross a bank-stride boundary, must select the same bank.
property p_bank_stride_is_honest;
@(posedge clk)
((sys_addr / bank_stride_bytes) == ($past(sys_addr) / bank_stride_bytes))
|-> (bank_operand == $past(bank_operand));
endproperty
assert property (p_bank_stride_is_honest);
// P2 -- the three fields are pairwise disjoint, proved through the column.
// The column field is the one the policy never moves, so it is the right
// probe: a change confined to the column region must move only the column.
property p_column_region_is_isolated;
@(posedge clk)
(((sys_addr ^ $past(sys_addr)) >> (OFFSET_W + COLUMN_W)) == '0)
|-> (bank_operand == $past(bank_operand))
&& (row_operand == $past(row_operand));
endproperty
assert property (p_column_region_is_isolated);
// P3 -- the degenerate case really does degenerate. With one bank the two
// policies must be indistinguishable, and this catches a policy expression
// that accidentally shifts a field when there is nothing to interleave.
property p_single_bank_collapses;
@(posedge clk)
(NUM_BANKS == 1) |-> (bank_operand == '0);
endproperty
assert property (p_single_bank_collapses);
// P4 -- total width is policy-independent. Both policies must report the
// same addresses as outside the device, because reordering fields cannot
// change how much memory the map describes.
property p_reach_is_policy_independent;
@(posedge clk)
addr_above_device == ((sys_addr >> MAP_W) != '0);
endproperty
assert property (p_reach_is_policy_independent);What these prove. P1 makes the stride output trustworthy, which matters because §6's reasoning is built on it. P2 proves disjointness using the one field the policy holds still — a better probe than testing the fields that move. P3 protects the degenerate configuration, which is the one most likely to be skipped in regression. P4 pins the invariant that survives the policy change, and an invariant that survives a parameter change is the most valuable kind, because it is what lets you swap policies without re-verifying everything.
What they do not prove. Nothing here says a policy is right for anything. These properties hold under both policies and would hold under a policy that was terrible for every real workload, because suitability is not a correctness property and cannot be asserted. That measurement is Module 18's. They also prove nothing about the device, nothing about whether the resulting commands are legal to issue (Chapter 7.3 and Modules 13 and 14), and — importantly — nothing about agreement with any other component's map. Two instances with different POLICY both satisfy all four properties while disagreeing about every address.
8. DV — Which Bank Did the Reference Model Choose?
Bank extraction creates a specific verification hazard, and it is worth separating from the general mapping-mismatch problem Chapter 8.1 §8 introduced.
A wrong bank field produces a legal, well-formed, consistent command stream. Activates and column commands agree with each other — the controller activates bank 2 and then reads bank 2 — so every protocol check passes, Chapter 5.2's state model stays coherent, and the commands are properly ordered. The device does exactly what it was asked. It simply stored the data somewhere other than where the reference model looked for it.
Which produces a distinctive symptom: data corruption with a perfectly clean protocol trace, and a reference model that is right about three fields out of four.
The practical measures:
Make the map a shared, single-sourced configuration object. If the DUT's POLICY and the environment's POLICY are separate declarations, they will eventually differ. This is the cheapest bug to prevent and one of the more expensive to find.
Log the coordinate, not just the address. A transaction record that carries (bank, row, column) alongside the system address lets a failure be bisected by field: if the row and column agree and the bank does not, the diagnosis is one line of configuration rather than a day of waveform reading.
Cover the policies, not just the addresses. A regression that elaborates one policy has verified one map. Chapter 3.3 §7 made this point about dimensions and it applies with more force here, because the policy changes every field boundary except one.
Test the pathological strides deliberately. §6 shows that a field map has a stride for which the bank never changes. Random addresses will almost never produce it, and a directed test with stride bank stride × NUM_BANKS is worth more than a million random accesses for exposing a parallelism assumption that the map does not actually support.
9. Debugging — Correct Row, Wrong Bank
Symptom. Data corruption. Reads return data that was written to a different location. The protocol trace is clean: legal commands, legal ordering, coherent bank state. Row and column operands match what the reference model expected; the bank does not.
Candidate mechanisms.
- The DUT and the reference model were compiled with different field orders — different
POLICY. - The bank field is extracted from the right position but is the wrong width, so its top bit is stolen from, or leaks into, a neighbour.
- Bank and row fields overlap because a boundary expression is wrong — Chapter 8.1's exercise 4.
- The map agrees everywhere and the bank operand is corrupted downstream, in the encoder or on the interface.
- The reference model is right, the DUT is right, and the test computes its expected address with a third map.
Evidence to collect. For each failure: the system address, and both models' full (bank, row, column) decomposition. Then look at which fields disagree, and by how much.
Discriminator — the disagreement pattern names the mechanism.
- Only the bank disagrees, and the column agrees: the column field is the one a policy change does not move, so agreement on column plus disagreement on bank is the signature of mechanism 1. Compare the two
POLICYvalues before doing anything else. - Bank and row both disagree, in a correlated way: a shifted boundary between them — mechanism 3. The correlation is the tell: the bits lost by one field are the bits gained by the other.
- The bank disagrees only for addresses above a power-of-two threshold: the field is too narrow, so its high bit never arrives — mechanism 2. The threshold is
2^(BANK_LSB + actual width). - All three fields agree and the failure persists: addressing is exonerated. Mechanism 4 or 5, and the next step is to compare the operand on the interface against the extracted one.
- Two of the three models agree: whichever is the outlier is wrong, and mechanism 5 is more common than its position on this list suggests — a test that recomputes the expected address independently has quietly introduced a third map.
Responsible layer. Mechanisms 1, 2, 3 and 5 are all layer B — the controller's map, or a duplicate of it — and none of them is a DDR protocol problem. Mechanism 4 is layer C or D. The clean protocol trace is the diagnostic, not a puzzle: it tells you the interface is behaving, which is precisely why the fault must be above it.
Fix. Single-source the map, then add Chapter 8.6's round-trip and cross-model checks so a reintroduced mismatch fails loudly at elaboration or on the first transaction rather than as corruption.
10. Common Misconceptions
"Bank bits and row bits are fixed across all controllers."
Why it is tempting: they are fixed in any one system, and documentation for that system presents them as facts.
Concrete failure: verification IP with a compiled-in map passes on one platform and reports corruption on the next, with no bug on either.
Correct model: the field order is a controller decision. §4's POLICY shows two valid answers and there are many more.
Prevention: parameterise and single-source the map. Never let a second component compute it independently.
"Sequential physical addresses always stay in the same bank."
Why it is tempting: sequential access feels like it should be local, and locality feels like it should mean one resource.
Concrete failure: a performance model assumes a sequential stream occupies one bank and predicts serialisation. Under Policy A the stream uses all four banks and the model's prediction is wrong by the bank count.
Correct model: sequential traffic changes bank every bank stride bytes, and the stride is a choice. Under Policy A it is 1 KB; under Policy B, 64 MB.
Prevention: read bank_stride_bytes. It exists to stop this assumption.
"More bank interleaving is always better."
Why it is tempting: parallelism is good, banks are the parallelism, so spreading across them faster sounds strictly better — and for a single stream in isolation it is.
Concrete failure: a system with aggressive interleaving performs worse under a multi-stream workload than a coarser map, because every stream now touches every bank and they close each other's rows. The engineer who chose the fine interleave has no model for the regression.
Correct model: fine interleaving buys parallelism within a stream and destroys isolation between streams. §3 derives both halves.
Prevention: evaluate against the real workload mix. Module 18 is the discipline; the mechanism is here.
"A bank address selects a location."
Why it is tempting: it is one of the coordinates, and it appears in the same tuple as row and column.
Concrete failure: an engineer reasons about bank as if it were an offset and cannot explain why changing the bank changes when an access completes rather than only what it returns.
Correct model: bank selects an independent resource. Changing it changes who serves the request and whether the request can overlap another.
Prevention: §1's table. Note which field changes who does the work.
"A correct command decoder proves the address mapping is correct."
Why it is tempting: the decoder is the thing that reads addresses off the interface, and if it works, addressing feels verified.
Concrete failure: Chapter 7.2's decoder is fully verified and every operand is decoded correctly, while the map that produced those operands is wrong. The decoder faithfully reports bank 2, and bank 2 was the wrong bank.
Correct model: the decoder verifies layer D. The map is layer B. A correct decoder proves the operands were transmitted and interpreted as sent — nothing about whether they were the right operands.
Prevention: check the map with a round trip against the address, not against the operands. Chapter 8.6.
"Choosing a mapping policy is a hardware cost trade-off."
Why it is tempting: most architectural choices cost area or timing, so this one should too.
Concrete failure: a review rejects a finer interleave on implementation-cost grounds and nobody measures the actual consequence, which is a traffic behaviour question.
Correct model: both policies are constant part-selects. Zero gates, zero delay, identical area. The trade-off is entirely in traffic behaviour.
Prevention: §4's synthesis note. The cost argument is not available here, so the decision has to be made on merit.
11. Interview Reasoning
"Why would a controller place bank bits low in the physical address?"
To spread a single sequential stream across independent banks quickly. Banks are the axis of independence — a bank holds one open row, so two accesses to the same bank cannot overlap — and placing the bank field low means consecutive regions of address land in different banks, exposing that independence to a stream that would otherwise serialise. Under §3's Policy A a 1 KB stride is enough to reach a different bank. The cost is isolation: every stream now touches every bank, so independent streams interfere.
"What is the trade-off between row locality and bank parallelism?"
They compete for the same address bits. Bits spent making a region share a row are bits not spent spreading it across banks, and the total is fixed by the field widths. Concretely: placing the bank field low gives a stream four banks and 1 KB per row per bank; placing it high gives one bank and a longer contiguous run per bank. Both need the same number of activates for a given range — the difference is whether those activates can overlap. And the right balance depends on the workload, which is why it is measured rather than derived.
"Why can two controllers use different address maps with the same DRAM device?"
Because the device never sees the system address. It receives a bank operand, a row operand and a column operand, and it has no way to know — and no reason to care — which address bits produced them. The map is entirely internal to the controller. This is why the map is configuration for verification IP rather than a constant, and why a reference model must be told the policy rather than assume it.
"Sequential traffic is unexpectedly alternating between banks. Is that a bug?"
Probably not — it is the most likely intentional behaviour of a bank-interleaved map, and it is what Policy A is for. The way to settle it is to compute the bank stride from the map and check whether the observed alternation matches it. If the traffic changes bank every 1 KB and the bank field sits at bit 10, the map is working as designed. Treating intended interleaving as a bug is a common way to waste a day, which is why §4 makes the stride an output.
"A 4 KB-strided stream gets no bank parallelism on a four-bank interleaved map. Why?"
Because the map is periodic. With the bank field at bit 10 and four banks, the bank index repeats every 4 KB, so a 4 KB stride selects the same bank every time. It is not a bug in the map — it is an inherent property of taking a contiguous slice as an index, and 4 KB is an unfortunately common stride. The mitigation is to hash several address bits into the bank index instead, which breaks the periodicity; that is Module 18's subject.
12. Engineering Exercise
Config: OFFSET_W = 6, NUM_COLS = 16, NUM_BANKS = 4, ROW_W = 16, ADDR_W = 32.
1. Compute BANK_LSB, ROW_LSB and MAP_W for both policies.
2. Decompose 0x00208840 under both policies.
3. Under Policy A, how many banks does a stream of stride 2 KB use? Stride 4 KB? Stride 5 KB?
4. Under Policy B, what stride does a stream need before it changes bank?
5. A colleague proposes eight banks with the field still at bit 10 under Policy A. What changes, and what new pathological stride appears?
6. Your reference model and the DUT agree on column and row but disagree on bank. What do you check first, and why is the column agreement the clue?
13. Summary
A bank address selects an independent resource, not a location. It is the only field in the map that changes who serves a request, and therefore the only one that determines whether two accesses can overlap.
A field at bit position p changes every 2^p bytes. That arithmetic is the whole mechanism, and it turns "where do the bank bits go" into "after how many bytes should traffic move to a different bank" — a question with units.
Two defensible policies give opposite answers. Bank low spreads one stream across banks quickly and lets its activates overlap. Bank high gives large contiguous regions their own bank and keeps independent streams from interfering. Same fields, same total width, same zero gates — only the boundaries move.
Neither is better. Fine interleaving buys parallelism within a stream and spends isolation between streams. Which matters is a workload question, settled by measurement in Module 18, not by inspection here.
Bit-field maps are periodic, so pathological strides exist by construction. A stride equal to bank stride × NUM_BANKS uses one bank out of all of them, and 4 KB is exactly that stride for a common configuration. Hashing breaks the periodicity and is Module 18's.
And a wrong bank field is the quietest addressing bug there is. The protocol trace stays clean, state tracking stays coherent, three fields out of four agree — and data lands where nobody looks for it. The fingerprint is column agrees, bank disagrees, and it means two components were compiled with different maps.
14. What Comes Next
The bank field has been treated as a single flat index, which is how it worked in DDR3 and earlier. Chapter 8.4 — Bank-Group Address takes that apart.
Chapter 5.3 established that DDR4 introduced bank groups, and that banks in different groups behave differently from banks in the same group because they do not share a column data path. So the flat bank index becomes two fields — and the map now has to decide not only where the bank bits sit, but how to split them, because which split you choose determines whether consecutive accesses land in the same group or different ones.
That is a generation-specific field in a module that has so far been generation-neutral, and the chapter handles the generation boundary explicitly.
Return to Banks for the independence this chapter's field selects, Bank Groups for the classifier that consumes these operands, Row Address and Column Address for the other two fields, and Activate for the command that carries a bank operand.
Continue learning
Related tutorials
- Related topic
Channels
A channel has its own command bus, data bus and scheduling, so two channels contend for nothing. That independence means the address alone decides which channel a request uses, making the mapping — not the hardware — the thing that determines whether the parallelism is real.
- Related topic
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.
- Related topic
The Refresh Requirement
Leakage produces a rule about the passage of time rather than about any operation. What the maintenance operation actually does, why it costs device availability, and how a digital design tracks a deadline, arbitrates it against traffic, and proves it never silently drops the obligation.
- Related topic
Restore Operations
Sensing consumed the stored state, so something must put it back. What restoration drives, why it covers a whole row, why a restored row is then cheap to access again, and an educational control model that cannot skip a prerequisite the array is unable to enforce.
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.
