DDR · Module 25
DDR5 Bank-Group Enhancements
Chapter 16.3 recorded this as a question it could not close from primary material. The die datasheet closes it — and shows a x16 part has half the banks of a x8 and twice the page.
Chapter 16.3 §4 is titled “DDR4 to DDR5 — What Carries and What I Could Not Close.” It reported DDR5's bank organisation as corroborated but not retrieved in primary form, recorded that “Micron's asset and distributor servers refused every automated retrieval I attempted,” and noted this was “the seventh DDR5 question this curriculum has been unable to close from accessible primary material.”
This chapter closes it. The organisation below is read from a named DDR5 die datasheet's own addressing table, which also supplies something 16.3 never had: the page size.
And the page size produces a result worth the retrieval on its own. A x16 DDR5 device has half the banks of a x8 and twice the page. It is not a wider x8 — it is a different organisation, and §6 works through what that costs.
1. The Retrieval That Closed
DEVICE-VERIFIED, from the Micron 16Gb DDR5 SDRAM Die Revision D addendum covering part numbers MT60B4G4, MT60B2G8 and MT60B1G16 — document 16gb_ddr5_sdram_dierevD.pdf, Rev. F, obtained from a distributor mirror. The document states it is JESD79-5 compliant.
Its feature list gives the organisation in two lines:
- 32 internal banks (x4, x8): 8 groups of 4 banks each
- 16 internal banks (x16): 4 groups of 4 banks each
And its addressing table gives the rest. DEVICE-VERIFIED, Table 1 of that document:
| 4Gb x4 | 2Gb x8 | 1Gb x16 | |
|---|---|---|---|
| Groups / banks per group / banks | 8 / 4 / 32 | 8 / 4 / 32 | 4 / 4 / 16 |
| Bank group address | BG0–BG2 | BG0–BG2 | BG0–BG1 |
| Bank address within group | BA0–BA1 | BA0–BA1 | BA0–BA1 |
| Row address | R0–R15 | R0–R15 | R0–R15 |
| Column address | C0–C10 | C0–C9 | C0–C9 |
| Page size | 1 KB | 1 KB | 2 KB |
The counts match what 16.3 §4 reported from search and what 5.3 §2 wrote independently. Three routes, same numbers — and now one of them is the document itself.
2. Reading the Addressing Fields
The table's field widths are not decoration — every other number in the chapter derives from them, so it is worth confirming they are consistent.
DERIVED, recomputed:
| Width | BG bits | BA bits | Total bank bits | Banks addressed |
|---|---|---|---|---|
| x4 | 3 (BG0–BG2) | 2 | 5 | 2⁵ = 32 |
| x8 | 3 | 2 | 5 | 32 |
| x16 | 2 (BG0–BG1) | 2 | 4 | 2⁴ = 16 |
The address field width and the stated bank count agree exactly at every width. That is a small check and it is the kind worth doing: a datasheet's feature list and its addressing table are written by different processes, and when they agree the organisation is confirmed twice.
Now the page size, which is the figure 16.3 did not have. DERIVED from the column field and the device width:
page size = 2^(column address bits) x device width
x4 : 2^11 x 4 bits = 8192 bits = 1 KB published 1 KB
x8 : 2^10 x 8 bits = 8192 bits = 1 KB published 1 KB
x16 : 2^10 x 16 bits = 16384 bits = 2 KB published 2 KBAll three reproduce the published page size exactly. Note where the x4 device differs: it has eleven column bits where the others have ten, which is exactly what keeps its page at 1 KB despite being the narrowest part.
3. The Whole Die, Checked Against Itself
One more consistency check, and this one closes the organisation completely. If the row count, the page size and the bank count are all right, they must multiply to the die's density.
DERIVED, recomputed for all three widths:
| Width | Rows | × Page | = Per bank | × Banks | = Total |
|---|---|---|---|---|---|
| x4 | 65,536 | 1 KB | 512 Mb | 32 | 16 Gb |
| x8 | 65,536 | 1 KB | 512 Mb | 32 | 16 Gb |
| x16 | 65,536 | 2 KB | 1 Gb | 16 | 16 Gb |
Every width arrives at exactly 16 Gb. Three independent field widths, a stated bank count and a stated page size, and the product is the density on the label in all three cases.
Two things that follow immediately, and the second is this chapter's central observation.
The row count is identical at every width. R0–R15 — 65,536 rows per bank, regardless of organisation. So the width does not change how many rows a bank has; it changes how wide each row is and how many banks there are.
And the x16 device trades banks for page. It has half the banks and twice the page of the x8, and the product is preserved. That is not a smaller version of the same architecture — it is the same silicon partitioned differently, and §6 is about which workloads prefer which partitioning.
4. What Doubling the Groups Buys — the Structural Half
Chapter 5.3 §1 established the mechanism: a bank group is the scope at which the column data path is shared, so two accesses to the same group contend for a resource that two accesses to different groups do not. Chapter 16.3 §3 measured that the resulting penalty varies with class, width and grade, and found one configuration where it vanishes entirely.
What can be concluded without any DDR5 timing value is the change in how often the expensive relationship arises.
DERIVED. Pick a bank, then pick a different bank at random. The chance the second shares a group with the first is (banks per group − 1) / (total banks − 1):
| Organisation | Same-group chance |
|---|---|
| DDR4 x8 — 4 groups × 4 banks | 3/15 = 20.000% |
| DDR5 x8 — 8 groups × 4 banks | 3/31 = 9.677% |
| DDR5 x16 — 4 groups × 4 banks | 3/15 = 20.000% |
At x8, DDR5 makes the cheap relationship roughly twice as likely — a factor of 2.067 fewer same-group pairs, recomputed. Chapter 16.3 §4 stated this result in passing (“roughly 3 times in 15” against “3 in 31”) and this chapter confirms it against the verified counts.
And at x16, DDR5's group statistics are identical to DDR4's x8. Same four groups, same four banks per group, same 20%. A controller that tuned its mapping around DDR5's improved group statistics gets nothing from an x16 part.
5. Concurrency Is Not the Only Thing That Doubled
Thirty-two banks against sixteen is the headline, and taken alone it overstates the change. The banks are not just more numerous — they are smaller.
DERIVED, from §3's table:
| DDR4 x8, 16 Gb | DDR5 x8, 16 Gb | |
|---|---|---|
| Banks | 16 | 32 |
| Per-bank capacity | 1 Gb | 512 Mb |
| Page size | 1 KB | 1 KB |
| Rows per bank | 131,072 | 65,536 |
DDR4's row count here is DERIVED from 16.3 §4's verified 16-bank organisation at the same density and page size, not read from a DDR5 document.
So the same silicon is cut into twice as many pieces, each half the size. Two consequences, pointing in opposite directions.
More banks means more independent row state, which is Module 17's subject: more banks can hold open rows simultaneously, so a scattered access stream finds more of its requests landing in an already-open row.
And fewer rows per bank means a given bank covers less of the address space. Each DDR5 bank spans half the addresses a DDR4 bank of the same density does, so under a flat mapping a sequential walk leaves a bank twice as often.
But the magnitudes make that far less significant than it sounds, and it is worth putting numbers on rather than leaving as a ratio. DERIVED from §3's figures:
| Boundary | x8 span | x16 span |
|---|---|---|
| Page | 1 KiB | 2 KiB |
| Bank | 64 MiB | 128 MiB |
| Bank group | 256 MiB | 512 MiB |
All six recomputed, and each width's four groups or eight multiply back to 2 GiB — the 16 Gb of §3.
Now apply them. A 1 MiB sequential read on the x8 organisation, under a flat mapping:
page crossings 1,024
bank crossings 0
bank-group crossings 0A sequential stream of any realistic size never leaves its bank at all, because the bank spans 64 MiB. So “DDR5 crosses bank boundaries twice as often” is arithmetically true and practically empty — it is twice as often as almost never.
The real conclusion is the opposite of what the ratio suggests. Bank and bank-group relationships are scattered-access concerns, not sequential ones. §4's same-group probability and §5's bank count matter when consecutive requests come from unrelated places; for a contiguous walk, the only boundary being crossed is the page, a thousand times per megabyte, and the bank organisation is irrelevant to it.
So which effect dominates depends on the access pattern, and for anything contiguous the answer is neither. §11's monitor measures the group relationship on a real stream rather than assuming one.
6. Why x16 Is a Different Architecture
§3 established that the x16 part has half the banks and twice the page. It is worth spelling out who wants which, because the choice between an x8-based and an x16-based module is usually made for pin-count reasons and has consequences that are not about pins at all.
| x8 (8 groups, 32 banks, 1 KB page) | x16 (4 groups, 16 banks, 2 KB page) | |
|---|---|---|
| Concurrency | higher — 32 independent row states | lower — 16 |
| Same-group chance | 9.677% | 20.000% |
| Row-hit opportunity per open row | smaller page | larger page — 2 KB of contiguous data |
| Devices per 32-bit sub-channel | 4 | 2 |
The last row is 25.2's subject and is included because it is the reason anyone chooses x16: fewer devices per sub-channel means fewer loads, fewer packages and a smaller module.
The architectural consequence is that x16 favours locality and x8 favours concurrency. A stream with strong spatial locality gets more out of a 2 KB page — Chapter 23.3 quantifies at length what a high row-hit rate is worth. A stream of scattered small requests from many requesters gets more out of 32 banks.
And a controller cannot compensate for the wrong choice. An address mapping can spread accesses across whatever banks exist, but it cannot manufacture banks that are not there. Chapter 18.1 owns making the most of a given organisation; the organisation itself is a purchasing decision.
7. The Two Organisations Hold Exactly the Same Amount Open
§6 said x16 favours locality and x8 favours concurrency, which is true and vague. Multiplying two verified numbers makes it precise, and the product is not what it looks like it should be.
How much of the address space can a device hold open at once? That is banks times page size, and both are device-verified. DERIVED, recomputed:
| Organisation | Banks | × Page | = Openable at once |
|---|---|---|---|
| DDR4 x8, 16 Gb | 16 | 1 KB | 16,384 B |
| DDR5 x8, 16 Gb | 32 | 1 KB | 32,768 B |
| DDR5 x16, 16 Gb | 16 | 2 KB | 32,768 B |
The two DDR5 organisations are identical on this measure — exactly 32,768 bytes, both of them. And both double DDR4's at the same density.
So the x8-versus-x16 choice is not about how much data can be open. It is about how that same fixed amount is divided:
x8 : 32 pieces of 1 KB
x16 : 16 pieces of 2 KB
both total 32,768 BThat reframes §6's table into something a purchaser can actually evaluate. Take a workload with K concurrent localities, each with a working set of W bytes. An organisation serves it without conflict when it has enough banks for K and a page large enough for W. DERIVED, with ILLUSTRATIVE K and W:
K streams | W each | x8 (32 × 1 KB) | x16 (16 × 2 KB) | Verdict |
|---|---|---|---|---|
| 8 | 512 B | fits | fits | either |
| 8 | 1536 B | no — page too small | fits | x16 only |
| 24 | 512 B | fits | no — too few banks | x8 only |
| 16 | 1024 B | fits | fits | either |
| 24 | 1536 B | no | no | neither |
| 40 | 256 B | no — too few banks | no | neither |
The two boundaries are clean. x8 wins once K exceeds 16, because x16 does not have the banks. x16 wins once W exceeds 1 KB, because x8 does not have the page. Between those, either works.
8. The Three Quantities, at This Level
DDR5's changes in this chapter improve one thing and cost another every time, and the pattern is worth naming before the RTL.
| Change | Concurrency | Granularity | Integrity |
|---|---|---|---|
| 8 groups instead of 4 (x4/x8) | better — cheaper pairs more often | unchanged | unchanged |
| 32 banks instead of 16 | better — more open rows | worse — bank crossings twice as often | unchanged |
| x16's 2 KB page | worse — 16 banks | better for locality, worse for small access | unchanged |
Nothing in this chapter touches integrity — that column is 25.4's entirely, and leaving it visibly empty is more honest than implying the organisation helps.
And the middle column is the one usually left out. “More banks” is reported as a pure win, and §5 established it is not: the banks are smaller, so the boundaries are closer together. A chapter that reported only the first column would be selling.
9. The Organisation as a Structure
Two things the structure makes visible that the tables do not.
Only one given quantity drives both branches. Width feeds the group count and the page size, which is why it is the parameter that decides the architecture rather than merely its size. Density feeds only the total check; the row field is constant. So the entire x8-versus-x16 difference in §6 traces to one arrow in this diagram forking.
And Page size feeds both a consequence and a capacity. It determines granularity directly and per-bank capacity through the row count — which is why a larger page both helps locality and reduces the bank count at fixed density. The two effects §6 describes are not independent choices a designer makes; they are one structural consequence seen from two sides.
10. The Organisation Invariant
// ---------------------------------------------------------------------
// ddr5_bank_organisation -- holds a DDR5 die's organisation and proves
// at elaboration that the fields, the page size, the bank count and
// the density all agree.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHY ELABORATION AND NOT RUNTIME: 4.6 §5 established the principle --
// an architectural rule belongs where a violation cannot build. A
// runtime check would let a mis-parameterised design elaborate and
// then mis-address silently, which is the failure this prevents.
//
// WHAT IT DOES NOT MODEL:
// - the array, any timing parameter, or the shared column data path
// (5.3 owns that mechanism; 14.6 and 14.7 own its timing)
// - any access. It describes a device, it does not serve requests.
// - DDR5 timing values, which 16.3 §4 could not retrieve and this
// chapter did not either. NOTHING here depends on one.
//
// THE FIGURES IT DEFAULTS TO ARE DEVICE-VERIFIED for a named 16Gb
// DDR5 die at x8 (§1). Other widths are legal parameterisations and
// the checks below are what keep them honest.
// ---------------------------------------------------------------------
module ddr5_bank_organisation #(
parameter int DEVICE_WIDTH = 8, // 4, 8 or 16
parameter int BANK_GROUPS = 8, // 8 at x4/x8, 4 at x16
parameter int BANKS_PER_BG = 4, // constant at every width
parameter int ROW_BITS = 16, // R0-R15
parameter int COL_BITS = 10, // C0-C9 at x8/x16, C0-C10 at x4
parameter int DENSITY_GB = 16, // gigabits on the label
// ── Derived, not supplied. A caller that could override these
// could break the agreement the module exists to enforce.
parameter int BANKS = BANK_GROUPS * BANKS_PER_BG,
parameter int PAGE_BITS = (2 ** COL_BITS) * DEVICE_WIDTH,
// Field widths. BG indexes into BANK_GROUPS, so clog2(BANK_GROUPS).
parameter int BG_BITS = (BANK_GROUPS <= 1) ? 1 : $clog2(BANK_GROUPS),
parameter int BA_BITS = (BANKS_PER_BG <= 1) ? 1 : $clog2(BANKS_PER_BG),
parameter int BANK_BITS = BG_BITS + BA_BITS,
// A COUNT of banks up to BANKS needs clog2(BANKS+1), not clog2(BANKS).
parameter int BANKCNT_W = $clog2(BANKS + 1)
) (
input logic clk,
input logic rst_n,
// ── Query interface. A bank address in, its decomposition out.
input logic q_valid,
input logic [BANK_BITS-1:0] q_bank,
output logic [BG_BITS-1:0] q_group,
output logic [BA_BITS-1:0] q_bank_in_group,
output logic q_in_range,
// ── The organisation, published so a consumer need not recompute it.
output logic [BANKCNT_W-1:0] n_banks,
output logic [BG_BITS:0] n_groups,
output logic [31:0] page_bytes,
output logic [31:0] rows_per_bank,
output logic [63:0] per_bank_bits,
// §5: how much of the address space one bank spans. The quantity
// that makes "more banks" cost something.
output logic [63:0] bank_span_bits,
output logic err_bank_out_of_range,
output logic any_error
);
// ── The organisation invariant, as elaboration checks. Each one is
// a check §2 or §3 performed by hand on the datasheet's table.
initial begin
if (!(DEVICE_WIDTH inside {4, 8, 16}))
$fatal(1, "ddr5_bank_organisation: DEVICE_WIDTH must be 4, 8 or 16 (got %0d)",
DEVICE_WIDTH);
if (BANKS_PER_BG != 4)
// DEVICE-VERIFIED constant at every width (§1). A different
// value is not a DDR5 organisation.
$fatal(1, "ddr5_bank_organisation: BANKS_PER_BG must be 4 (got %0d)",
BANKS_PER_BG);
if (!(BANK_GROUPS inside {4, 8}))
$fatal(1, "ddr5_bank_organisation: BANK_GROUPS must be 4 or 8 (got %0d)",
BANK_GROUPS);
// §1: the x16 organisation has FOUR groups and the narrower
// widths have eight. A x16 part with eight groups is not a
// device this addendum describes.
if ((DEVICE_WIDTH == 16) && (BANK_GROUPS != 4))
$fatal(1, "ddr5_bank_organisation: x16 has 4 bank groups, not %0d",
BANK_GROUPS);
if ((DEVICE_WIDTH != 16) && (BANK_GROUPS != 8))
$fatal(1, "ddr5_bank_organisation: x4/x8 have 8 bank groups, not %0d",
BANK_GROUPS);
// Powers of two, with the >= 2 lower bound the shift arithmetic
// needs. A power-of-two test ALONE accepts 0 and 1, both of which
// would make a clog2 zero and the field width wrong.
if (BANK_GROUPS < 2 || (BANK_GROUPS & (BANK_GROUPS - 1)) != 0)
$fatal(1, "ddr5_bank_organisation: BANK_GROUPS must be a power of two >= 2");
if (BANKS < 2 || (BANKS & (BANKS - 1)) != 0)
$fatal(1, "ddr5_bank_organisation: BANKS must be a power of two >= 2");
if (BANK_BITS != $clog2(BANKS))
// The field widths must address exactly the stated bank count --
// §2's check that the feature list and the addressing table agree.
$fatal(1, "ddr5_bank_organisation: BG+BA bits (%0d) do not address %0d banks",
BANK_BITS, BANKS);
if (ROW_BITS < 1 || COL_BITS < 1)
$fatal(1, "ddr5_bank_organisation: field widths must be positive");
// §3's whole-die check: rows x page x banks must equal the density.
if (((2 ** ROW_BITS) * PAGE_BITS * BANKS) != (DENSITY_GB * (1024 ** 3)))
$fatal(1,
"ddr5_bank_organisation: rows(%0d) x page(%0d bits) x banks(%0d) != %0d Gb",
2 ** ROW_BITS, PAGE_BITS, BANKS, DENSITY_GB);
if ((PAGE_BITS % 8) != 0)
$fatal(1, "ddr5_bank_organisation: page must be a whole number of bytes");
end
logic e_range;
// Declared above the continuous assigns that read it.
logic in_rng;
assign in_rng = q_valid && ({{(32-BANK_BITS){1'b0}}, q_bank} < 32'(BANKS));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) e_range <= 1'b0;
else e_range <= q_valid && !in_rng;
end
// Field split follows the datasheet's own decomposition: the group
// field is the high part and the bank-within-group the low part.
// 8.4 owns extracting these from a full address; this is the
// device-side decomposition of an already-formed bank number.
assign q_group = q_bank[BANK_BITS-1 -: BG_BITS];
assign q_bank_in_group = q_bank[BA_BITS-1 : 0];
assign q_in_range = in_rng;
assign n_banks = BANKCNT_W'(BANKS);
assign n_groups = (BG_BITS+1)'(BANK_GROUPS);
assign page_bytes = 32'(PAGE_BITS / 8);
assign rows_per_bank = 32'(2 ** ROW_BITS);
assign per_bank_bits = 64'(2 ** ROW_BITS) * 64'(PAGE_BITS);
// §5: one bank spans this much of the address space, so a
// sequential walk crosses a bank boundary every this-many bits.
assign bank_span_bits = 64'(2 ** ROW_BITS) * 64'(PAGE_BITS);
assign err_bank_out_of_range = e_range;
assign any_error = e_range;
endmoduleEvery elaboration check here is a check §2 or §3 did by hand, and that is the point of the block. The datasheet's feature list, its addressing table and its density label are three independent statements about one device; the module refuses to build unless they agree.
The width-specific group check is the sharpest one. §1 verified that x16 has four groups and the narrower widths have eight. A parameterisation claiming a x16 part with eight groups describes no device in the addendum — and without that check it would elaborate happily and produce a four-bit bank field for a part that needs five.
11. The Same-Group Opportunity Monitor
// ---------------------------------------------------------------------
// same_group_opportunity_monitor -- measures the observed same-group
// fraction of a request stream against the structural baseline §4
// derives from the organisation alone.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHY IT COMPARES AGAINST A BASELINE: §4 computes that a random pair
// of distinct banks shares a group 3/31 of the time at x8 and 3/15 at
// x16. That is what a RANDOM stream would produce. A real stream that
// does better has a mapping working for it; one that does worse has a
// mapping working against it -- and neither is visible from the
// same-group count alone.
//
// WHAT IT DOES NOT MODEL:
// - the shared column path (5.3), or any timing (14.6, 14.7, 16.3)
// - ANY PENALTY. §4's callout: DDR5's same-group cost is not
// verified anywhere in this chapter, so this block reports
// FREQUENCY and never converts it to cycles.
//
// NO DIVISION: the observed fraction and the baseline are compared by
// CROSS-MULTIPLICATION, and both are published as numerator and
// denominator pairs so the caller states its own denominator.
// ---------------------------------------------------------------------
module same_group_opportunity_monitor #(
parameter int BANK_GROUPS = 8,
parameter int BANKS_PER_BG = 4,
parameter int BANKS = BANK_GROUPS * BANKS_PER_BG,
parameter int BG_BITS = (BANK_GROUPS <= 1) ? 1 : $clog2(BANK_GROUPS),
parameter int BANK_BITS = $clog2(BANKS),
parameter int CNT_W = 32
) (
input logic clk,
input logic rst_n,
input logic req_valid,
input logic [BANK_BITS-1:0] req_bank,
// ── Observed, as a pair. Consecutive DISTINCT banks only: a repeat
// of the same bank is neither a same-group nor a different-group
// pair, and counting it as either would bias the fraction.
output logic [CNT_W-1:0] n_pairs,
output logic [CNT_W-1:0] n_same_group,
output logic [CNT_W-1:0] n_diff_group,
output logic [CNT_W-1:0] n_same_bank,
// ── The structural baseline of §4: (BANKS_PER_BG-1)/(BANKS-1).
output logic [CNT_W-1:0] base_num,
output logic [CNT_W-1:0] base_den,
// Verdict by cross-multiplication, never by dividing. A mapping is
// "better than random" when observed/pairs < base_num/base_den.
output logic better_than_random,
output logic worse_than_random,
output logic at_baseline,
output logic err_bank_out_of_range,
output logic any_saturated
);
initial begin
if (BANKS_PER_BG < 2)
$fatal(1, "same_group_opportunity_monitor: BANKS_PER_BG must be >= 2");
if (BANKS <= BANKS_PER_BG)
// With one group every pair is same-group and the baseline is 1,
// which makes the comparison meaningless rather than extreme.
$fatal(1, "same_group_opportunity_monitor: need more than one bank group");
if (BANK_BITS != $clog2(BANKS))
$fatal(1, "same_group_opportunity_monitor: BANK_BITS must address BANKS");
end
logic [CNT_W-1:0] c_pair, c_same, c_diff, c_reps;
logic have_prev, sat, e_rng;
logic [BANK_BITS-1:0] prev_bank;
// Declared above the continuous assigns that read them.
logic [BG_BITS-1:0] this_grp, prev_grp;
assign this_grp = req_bank[BANK_BITS-1 -: BG_BITS];
assign prev_grp = prev_bank[BANK_BITS-1 -: BG_BITS];
logic in_rng;
assign in_rng = ({{(32-BANK_BITS){1'b0}}, req_bank} < 32'(BANKS));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_pair <= '0; c_same <= '0; c_diff <= '0; c_reps <= '0;
have_prev <= 1'b0; prev_bank <= '0; sat <= 1'b0; e_rng <= 1'b0;
end else begin
e_rng <= 1'b0;
if (req_valid) begin
if (!in_rng) begin
// Out of range: reported, and NOT folded into the counts. A
// bad bank number classified as either kind would corrupt
// the fraction the block exists to report.
e_rng <= 1'b1;
end else begin
if (have_prev) begin
if (req_bank == prev_bank) begin
// Same bank is neither pair kind -- counted separately.
if (c_reps != {CNT_W{1'b1}}) c_reps <= c_reps + CNT_W'(1);
end else begin
if (c_pair == {CNT_W{1'b1}}) sat <= 1'b1;
else begin
c_pair <= c_pair + CNT_W'(1);
if (this_grp == prev_grp) c_same <= c_same + CNT_W'(1);
else c_diff <= c_diff + CNT_W'(1);
end
end
end
prev_bank <= req_bank;
have_prev <= 1'b1;
end
end
end
end
assign n_pairs = c_pair;
assign n_same_group = c_same;
assign n_diff_group = c_diff;
assign n_same_bank = c_reps;
assign base_num = CNT_W'(BANKS_PER_BG - 1);
assign base_den = CNT_W'(BANKS - 1);
// observed(c_same/c_pair) vs baseline(base_num/base_den), compared
// as c_same*base_den vs base_num*c_pair. No divide.
assign better_than_random =
(c_pair != '0) && ((c_same * CNT_W'(BANKS - 1)) < (CNT_W'(BANKS_PER_BG - 1) * c_pair));
assign worse_than_random =
(c_pair != '0) && ((c_same * CNT_W'(BANKS - 1)) > (CNT_W'(BANKS_PER_BG - 1) * c_pair));
assign at_baseline =
(c_pair != '0) && ((c_same * CNT_W'(BANKS - 1)) == (CNT_W'(BANKS_PER_BG - 1) * c_pair));
assign err_bank_out_of_range = e_rng;
assign any_saturated = sat;
endmoduleA repeated bank is counted separately and excluded from the pairs. §4's baseline is the chance that a different bank shares a group, so a stream that hits the same bank repeatedly would drag the observed fraction toward zero and look like an excellent mapping. Separating the three outcomes keeps the comparison to the question the baseline answers.
And the comparison is a cross-multiplication. Dividing two counters to get a percentage would introduce a divide and a rounding decision, and the verdict only needs an ordering. The two published pairs let a caller compute whatever ratio it wants with its own denominator.
12. The Page-Granularity Accountant
// ---------------------------------------------------------------------
// page_granularity_accountant -- counts page and bank crossings for a
// stream, which is what §5 says "more banks" costs.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT ADDS: §5 argues that DDR5's smaller banks put boundaries
// twice as close together at x8, so a sequential walk crosses them
// twice as often. That is a claim about a STREAM, not about a device,
// so it needs measuring rather than deriving.
//
// WHAT IT DOES NOT MODEL: energy, bandwidth, any timing parameter, or
// the row buffer's contents. 23.3 owns row-buffer locality and what a
// hit is worth; this counts boundary crossings and nothing more.
// ---------------------------------------------------------------------
module page_granularity_accountant #(
parameter int ADDR_W = 36,
parameter int PAGE_BYTES = 1024, // DEVICE-VERIFIED at x8 (§1)
parameter int BANKS = 32,
parameter int ROWS = 65536,
parameter int PAGE_SH = $clog2(PAGE_BYTES),
parameter int CNT_W = 32
) (
input logic clk,
input logic rst_n,
input logic acc_valid,
input logic [ADDR_W-1:0] acc_addr,
input logic [15:0] acc_bytes,
// ── Crossings.
output logic [CNT_W-1:0] n_accesses,
output logic [CNT_W-1:0] n_page_crossings,
output logic [CNT_W-1:0] n_within_page,
// An access that itself spans a page boundary -- distinct from two
// consecutive accesses landing in different pages.
output logic [CNT_W-1:0] n_self_spanning,
// ── Span facts, published so a caller need not recompute them.
output logic [31:0] page_span_bytes,
output logic [63:0] bank_span_bytes,
output logic err_zero_length,
output logic err_access_exceeds_page,
output logic any_saturated
);
initial begin
if (PAGE_BYTES < 2 || (PAGE_BYTES & (PAGE_BYTES - 1)) != 0)
// The lower bound matters: a power-of-two test alone accepts 0
// and 1, either of which makes PAGE_SH zero and every shift a
// no-op.
$fatal(1, "page_granularity_accountant: PAGE_BYTES must be a power of two >= 2");
if (BANKS < 1 || ROWS < 1)
$fatal(1, "page_granularity_accountant: BANKS and ROWS must be positive");
if (PAGE_SH >= ADDR_W)
$fatal(1, "page_granularity_accountant: page field exceeds ADDR_W");
end
logic [CNT_W-1:0] c_acc, c_cross, c_within, c_span;
logic have_prev, sat, e_zero, e_big;
logic [ADDR_W-1:0] prev_page;
// Declared above the continuous assigns that read them.
logic [ADDR_W-1:0] this_page, end_page;
assign this_page = acc_addr >> PAGE_SH;
// The page the LAST byte of this access falls in. Equal to
// this_page unless the access itself straddles a boundary.
assign end_page = (acc_bytes == '0) ? this_page
: ((acc_addr + ADDR_W'(acc_bytes) - ADDR_W'(1)) >> PAGE_SH);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_acc <= '0; c_cross <= '0; c_within <= '0; c_span <= '0;
have_prev <= 1'b0; prev_page <= '0; sat <= 1'b0;
e_zero <= 1'b0; e_big <= 1'b0;
end else begin
e_zero <= 1'b0; e_big <= 1'b0;
if (acc_valid) begin
if (acc_bytes == '0) begin
e_zero <= 1'b1;
end else begin
if (c_acc == {CNT_W{1'b1}}) sat <= 1'b1;
else begin
c_acc <= c_acc + CNT_W'(1);
if (have_prev) begin
if (this_page != prev_page) c_cross <= c_cross + CNT_W'(1);
else c_within <= c_within + CNT_W'(1);
end
if (end_page != this_page) c_span <= c_span + CNT_W'(1);
end
if ({{(32-16){1'b0}}, acc_bytes} > 32'(PAGE_BYTES))
// Larger than a page: legal to request and it guarantees
// a crossing, so it is reported rather than silently
// inflating the crossing count.
e_big <= 1'b1;
prev_page <= this_page;
have_prev <= 1'b1;
end
end
end
end
assign n_accesses = c_acc;
assign n_page_crossings = c_cross;
assign n_within_page = c_within;
assign n_self_spanning = c_span;
assign page_span_bytes = 32'(PAGE_BYTES);
// §5: one bank spans rows x page bytes of the address space.
assign bank_span_bytes = 64'(ROWS) * 64'(PAGE_BYTES);
assign err_zero_length = e_zero;
assign err_access_exceeds_page = e_big;
assign any_saturated = sat;
endmodulen_self_spanning is separate from n_page_crossings and the distinction is real. Two consecutive accesses in different pages is a stream property that an address mapping can improve. A single access that straddles a page boundary is an alignment property that no mapping fixes — it needs two row activations no matter how the address space is arranged.
13. What the Assertions Prove
// Bind unit note: P1-P5 reference ddr5_bank_organisation's internals,
// P6-P10 same_group_opportunity_monitor's, P11-P14
// page_granularity_accountant's. Each is written as though bound into
// its own module.
// P1 -- the decomposition is exact: recombining the group and the
// bank-within-group reproduces the queried bank.
property p_decomposition_is_lossless;
@(posedge clk) disable iff (!rst_n)
q_in_range |-> ({q_group, q_bank_in_group} == q_bank);
endproperty
assert property (p_decomposition_is_lossless);
// P2 -- the published bank count always equals the parameterised
// organisation. Guards the COUNT-vs-INDEX trap: a too-narrow
// BANKCNT_W would silently truncate 32 to 0.
property p_bank_count_matches_organisation;
@(posedge clk) disable iff (!rst_n)
(n_banks == BANKCNT_W'(BANK_GROUPS * BANKS_PER_BG));
endproperty
assert property (p_bank_count_matches_organisation);
// P3 -- §3's whole-die identity, as a runtime invariant as well as an
// elaboration check: rows x page x banks is the density.
property p_die_identity_holds;
@(posedge clk) disable iff (!rst_n)
((per_bank_bits * 64'(BANKS)) == (64'(DENSITY_GB) * 64'(1024*1024*1024)));
endproperty
assert property (p_die_identity_holds);
// P4 -- FORBIDDEN. A bank number outside the organisation is never
// reported in range, and is always flagged.
property p_out_of_range_never_accepted;
@(posedge clk) disable iff (!rst_n)
(q_valid && ({{(32-BANK_BITS){1'b0}}, q_bank} >= 32'(BANKS)))
|-> (!q_in_range ##1 err_bank_out_of_range);
endproperty
assert property (p_out_of_range_never_accepted);
// P5 -- §2's page derivation, as an invariant: page bytes is
// 2^colbits x width / 8.
property p_page_size_derivation;
@(posedge clk) disable iff (!rst_n)
(page_bytes == 32'(((2 ** COL_BITS) * DEVICE_WIDTH) / 8));
endproperty
assert property (p_page_size_derivation);
// P6 -- the three pair outcomes partition every classified request
// after the first. Nothing is double-counted or lost.
property p_pair_outcomes_partition;
@(posedge clk) disable iff (!rst_n)
((n_same_group + n_diff_group) == n_pairs);
endproperty
assert property (p_pair_outcomes_partition);
// P7 -- FORBIDDEN. A repeated bank never counts as a pair. §11: it is
// neither same-group nor different-group, and counting it as either
// would bias the fraction toward "better than random".
property p_repeat_is_never_a_pair;
@(posedge clk) disable iff (!rst_n)
(req_valid && (req_bank == $past(req_bank, 1)) && $past(req_valid, 1))
|=> (n_pairs == $past(n_pairs, 1));
endproperty
assert property (p_repeat_is_never_a_pair);
// P8 -- FORBIDDEN. An out-of-range bank is never folded into any
// pair count. A bad bank number classified either way corrupts the
// measurement the block exists to produce.
property p_bad_bank_never_counted;
@(posedge clk) disable iff (!rst_n)
err_bank_out_of_range |-> ((n_pairs == $past(n_pairs, 1)) &&
(n_same_group == $past(n_same_group, 1)));
endproperty
assert property (p_bad_bank_never_counted);
// P9 -- the baseline is the structural fraction of §4 and does not
// depend on the stream at all.
property p_baseline_is_structural;
@(posedge clk) disable iff (!rst_n)
((base_num == CNT_W'(BANKS_PER_BG - 1)) && (base_den == CNT_W'(BANKS - 1)));
endproperty
assert property (p_baseline_is_structural);
// P10 -- the three verdicts are mutually exclusive, and none holds
// before any pair has been seen. A verdict on zero samples would be
// an opinion rather than a measurement.
property p_verdict_exclusive_and_grounded;
@(posedge clk) disable iff (!rst_n)
((n_pairs == '0) ? (!better_than_random && !worse_than_random && !at_baseline)
: $onehot({better_than_random, worse_than_random, at_baseline}));
endproperty
assert property (p_verdict_exclusive_and_grounded);
// P11 -- crossings and within-page accesses partition every access
// after the first.
property p_crossings_partition;
@(posedge clk) disable iff (!rst_n)
((n_page_crossings + n_within_page) <= n_accesses);
endproperty
assert property (p_crossings_partition);
// P12 -- an access larger than a page is always flagged, and always
// self-spanning. It cannot fit, so it must straddle.
property p_oversize_access_spans;
@(posedge clk) disable iff (!rst_n)
(acc_valid && (acc_bytes != '0) &&
({{(32-16){1'b0}}, acc_bytes} > 32'(PAGE_BYTES)))
|=> err_access_exceeds_page;
endproperty
assert property (p_oversize_access_spans);
// P13 -- FORBIDDEN. A zero-length access is never counted. It is not
// silently treated as one byte, which would invent a crossing.
property p_zero_length_not_counted;
@(posedge clk) disable iff (!rst_n)
(acc_valid && (acc_bytes == '0))
|=> ((n_accesses == $past(n_accesses, 1)) && err_zero_length);
endproperty
assert property (p_zero_length_not_counted);
// P14 -- §5's span arithmetic: a bank covers rows x page bytes.
property p_bank_span_is_rows_times_page;
@(posedge clk) disable iff (!rst_n)
(bank_span_bytes == (64'(ROWS) * 64'(PAGE_BYTES)));
endproperty
assert property (p_bank_span_is_rows_times_page);
// ── Cover.
// Every bank group reached, and every bank within a group.
cover property (@(posedge clk) disable iff (!rst_n)
q_in_range && (q_group == '0));
cover property (@(posedge clk) disable iff (!rst_n)
q_in_range && (q_group == BG_BITS'(BANK_GROUPS - 1)));
cover property (@(posedge clk) disable iff (!rst_n)
q_in_range && (q_bank_in_group == BA_BITS'(BANKS_PER_BG - 1)));
// Each forbidden condition ATTEMPTED. A property forbidding something
// is vacuous until the testbench tries it.
cover property (@(posedge clk) disable iff (!rst_n) err_bank_out_of_range);
cover property (@(posedge clk) disable iff (!rst_n) err_zero_length);
cover property (@(posedge clk) disable iff (!rst_n) err_access_exceeds_page);
// A repeated bank actually driven -- P7 is vacuous without it.
cover property (@(posedge clk) disable iff (!rst_n)
req_valid && $past(req_valid,1) && (req_bank == $past(req_bank,1)));
// All three mapping verdicts reached. "At baseline" is the one a
// suite is likeliest to miss.
cover property (@(posedge clk) disable iff (!rst_n) better_than_random);
cover property (@(posedge clk) disable iff (!rst_n) worse_than_random);
cover property (@(posedge clk) disable iff (!rst_n) at_baseline);
// Both crossing kinds, and the distinction of §12.
cover property (@(posedge clk) disable iff (!rst_n)
acc_valid && (n_page_crossings != $past(n_page_crossings,1)));
cover property (@(posedge clk) disable iff (!rst_n) n_self_spanning != '0);
// Both verified organisations exercised: 32 banks and 16 banks.
cover property (@(posedge clk) disable iff (!rst_n) n_banks == BANKCNT_W'(32));
cover property (@(posedge clk) disable iff (!rst_n) n_banks == BANKCNT_W'(16));14. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
| x16 parameterised with 8 bank groups | $fatal at elaboration | §1 — x16 has four groups; the bank field would be a bit short |
| x8 parameterised with 4 bank groups | $fatal at elaboration | §1 — the narrower widths have eight |
BANKS_PER_BG other than 4 | $fatal at elaboration | DEVICE-VERIFIED constant at every width |
BANK_GROUPS of 0 or 1 | $fatal at elaboration | A power-of-two test alone accepts both |
| Fields that do not address the stated bank count | $fatal at elaboration | §2's check that the two datasheet statements agree |
| Rows × page × banks ≠ density | $fatal at elaboration | §3's whole-die identity |
Bank number ≥ BANKS | Reported; never in range, never counted | P4, P8 |
| Same bank twice in a row | Counted as a repeat, not a pair | P7 — counting it would bias toward “better than random” |
| No pairs seen yet | No verdict asserted | P10 — a verdict on zero samples is an opinion |
| Observed exactly at the structural fraction | at_baseline | The stream is behaving randomly with respect to groups |
| Zero-length access | Reported; not counted | P13 — treating it as one byte would invent a crossing |
| Access larger than a page | Reported; counted; always self-spanning | P12 — it cannot fit, so it must straddle |
| Access straddling a page boundary | n_self_spanning, separately from crossings | §12 — alignment, which no mapping fixes |
PAGE_BYTES of 0 or 1 | $fatal at elaboration | PAGE_SH would be zero and every shift a no-op |
Row eight is the one worth dwelling on. A stream that hammers one bank produces zero same-group pairs, and a monitor that counted repeats as different-group pairs would report it as an ideal mapping. It is the opposite — a stream with no bank-level parallelism at all.
15. DV — Two Representations of One Organisation
// Independent organisation reference. Builds the bank list explicitly
// as a table of (group, bank-in-group) pairs rather than by field
// arithmetic, so agreement with the DUT is evidence rather than a
// restatement of the same shift. SIMULATION-ONLY.
class organisation_ref;
int grp_of[$]; // grp_of[bank] = its group
int idx_of[$]; // idx_of[bank] = its index within the group
int groups, per_group;
function void build(int n_groups, int banks_per_group);
groups = n_groups; per_group = banks_per_group;
grp_of.delete(); idx_of.delete();
for (int g = 0; g < n_groups; g++)
for (int b = 0; b < banks_per_group; b++) begin
grp_of.push_back(g);
idx_of.push_back(b);
end
endfunction
function int n_banks(); return grp_of.size(); endfunction
// The structural baseline of §4, as a pair -- never as a real.
function void baseline(output int num, output int den);
num = per_group - 1;
den = grp_of.size() - 1;
endfunction
// Page size from the fields, computed independently of the DUT.
function int page_bytes(int col_bits, int width);
return ((1 << col_bits) * width) / 8;
endfunction
// §3's whole-die identity.
function bit die_consistent(int row_bits, int col_bits, int width,
int density_gb);
longint total;
total = longint'(1 << row_bits) * longint'((1 << col_bits) * width)
* longint'(grp_of.size());
return total == longint'(density_gb) * 1024 * 1024 * 1024;
endfunction
endclass| Check | What it establishes |
|---|---|
Every bank's (group, index) matches the reference table | P1 against a non-arithmetic representation |
| Sweep all 32 banks at x8 and all 16 at x16 | Both verified organisations, and the two bank-count covers |
Reference page_bytes against the DUT at x4, x8 and x16 | §2's derivation reproduces all three published page sizes |
Reference die_consistent at all three widths | §3's identity, independently computed |
| Parameterise x16 with 8 groups; expect no elaboration | §14's first row |
Parameterise BANK_GROUPS as 0 and 1 | The guard that a power-of-two test alone would pass |
| Drive a uniformly random bank stream over many pairs | Observed fraction converges on the reference baseline |
| Drive a stream that alternates between two groups | better_than_random, strongly |
| Drive a stream confined to one group | worse_than_random |
| Drive a stream repeating one bank | Zero pairs, no verdict, n_same_bank rising |
| Drive out-of-range banks between valid ones | P8 — counts unchanged across the bad request |
| Sequential walk of 1 MB at 1 KB and at 2 KB page | Crossing count halves; §5's claim, measured |
| Unaligned 64 B accesses across a page boundary | n_self_spanning rises while crossings do not |
The tenth check is the one that produces a report worth publishing, because it is the failure mode §14's row eight describes and it looks like success:
THE STREAM THAT LOOKS LIKE A PERFECT MAPPING
A request stream confined to a single bank, 100,000 accesses.
n_pairs 0
n_same_group 0
n_diff_group 0
n_same_bank 99,999
verdict: none asserted (P10 holds: no pairs, no opinion)
assertions: P6 pair outcomes partition PASS (0 + 0 == 0)
P7 repeat is never a pair PASS
P9 baseline is structural PASS
all 14 properties PASS
what a NAIVE monitor would have reported:
same-group fraction 0 / 99,999 = 0.000%
baseline 3 / 31 = 9.677%
verdict: BETTER THAN RANDOM, by a wide margin
diagnosis : the naive reading is exactly backwards. A stream with
zero same-group pairs because it never leaves one bank has NO
bank-level parallelism at all -- it is the worst possible
stream, reported as the best.
what makes it dangerous : the same-group count is the number a
mapping study naturally optimises, and it is minimised by the
degenerate stream. The metric and the goal diverge precisely
where the design is worst.
the fix : the three outcomes must PARTITION and the repeat count
must be visible (§11). P10's refusal to render a verdict on
zero pairs is what stops the degenerate case from being scored
at all, and n_same_bank is what names it.16. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Design will not elaborate at x16 | Group count left at 8 — §14 | The $fatal message names the expected value |
| Bank field one bit short | x16 organisation with an x8 field width | P2; BANK_BITS against $clog2(BANKS) |
| Addresses alias onto the wrong bank | Field widths do not match the bank count | §2's check; the elaboration guard catches it first |
| Page size disagrees with the datasheet | Column bits taken from the wrong width | P5; x4 has eleven column bits, not ten |
| Whole-die check fails | Row, page and bank figures from different parts | §3's identity; all three must come from one table |
| Same-group fraction suspiciously low | The stream may be hitting one bank — §14 row eight | n_same_bank against n_pairs |
| No verdict ever asserted | Fewer than one distinct-bank pair seen | P10 — this is correct behaviour, not a fault |
| Mapping tuned for DDR5 gains nothing | The part is x16 — its group statistics equal DDR4 x8 | §4's table; check the width, not the generation |
| Concurrency lower than 32 banks suggests | Mapping not spreading across groups | worse_than_random; 18.1 owns the fix |
| More activations than DDR4 at the same workload | Smaller banks, closer boundaries — §5 | n_page_crossings; bank_span_bytes halved |
| Crossings high but mapping looks correct | Accesses straddling boundaries, not stream drift | n_self_spanning — alignment, not mapping |
Row eight is the entry this chapter exists to add. “We moved to DDR5 and the bank-group mapping stopped helping” is explained by the device width, not by the generation — and an engineer who does not know that x16 has DDR4's group count will look for the fault in the controller.
17. Misconceptions
“DDR5 doubled the bank groups.” §1. At x4 and x8, yes — eight against four. At x16 it is four, the same as DDR4's x8.
“DDR5 has 32 banks.” §1. At x4 and x8. An x16 part has 16.
“x16 is a wider x8.” §3. It has half the banks and twice the page. Same silicon, different partition.
“DDR5 pages are smaller.” §3's callout. Nothing retrieved shows a page below the 1 KB that DDR4 parts of this density also carry, and the x16 part's page is 2 KB.
“More banks is a pure win.” §5. The banks are also smaller — half the rows each — so a sequential stream crosses bank boundaries twice as often.
“DDR5's bank groups are twice as good.” §4's callout. The expensive relationship arises about half as often at x8. Whether it costs the same as DDR4's is not established anywhere in this chapter.
“The same-group penalty carried over from DDR4.” §4. 16.3 §4 left this open and no DDR5 timing value was retrieved here either. An inherited claim needs a reason.
“A low same-group fraction means a good mapping.” §15. A stream confined to one bank has a fraction of zero and no parallelism whatsoever.
“The row count grew with capacity.” §3. R0–R15 at every width — 65,536 rows per bank, identical. What changes is the row's width and the bank count.
“This chapter verified DDR5's timings.” §1's callout. It verified the organisation. The timing questions 16.3 §4 opened are still open.
18. Interview Reasoning
How many banks does a DDR5 device have? It depends on the width. At x4 and x8, eight bank groups of four banks each — 32 banks. At x16, four groups of four — 16 banks.
Derive a DDR5 device's page size from its addressing fields. Page size is 2 to the power of the column-address bits, times the device width. At x8 with ten column bits: 1024 × 8 bits = 8192 bits = 1 KB. At x16 with ten column bits: 1024 × 16 = 2 KB.
Check a die's organisation for self-consistency. Rows times page size times bank count must equal the density. At x8: 65,536 × 1 KB × 32 = 16 Gb. If it does not multiply out, one of the four figures is from a different part.
Why is x16 not simply a wider x8? Half the banks and twice the page. It favours locality where x8 favours concurrency, and the choice is usually made for device count per sub-channel rather than for either.
What does doubling the bank groups actually change? How often two distinct banks share a group — 3 in 15 at four groups, 3 in 31 at eight. The cheap relationship becomes about twice as likely; the cost of the expensive one is a separate question.
Does that mean DDR5 is twice as good here? No. The frequency halves; the penalty's size is a different claim, and it needs its own citation rather than inheritance from DDR4.
What does having more banks cost? The banks are smaller at fixed density — half the rows each — so each bank spans half as much address space and a sequential stream crosses bank boundaries twice as often.
Your same-group fraction is zero. Is your mapping perfect? Possibly the opposite. Check whether the stream is repeating one bank: zero distinct-bank pairs means no bank-level parallelism, which minimises the metric and maximises the problem.
A bank-group-aware mapping that helped on one DDR5 part does nothing on another. Why? Check the device width. An x16 part has four groups — DDR4's count — so the improved group statistics the mapping was tuned for do not exist on it.
19. Exercises
-
Derive the page size, per-bank capacity and bank count for a hypothetical 32 Gb DDR5 x8 die, stating which field you must change from §1's table and why the others cannot absorb the difference.
-
§4 gives the same-group chance for a randomly chosen different bank. Derive the chance for two independently chosen banks, including the same-bank case, and explain why §11's monitor uses the first definition.
-
Using §5's figures, compute how many more activations a 16 MB sequential read issues on DDR5 x8 than on DDR4 x8 at the same density, stating every assumption you must add.
-
Remove the width-specific group check from §10's block and parameterise an x16 part with eight groups. Which property fires, and which does not? Explain why P3 still passes.
-
§4's callout says the frequency halves while the penalty is unverified. Construct the condition on the DDR5 penalty under which DDR5 x8 is worse than DDR4 x8 for same-group traffic, and say whether the retrieved material can rule it out.
-
§11's monitor excludes repeated banks. Construct the stream that maximises
better_than_randomwhile having the worst possible bank-level parallelism, and propose the additional output that would expose it. -
For an x16 part, compute the same-group chance and compare it against DDR4 x8. Then state what an x16 DDR5 part does offer over a DDR4 x8 part of the same density, using only figures verified in this chapter.
-
§12 separates stream crossings from self-spanning accesses. Derive the fraction of 64-byte accesses that self-span at 1 KB and at 2 KB pages under uniformly random alignment, and say which page size is better on that measure alone.
20. Where This Goes
DDR5's bank organisation is now verified rather than corroborated. Eight bank groups of four banks at x4 and x8 for 32 banks; four groups of four at x16 for 16; 65,536 rows per bank at every width; and a page of 1 KB, 1 KB and 2 KB respectively — with all three widths multiplying out to exactly 16 Gb.
Four results carry forward. The retrieval closed, which upgrades 16.3 §4's corroboration to a primary citation without changing its numbers. The x16 organisation is not a scaled x8 — half the banks, twice the page, and DDR4's group statistics. More banks costs granularity, because at fixed density the banks are smaller and the boundaries closer. And the frequency of the expensive bank relationship halves at x8 while its cost stays unverified, which is a weaker and more useful claim than the one usually made.
Two things left open, and both were open before this chapter. DDR5's nCCD, nRRD and nFAW values did not retrieve, and the die addendum consulted supersedes parts of a core datasheet that did not retrieve either. Nor did the _S / _L naming question close. Chapter 16.3 §4 counted seven DDR5 questions this curriculum could not close from accessible material; this chapter closed one of them and leaves the timing ones where they were.
What every section here has assumed is that a command simply arrives at the device. Chapter 7.1 §4 is explicit that it does not: its command encoder refuses to elaborate for DDR5, with the comment that “DDR5's CA encoding is not modelled” and Module 25 owns it. Chapter 6.4 §7 adds that in DDR5 the arrangement changed again, “with a differently encoded command/address interface in which RAS# does not appear.”
Chapter 25.2 takes up that debt: a fourteen-bit command and address bus, commands that occupy one or two clock cycles, CS_n that is part of the command code rather than a qualifier beside it, and two sub-channels each carrying its own copy of all of it. Chapter 4.6 §2 argued why that duplication is necessary; the next chapter is what is actually duplicated.
Continue learning
Related tutorials
- Related topic
DDR4
DDR4 is the generation where prefetch depth stops changing. With granularity already at a cache line, the rate had to come from overlapping independent accesses instead — which is what bank groups are, and why peak bandwidth became conditional on the access pattern.
- Related topic
Bank Groups
Not all bank pairs are equally independent. A bank group is the scope at which the internal column data path is shared, and the three-way classification of a request against its predecessor is the interface every later timing module consumes.
- Related topic
DDR4 / DDR5 Bank-Group Concepts
The same-group penalty is not one number. It reaches twice for column commands, tracks that for activates on x8 parts, and on one verified x16 configuration disappears entirely.
- Related topic
DDR (DDR1)
Double data rate doubles transfer opportunities per clock cycle, not the clock. Two mechanisms make that survivable: a 2n prefetch so a slow array can feed a fast interface, and a source-synchronous DQS strobe so data carries its own timing.
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.
