Skip to content
VLSI Mentor

DDR · Module 8

Bank-Group Address

Bank groups are a DDR4 and DDR5 field, which makes this the first address field that may not exist. How the bank index splits between group and bank decides whether consecutive accesses are neighbours or strangers.

Chapter 8.3 treated the bank as a single flat index and showed that its position in the map decides how quickly traffic reaches an independent resource. That treatment was accurate for DDR3 and everything before it.

It is incomplete for DDR4 and DDR5, because in those generations not all banks are equally independent.

Chapter 5.3 established why: banks are organised into groups, and banks in the same group share a column data path while banks in different groups do not. So a flat bank index hides a distinction the device genuinely makes — and the address map is where that distinction is either exposed or thrown away.

Which gives two questions, and they must be answered in order:

Does this generation have a bank-group field at all — and if it does, how should the bank index be split between group and bank?

The first question makes this the first field in the module that may not exist, and that turns out to be an interesting thing to build in RTL.

1. What a Bank-Group Address Selects

A bank-group address selects which group of banks the target bank belongs to.

That sounds like a minor refinement of Chapter 8.3's bank field, and structurally it is — together they name one bank. But what they select is different in kind:

FieldSelectsIndependence
bank (within a group)which bank inside the groupindependent row state
bank groupwhich groupindependent column data path

Chapter 5.3 laid this out: every bank owns its row state, so any two banks can hold different open rows. But the column data path — the resource that moves a column's worth of data toward the interface — is shared at the group level in DDR4 and DDR5. So two banks in the same group are independent in state and contended in data path. Two banks in different groups are independent in both.

Which means the pair of operands answers a question a flat index cannot: given two consecutive accesses, are they neighbours sharing a resource, or strangers sharing nothing?

That question has a name in this curriculum already — it is exactly what Chapter 5.3's classifier answers, and its three outcomes are SAME_BANK, SAME_GROUP_DIFF_BANK and DIFF_GROUP. This chapter's job is to notice that the address map decides which of those three a given traffic pattern produces.

2. The Generation Boundary

Before any of that, the field has to exist.

Verified: bank groups are a DDR4 and DDR5 feature. Chapter 5.3 §7 records the organisations:

GenerationDevice widthOrganisation
DDR3a flat bank index — no groups
DDR4x4 / x84 groups × 4 banks = 16
DDR4x162 groups × 4 banks = 8
DDR5x4 / x88 groups × 4 banks = 32
DDR5x164 groups × 4 banks = 16

Banks per group is 4 in every verified organisation, which is worth knowing because it means the group count carries all the variation. The DDR3 row states only what this chapter relies on — that there is no group field — since the bank count of a pre-DDR4 device is a per-device figure and the exercises below supply it as a given rather than as a claim.

So a DDR3 controller's map has no bank-group field, and a DDR4 controller's map has one whose width depends on the device. This is the first time in the module that a structural part of the map is generation-dependent, and it has to be handled structurally rather than with a runtime flag.

3. Splitting the Bank Index — Two Placements

Assume DDR4 with 4 groups of 4 banks: 16 banks, 4 bits of bank index in total, split 2 and 2.

The map must decide which address bits feed the group and which feed the bank, and the two natural choices sit next to each other:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  PLACEMENT A -- BG_LOW    (group bits below bank bits)
  ┌────────┬────────┬────────┬─────────┬──────────┐
  │  row   │   BA   │   BG   │ column  │  offset  │
  └────────┴────────┴────────┴─────────┴──────────┘
        14  13    12  11    10  9      6  5       0

  PLACEMENT B -- BG_HIGH   (bank bits below group bits)
  ┌────────┬────────┬────────┬─────────┬──────────┐
  │  row   │   BG   │   BA   │ column  │  offset  │
  └────────┴────────┴────────┴─────────┴──────────┘
        14  13    12  11    10  9      6  5       0

Identical widths. Identical total. The two middle fields swapped. And by Chapter 8.3 §2's rule — a field at bit p changes every 2^p bytes — the consequence is immediate:

A — BG_LOWB — BG_HIGH
group changes every1 KB4 KB
bank changes every4 KB1 KB
consecutive 1 KB blocks aredifferent groupssame group, different banks

That last row is the chapter. Under Placement A, a sequential stream walks group 0, 1, 2, 3 — so each access and its successor are in different groups, and Chapter 5.3's classifier reports DIFF_GROUP for every consecutive pair. Under Placement B, the stream walks banks 0, 1, 2, 3 within group 0 — so every consecutive pair is SAME_GROUP_DIFF_BANK.

Same device, same stream, opposite relationship between neighbouring accesses.

And note the symmetry with Chapter 8.3. That chapter's choice was about reaching independent state; this one is about avoiding a shared data path. Both are decided by field position, both cost zero gates, and both are invisible to any check that only verifies total map width.

4. RTL — A Field That May Not Exist

The engineering problem

Build a decomposition that produces bank-group and bank operands under either placement, and that is structurally correct for a generation with no bank-group field at all — without allocating address bits to a field that does not exist, and without producing an illegal zero-width signal.

Why hardware needs it

A controller IP targeting more than one generation must handle this. The naive solutions both fail: allocating the bits unconditionally mis-describes DDR3, and making the field's presence a runtime signal means the address map's shape changes at run time, which is not a thing a bit-field map can do.

Classification

SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.

What it models

Extraction of column, bank-group, bank and row operands under a selectable placement; the structural presence or absence of the bank-group field by generation; and the flat bank index that Chapter 5.2's state table needs.

What it does NOT model

Request-pair classificationChapter 5.3's bank_group_classifier consumes bg_operand and ba_operand and produces the same-bank / same-group / different-group verdict. Reimplementing it here would be duplicating an existing educational block under a new name. Also not modelled: the shared column data path itself (Chapter 5.3), bank state (Chapter 5.2), any timing rule (Modules 13, 14), scheduling (Modules 16, 17), and placement quality (Module 18).

Interface and parameter contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// bg_bank_field_map
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// BOTH PLACEMENTS ARE EXAMPLE CONTROLLER ADDRESS-MAPPING POLICIES. NEITHER
// IS A JEDEC-MANDATED UNIVERSAL DDR ADDRESS MAP.
//
// VERIFIED (JEDEC / vendor material): bank groups are a DDR4 and DDR5
// feature. DDR3 and earlier have a flat bank index and no group field.
// Banks per group is 4 in every verified DDR4 and DDR5 organisation.
//
// MODELS: column / bank-group / bank / row extraction under a selectable
// placement, the STRUCTURAL presence or absence of the bank-group field by
// generation, and the flat bank index Chapter 5.2's table indexes with.
//
// DOES NOT MODEL: request-pair classification -- Chapter 5.3's
// bank_group_classifier consumes these operands and must not be
// duplicated here -- the shared column data path (Chapter 5.3), bank state
// (Chapter 5.2), timing rules (Modules 13, 14), scheduling (Modules 16,
// 17), or placement quality (Module 18).
// ─────────────────────────────────────────────────────────────────────────
module bg_bank_field_map #(
  parameter int ADDR_W   = 32,
  parameter int OFFSET_W = 6,
  parameter int NUM_COLS = 16,

  // GENERATION. 3 = no bank groups. 4 or 5 = bank groups present.
  // This is a STRUCTURAL parameter: it changes the shape of the map, so it
  // cannot be a runtime input. See BG_MAP_W below.
  parameter int GEN = 4,

  // Must be 1 when GEN == 3. Verified DDR4/DDR5 group counts are 2, 4 or 8
  // depending on generation and device width -- Section 2.
  parameter int NUM_GROUPS      = 4,
  // Verified as 4 in every DDR4 and DDR5 organisation. Parameterised
  // because this is an educational model, not because the value varies.
  parameter int BANKS_PER_GROUP = 4,

  parameter int ROW_W = 16,

  // 0 = BG_LOW  : offset | column | bg | ba | row
  // 1 = BG_HIGH : offset | column | ba | bg | row
  parameter int BG_PLACEMENT = 0,

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

  // ── THE ZERO-WIDTH PROBLEM, SOLVED IN ONE LINE.
  //    BG_W stays >= 1 so the port below is legal SystemVerilog. But the
  //    number of ADDRESS BITS the field occupies is separate, and is zero
  //    when the generation has no bank-group field. Conflating "the width
  //    of the signal" with "the bits it consumes in the map" is what makes
  //    an absent field either illegal or silently wasteful.
  parameter int BG_MAP_W = (GEN == 3) ? 0 : BG_W,

  // Flat index width, for Chapter 5.2's table. >= 1 always, because
  // BA_W >= 1.
  parameter int FLAT_W = BG_MAP_W + BA_W
) (
  input  logic [ADDR_W-1:0]   sys_addr,

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

  // 0 when this generation has no bank-group field. bg_operand then reads
  // zero and MUST NOT be placed on any interface -- there is nowhere for
  // it to go, and Chapter 7.1's encoder would have no field for it.
  output logic                bg_field_present,

  // {bg, ba} for a grouped generation; ba alone otherwise. This is what
  // Chapter 5.2's ddr_bank_state_table indexes with -- that table takes a
  // FLAT bank index and knows nothing about groups.
  output logic [FLAT_W-1:0]   flat_bank_index,

  output logic                addr_above_device,

  output logic [ADDR_W-1:0]   bg_stride_bytes,
  output logic [ADDR_W-1:0]   ba_stride_bytes
);

  localparam int PLACE_BG_LOW  = 0;
  localparam int PLACE_BG_HIGH = 1;

  // ── Field positions. BG_MAP_W does the generation work: when it is
  //    zero, the bank field starts exactly where the group field would
  //    have, and the map is one field shorter with no special-casing.
  localparam int COL_LSB = OFFSET_W;

  localparam int BG_LSB  = (BG_PLACEMENT == PLACE_BG_LOW)
                           ? (COL_LSB + COLUMN_W)
                           : (COL_LSB + COLUMN_W + BA_W);

  localparam int BA_LSB  = (BG_PLACEMENT == PLACE_BG_LOW)
                           ? (COL_LSB + COLUMN_W + BG_MAP_W)
                           : (COL_LSB + COLUMN_W);

  localparam int ROW_LSB = COL_LSB + COLUMN_W + BG_MAP_W + BA_W;
  localparam int MAP_W   = ROW_LSB + ROW_W;

  // ── Elaboration legality.
  if ((GEN != 3) && (GEN != 4) && (GEN != 5)) begin : g_gen
    initial $fatal(1, "bg_bank_field_map: GEN must be 3, 4 or 5");
  end
  // The generation boundary as a structural check. A DDR3 map with more
  // than one group is not a policy mistake -- it describes a device that
  // does not exist.
  if ((GEN == 3) && (NUM_GROUPS != 1)) begin : g_gen3
    initial $fatal(1, "bg_bank_field_map: GEN 3 has no bank groups -- NUM_GROUPS must be 1");
  end
  if ((GEN != 3) && (NUM_GROUPS < 2)) begin : g_gen45
    initial $fatal(1, "bg_bank_field_map: GEN 4/5 with NUM_GROUPS < 2 has no group structure to map");
  end
  if ((BG_PLACEMENT != PLACE_BG_LOW) && (BG_PLACEMENT != PLACE_BG_HIGH)) begin : g_place
    initial $fatal(1, "bg_bank_field_map: BG_PLACEMENT must be 0 (BG_LOW) or 1 (BG_HIGH)");
  end
  if (OFFSET_W < 1) begin : g_ow
    initial $fatal(1, "bg_bank_field_map: OFFSET_W must be >= 1");
  end
  if (ROW_W < 1) begin : g_rw
    initial $fatal(1, "bg_bank_field_map: ROW_W must be >= 1");
  end
  if (MAP_W > ADDR_W) begin : g_fit
    initial $fatal(1, "bg_bank_field_map: fields need more bits than ADDR_W");
  end
  // A bit-field map is a bijection -- Chapter 8.2 Section 4.
  if (NUM_COLS != (1 << COLUMN_W)) begin : g_cpow
    initial $fatal(1, "bg_bank_field_map: NUM_COLS must be a power of two");
  end
  if (BANKS_PER_GROUP != (1 << BA_W)) begin : g_bapow
    initial $fatal(1, "bg_bank_field_map: BANKS_PER_GROUP must be a power of two");
  end
  if ((GEN != 3) && (NUM_GROUPS != (1 << BG_W))) begin : g_bgpow
    initial $fatal(1, "bg_bank_field_map: NUM_GROUPS must be a power of two");
  end

  // ── Extraction. Constant part-selects.
  assign col_operand = sys_addr[COL_LSB +: COLUMN_W];
  assign ba_operand  = sys_addr[BA_LSB  +: BA_W];
  assign row_operand = sys_addr[ROW_LSB +: ROW_W];

  // ── The field that may not exist. The generate is not decoration: with
  //    GEN == 3 the part-select below would read address bits that belong
  //    to the BANK field, so a runtime mask would not be equivalent.
  if (GEN == 3) begin : g_nobg
    // No group field in the map, and no group operand on any interface.
    assign bg_operand      = '0;
    assign bg_field_present = 1'b0;
  end else begin : g_bg
    assign bg_operand      = sys_addr[BG_LSB +: BG_W];
    assign bg_field_present = 1'b1;
  end

  // ── The flat index Chapter 5.2's table consumes. Concatenation order
  //    matters and is arbitrary but must be CONSISTENT: group above bank
  //    here, so banks of one group are adjacent in the flat space.
  if (GEN == 3) begin : g_flat3
    assign flat_bank_index = ba_operand;
  end else begin : g_flat45
    assign flat_bank_index = {bg_operand, ba_operand};
  end

  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

  // ── Chapter 8.3 Section 2's rule, per field. The group stride is
  //    reported as zero when there is no group field, because "changes
  //    every 1 byte" would be a lie and "changes every 0 bytes" reads as
  //    "never" -- which is the truth.
  if (GEN == 3) begin : g_bgs3
    assign bg_stride_bytes = '0;
  end else begin : g_bgs45
    assign bg_stride_bytes = ADDR_W'(1) << BG_LSB;
  end
  assign ba_stride_bytes = ADDR_W'(1) << BA_LSB;

endmodule

State and sequential behaviour

Neither. The generation and the placement are both elaboration-time facts.

Combinational behaviour

Three or four constant slices depending on generation, one concatenation, one OR reduction, two constant shifts. All of it wiring.

Bit-level derivation

DDR4, 4 groups × 4 banks, 16 columns, 64-byte column access, ROW_W = 16:

COLBGBAROWMAP_Wbg_strideba_stride
BG_LOW9:611:1013:1229:14301,0244,096
BG_HIGH9:613:1211:1029:14304,0961,024

And the same module at GEN = 3, NUM_GROUPS = 1, BANKS_PER_GROUP = 8:

COLBGBAROWMAP_Wbg_stride
DDR39:6absent12:1028:13290

BG_MAP_W is 0, so the bank field starts at bit 10 — where the group field would have been. No bits are wasted, no special case appears in the extraction, and bg_field_present tells every consumer not to look for a group operand. Note also that MAP_W is 29 rather than 30: eight banks and no groups is one bit less of bank index than sixteen banks in four groups, so the map genuinely describes a smaller device.

Cycle example

Address 0x00104800 under both DDR4 placements:

BG_LOWBG_HIGH
column00
bank group20
bank02
flat bank index{2,0} = 8{0,2} = 2
row0x00410x0041

The group and bank operands have swapped. Both name a real bank, and — this is the part worth noticing — the flat indices differ, so Chapter 5.2's state table will track them as different banks. They are. The address reaches a different physical bank under each placement, which is exactly as legitimate as Chapter 8.3's two policies reaching different banks.

How to simulate, and expected output

Elaborate both DDR4 placements and one DDR3 configuration, then drive a 1 KB-strided stream:

sys_addrA: BGA: BAB: BGB: BA
0x001040000000
0x001044001001
0x001048002002
0x00104C003003
0x001050000110

Read the columns in pairs. Placement A's group changes on every step; Placement B's bank does. At 0x00105000 each rolls over into the other field. That table is §3's claim as data, and §5 puts it on a waveform with the classification that follows.

For the DDR3 instance, check that bg_field_present is 0, bg_operand is 0 for every address, bg_stride_bytes is 0, and that flat_bank_index equals ba_operand.

Synthesis implication

Zero gates, as with every block in this module. The generation choice removes a slice rather than adding logic, so a DDR3 configuration is strictly smaller, not merely equal — the concatenation is narrower and one part-select disappears.

Parameter corner cases

GEN == 3 with NUM_GROUPS != 1 does not elaborate, which is the generation boundary enforced structurally. GEN == 4 with NUM_GROUPS == 1 also does not elaborate: a grouped generation with one group has no group structure to map, and silently accepting it would produce a map that claims to be DDR4-aware while exposing nothing. BANKS_PER_GROUP == 1 gives BA_W == 1 through the guard and elaborates — degenerate but sound. Non-power-of-two group or bank counts do not elaborate, for Chapter 8.2 §4's bijection reason; note the group check is skipped for GEN == 3, because with no group field there is no group index to make bijective. BG_PLACEMENT outside {0, 1} does not elaborate.

Reset behaviour

No state, no reset. The generation cannot be reset into, and that is the point of making it a parameter: a controller that could be reconfigured between generations at run time would have an address map whose shape changed, which a bit-field map cannot express.

Debugging clues

bg_field_present low on a DDR4 platform means the IP was elaborated for the wrong generation, and every group operand it produces is a zero that the encoder will happily place. A group operand that never changes across a workload means bg_stride_bytes is larger than the workload's footprint — read the output rather than inferring it. Two models agreeing on column and row but swapping group and bank is the signature of mismatched BG_PLACEMENT, and it is a one-line fix once recognised.

Limitations

Two placements. No hashing. No rank or channel (Chapter 8.5's). It expresses no opinion about which placement suits a workload, and it cannot tell you the timing consequence of a same-group pair — that requires Modules 13 and 14, and the block deliberately produces operands rather than verdicts.

5. The Same Stream, Two Placements

bg_bank_field_map — placement decides the relationship between neighbours

8 cycles
Eight cycles presenting one system address per cycle, stepping by one kilobyte on a DDR4 configuration of four groups of four banks. Under placement A with the group bits low, the bank-group operand walks zero, one, two, three while the bank operand stays at zero, so every consecutive pair of accesses is in different groups. Under placement B with the bank bits low, the bank-group operand stays at zero while the bank operand walks zero, one, two, three, so every consecutive pair is in the same group but different banks. The classification row shows different-group for placement A and same-group-different-bank for placement B, which is the verdict Chapter 5.3's classifier produces from these operands.A: 4 groups · B: 1 groupA: 4 groups · B: 1 groupthe fields roll overthe fields roll overstride = 1 KBstride = 1 KBA: groups rotateA: groups rotateB: one groupB: one groupCKsys_addr00104000001044000010480000104C0000105000001054000010580000105C00A: bg01230123A: ba00001111A: 5.3--DIFFDIFFDIFFDIFFDIFFDIFFDIFFB: bg00001111B: ba01230123B: 5.3--SAMESAMESAMEDIFFSAMESAMESAMEt0t1t2t3t4t5t6t7
Figure 1 — One stream, two placements. Consecutive accesses are strangers under A and neighbours under B.

The two 5.3 rows are Chapter 5.3's classifier applied to each placement's operandsDIFF for different-group, SAME for same-group-different-bank. They are not outputs of this block; they are what the existing classifier reports when fed these operands, which is the composition §4 was built for.

Placement A produces DIFF_GROUP for every consecutive pair. Sequential traffic never revisits a group two accesses in a row.

Placement B produces SAME_GROUP_DIFF_BANK for almost every pair — and note cycle 4, where the bank field rolls over and the group increments, giving one DIFF in every four. That one transition per four accesses is the only group change Placement B's stream ever makes.

Neither is wrong. Both decompose the same addresses correctly, and a DDR4 device accepts either command stream. What differs is the resource relationship between accesses that arrive next to each other in time — and that relationship is what the timing rules in Modules 13 and 14 act on. This chapter produces the relationship and stops.

Representative educational cycles. One address per cycle implies no request rate, and no interval here corresponds to any timing parameter.

6. Composing With the Flat Bank Index

There is a layering detail here that causes real confusion, and it is worth making explicit because two existing blocks in this curriculum sit on either side of it.

Chapter 5.2's state table indexes banks flatly. It takes a bank index and tracks an open row per bank; it has no notion of groups, and it does not need one, because groups do not affect row state. Every bank owns its row regardless of which group it is in.

Chapter 5.3's classifier needs the split. It takes group and bank separately, because the distinction it reports is precisely the one the split encodes.

So the same access is addressed two ways inside one controller, and this block produces both:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  sys_addr

      ├──► bg_operand, ba_operand ──► 5.3 classifier ──► resource relationship

      └──► flat_bank_index        ──► 5.2 state table ──► open row per bank

The concatenation order is arbitrary but must be consistent. This block puts the group above the bank, so banks of one group are adjacent in the flat space. That choice is invisible to the state table — it only needs a unique index per bank — but if two components concatenate differently, they will disagree about which bank is which while both being internally consistent. That is Chapter 8.3 §9's quiet bug in a new place, and it is worth a single-sourced function rather than two concatenations written a month apart.

7. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1 -- the generation boundary as a runtime contract. With no bank-group
// field, the group operand must be zero and must be flagged absent. This
// catches an encoder being handed a group operand it has nowhere to place.
property p_absent_group_reads_zero;
  @(posedge clk)
    !bg_field_present |-> (bg_operand == '0) && (bg_stride_bytes == '0);
endproperty
assert property (p_absent_group_reads_zero);

// P2 -- the flat index really is the two fields, in the declared order.
// Section 6's consistency requirement stated as a property, so a
// concatenation written differently elsewhere fails here rather than
// silently renaming banks.
property p_flat_index_is_the_concatenation;
  @(posedge clk)
    bg_field_present |-> (flat_bank_index == {bg_operand, ba_operand});
endproperty
assert property (p_flat_index_is_the_concatenation);

// P3 -- the two bank-index fields are disjoint, probed through the column
// as in Chapter 8.3. A change confined to the column region must leave
// both the group and the bank alone; a placement expression that
// overlapped them would fail here.
property p_group_and_bank_are_disjoint;
  @(posedge clk)
    (((sys_addr ^ $past(sys_addr)) >> (OFFSET_W + COLUMN_W)) == '0)
      |-> (bg_operand == $past(bg_operand))
          && (ba_operand == $past(ba_operand));
endproperty
assert property (p_group_and_bank_are_disjoint);

// P4 -- the flat index is INVERTIBLE, in both generations. A consumer going
// the other way -- splitting a flat index back into group and bank, which a
// monitor must do -- depends on this, and it fails if the split is written
// with the wrong field width. Note the second conjunct covers the GEN == 3
// path, where the high part must be empty rather than merely ignored.
//
// NOT written as a range check: the elaboration guards force
// NUM_GROUPS * BANKS_PER_GROUP == 2 ** FLAT_W, so "the index is in range"
// is true by construction and an assertion for it would be a tautology.
property p_flat_index_is_invertible;
  @(posedge clk)
    ((flat_bank_index >> BA_W) == (bg_field_present ? bg_operand : '0))
      && (flat_bank_index[BA_W-1:0] == ba_operand);
endproperty
assert property (p_flat_index_is_invertible);

What these prove. P1 makes the absent field safe to consume — the most valuable property in a multi-generation IP, because the failure it catches produces a plausible-looking zero. P2 pins §6's consistency requirement where it can be checked. P3 proves disjointness. P4 proves the flat index can be split back into its two fields, which is what a monitor needs and what a wrong-width split breaks; it is deliberately not a range check, because the elaboration guards force NUM_GROUPS × BANKS_PER_GROUP to equal 2^FLAT_W, making any range assertion a tautology.

What they do not prove. Nothing here says the placement is right for a workload (Module 18), and nothing says anything about timing — a property that consecutive accesses are in different groups says nothing about how soon the second may be issued, which is Modules 13 and 14' entirely. They also do not prove the generation is right: GEN is a parameter, so P1 proves the block is self-consistent with whatever generation it was told, not that the device on the board matches. That mismatch is caught at integration, not by an assertion.

8. DV — Verifying a Field That Is Absent in One Generation

A generation-dependent field creates a coverage problem that is easy to miss and expensive to discover late.

A regression that elaborates only DDR4 has never exercised the DDR3 shape. Both configurations are the same RTL, but they are different designsChapter 1.6 §4's point about parameterised modules, sharpened, because here the parameter removes a field rather than resizing one. The g_nobg branch and the g_flat3 branch are untouched code in a DDR4-only regression.

The absent field is dangerous precisely because it reads as valid. bg_operand is zero, which is a legal group index. A consumer that ignores bg_field_present will place that zero on an interface, and for a DDR3 device there is no field to place it in — so whether the mistake is visible depends entirely on what Chapter 7.1's encoder does with an operand it has no home for. A zero is the worst possible sentinel, which is why the presence flag exists and why P1 asserts on it.

What to cover, concretely:

Every generation the IP claims to support, elaborated and run. Not simulated once at the end — in the nightly regression, with the same tests.

Both placements, for each grouped generation. The placement changes every field boundary above the column, so a single-placement regression has verified one map out of two.

The roll-over addresses, directed. §5's cycle 4 — where the low bank-index field saturates and the high one increments — is the only address in the stream that exercises the boundary between the two fields, and random addresses hit it with probability inversely proportional to the field width.

The classifier composition, not just the operands. The operands are easy to check and not very interesting. What matters is that Chapter 5.3's classifier, fed these operands, reports the relationship the placement was chosen to produce. That is a test of the map's purpose rather than its arithmetic, and it is the test that would catch a placement silently reversed.

9. Debugging — Wrong Group, Right Bank

Symptom. Data corruption on a DDR4 or DDR5 platform. The protocol trace is clean. Row and column operands agree with the reference model. The group and bank operands are each individually plausible — and swapped.

Or a second, quieter symptom: no corruption at all, but measured throughput well below expectation, with a command stream whose consecutive accesses are all in one group when the design intent was to rotate through groups.

Candidate mechanisms.

  1. BG_PLACEMENT differs between the DUT and the reference model — the group and bank fields are swapped.
  2. GEN is wrong: the IP was elaborated for DDR3 against a DDR4 device, so bg_operand is a constant zero and bg_field_present is low.
  3. NUM_GROUPS is wrong for the device width — §2's table shows x16 parts have half the groups of x8 parts in both generations — so the group field is one bit too wide and steals a bit from its neighbour.
  4. The flat index is concatenated in the opposite order somewhere, so Chapter 5.2's state table is tracking a different bank than the one being addressed — §6's hazard.
  5. The map is entirely correct and the intended placement was simply never the one configured — a specification problem, not a bug.

Evidence to collect. Both models' full (group, bank, row, column) decomposition per failing address; bg_field_present and both stride outputs from the DUT's instance; and the device's actual width and generation from the module's own identification, not from the project's assumptions.

Discriminator.

  • Group and bank swapped, row and column agreeing: mechanism 1, and it is the cleanest fingerprint in the chapter. Compare BG_PLACEMENT and stop.
  • bg_operand constant zero and bg_field_present low: mechanism 2. No further evidence needed; the block is telling you directly, which is why the flag is an output.
  • Failures only above a power-of-two address threshold: mechanism 3. An over-wide group field pushes ROW_LSB up, so the top of the row range becomes unreachable and the failure has an address floor rather than being uniform.
  • Correct operands but corruption, with state-tracking anomalies: mechanism 4. Check flat_bank_index against an independently computed {bg, ba}; P2 exists to make this automatic.
  • No corruption, throughput below expectation, all consecutive pairs in one group: mechanism 5, and it is not a bug at all — it is Chapter 8.3 §11's lesson again. Read bg_stride_bytes: if it is larger than the workload's stride, the map is doing exactly what it was configured to do. The conversation that follows belongs to Module 18.

Responsible layer. Mechanisms 1, 3, 4 and 5 are layer B. Mechanism 2 is a configuration or integration error that presents as layer B and is diagnosed in one signal. None of them is a DDR protocol defect, and the clean trace is the evidence for that rather than a mystery.

Fix. Single-source the map and the concatenation, enable P1 and P2, and add the DDR3 configuration to the regression so the absent-field path stops being untested code.

10. Common Misconceptions

"Bank-group bits exist identically in every DDR generation."

Why it is tempting: bank groups are prominent in DDR4 and DDR5 material, and a learner who starts there meets them as a basic feature.

Concrete failure: a map targeting DDR3 allocates group bits. They come out of the row field, so the row is one bit too narrow, the top half of the device becomes unreachable, and the group operand goes to an interface with no field for it.

Correct model: verified — bank groups are DDR4 and DDR5 only. §4's BG_MAP_W makes the field occupy zero address bits when it does not exist.

Prevention: make the generation a structural parameter with an elaboration guard, and never let the field's presence be a runtime flag.

"A bank group is just a bigger bank."

Why it is tempting: it is a grouping of banks, and groupings usually behave like larger versions of what they group.

Concrete failure: an engineer assumes accesses to one group serialise like accesses to one bank, and cannot explain why two banks in a group can hold two open rows simultaneously.

Correct model: a group shares a column data path, not row state. Chapter 5.3 covered this: banks in a group are independent in state and contended in data path.

Prevention: §1's table. Ask what the resource shares, not what it contains.

"The group and bank fields are interchangeable since together they name a bank."

Why it is tempting: they do together name a bank, and swapping them still produces a valid bank.

Concrete failure: two components use opposite placements. Every operand is plausible, every command legal, and consecutive accesses have exactly the opposite resource relationship to the one the design intended — plus, if the components exchange addresses, data corruption.

Correct model: they name the same set of banks in a different order, and the order is what determines whether neighbouring accesses contend.

Prevention: §5. Treat the placement as a design parameter with a stated intent, and test the composition with Chapter 5.3's classifier rather than checking operands in isolation.

"A zero bank-group operand is safe when there are no bank groups."

Why it is tempting: zero is the natural default and a legal group index everywhere it exists.

Concrete failure: the DDR3 configuration produces bg_operand = 0, a consumer ignores bg_field_present, and the zero is placed on an interface that has no group field — with behaviour that depends entirely on the encoder.

Correct model: absence is not a value. The flag is the information; the operand is meaningless when the flag is low.

Prevention: P1, and treating bg_field_present as a required input to every consumer rather than an optional one.

"Placing the group bits low is always better because consecutive accesses then avoid contention."

Why it is tempting: §3 makes the mechanism clear and the conclusion feels immediate.

Concrete failure: the same over-generalisation Chapter 8.3 warned about. Fine group interleaving means every stream touches every group, so independent streams lose the isolation that a coarser placement would have given them.

Correct model: the placement chooses which accesses are neighbours. Whether you want sequential accesses to be strangers depends on whether your workload is one stream or many.

Prevention: evaluate against the workload mix. Module 18.

"The address map has to know the bank-group timing rules."

Why it is tempting: the placement is chosen because of those rules, so it feels like the map should encode them.

Concrete failure: timing constants leak into an address-mapping block, which then has to be re-verified whenever a speed grade changes and produces verdicts it has no business producing.

Correct model: the map produces a resource relationship. Whether that relationship permits an immediate issue is Modules 13 and 14', consumed by a scheduler in Module 17.

Prevention: keep the block producing operands. §4's "does not model" list is the boundary.

11. Interview Reasoning

"Do bank groups exist in every DDR generation?"

No — they are a DDR4 and DDR5 feature. DDR3 has a flat bank index. That matters structurally rather than cosmetically: a multi-generation controller's address map changes shape between them, so the group field's presence has to be a compile-time property. Making it a runtime flag would imply a map whose field boundaries move at run time, which a bit-field decomposition cannot express.

"How would you place the group and bank bits, and why?"

The two bits of the answer are the mechanism and the admission that it depends. Group bits below bank bits makes consecutive address regions land in different groups, so sequential traffic's neighbouring accesses do not share a column data path. Bank bits below group bits does the opposite: sequential traffic walks the banks of one group before moving on. For a single sequential stream the first is usually what you want; for several independent streams that you would rather keep apart, the second gives isolation. The decision is made against a workload, and the mechanism is that a field at bit p changes every 2^p bytes.

"Why does the state table not need to know about groups?"

Because groups do not affect row state. Every bank owns its own row decoder and sense amplifiers regardless of grouping, so a table tracking one open row per bank needs only a unique index per bank — the flat index. Groups matter for the shared column data path, which is a different resource and a different consumer: the classifier, not the state table. One access is therefore addressed two ways inside one controller, which is a layering fact worth being able to state.

"You see a DDR4 platform where the bank-group operand is always zero. What is your first hypothesis?"

That the IP was elaborated for DDR3 — group field absent, operand hard-wired to zero. It is a one-signal check if the block exposes a presence flag, and this is exactly why it should: a constant zero is a legal group index, so without the flag the symptom is indistinguishable from a workload that happens never to leave group 0. Second hypothesis would be that the group stride exceeds the workload's footprint, which is not a bug.

"Two components agree on row and column but disagree on group and bank. What happened?"

They were compiled with opposite group/bank placements. The fingerprint is precise: the column is unaffected by the placement, the row is unaffected because both placements put it above the whole bank index, and the two middle fields swap. One parameter, one line, and a class of corruption that presents as a clean protocol trace.

12. Engineering Exercise

DDR4 config: OFFSET_W = 6, NUM_COLS = 16, NUM_GROUPS = 4, BANKS_PER_GROUP = 4, ROW_W = 16, ADDR_W = 32.

1. Compute BG_LSB, BA_LSB, ROW_LSB and MAP_W for both placements.

2. Decompose 0x00205400 under both placements, and give the flat bank index for each.

3. Under BG_LOW, what stride makes a stream use only one group? Under BG_HIGH?

4. Re-parameterise for a DDR4 x16 device using §2's verified organisation. What changes, and what is MAP_W?

5. Re-parameterise for DDR3 with 8 banks. Give every field's position and explain why MAP_W is not simply one less than the DDR4 value.

6. A colleague suggests making GEN a runtime input so one bitstream supports both generations. Give the structural objection.

13. Summary

A bank-group address selects which group the target bank is in — and a group shares a column data path, not row state. Banks in one group remain independent in what they hold and contended in how the data moves.

Verified: bank groups are DDR4 and DDR5 only. DDR3 has a flat bank index. This makes the group the first field in the module that may not exist, and absence has to be structural: BG_MAP_W is the number of address bits the field occupies, which is zero when the field is absent, and it is a different quantity from the signal width, which stays at one so the port remains legal.

Absence is not a value. An absent group operand reads zero, which is a perfectly legal group index — so the presence flag carries the information and a consumer that ignores it will place a meaningless zero on an interface.

The split decides who your neighbours are. Group bits below bank bits makes consecutive address regions land in different groups; the reverse walks the banks of one group first. Same fields, same total width, same zero gates — and the opposite resource relationship between accesses that arrive next to each other.

One access, two addressings. Chapter 5.2's state table wants a flat index because groups do not affect row state; Chapter 5.3's classifier wants the split because the split is the distinction it reports. Both come from the same map, and the concatenation that joins them must be single-sourced.

And the fingerprint of a placement mismatch is exact: column agrees, row agrees, group and bank swap. One parameter, one line, and a clean protocol trace hiding it.

14. What Comes Next

Every field so far — column, row, bank, bank group — becomes an operand on the command interface. Chapter 7.1's encoder places each of them into a generation-specific position, and a device decodes them.

Chapter 8.5 — Rank Selection breaks that pattern, and it is the most important distinction left in the module.

A rank is selected too — Chapter 5.5 established what a rank is and why more than one shares a bus. But rank selection does not arrive as an operand on the command/address interface. It arrives as qualification: Chapter 6.2 showed that a command is addressed to a device by which chip-select is asserted, not by an address field. The same is true, even more strongly, of channel selection — which may be resolved before a DDR interface is involved at all.

So the next chapter asks whether rank is an address field in any meaningful sense, and the answer reshapes the layer model from Chapter 8.1 §2.


Return to Bank Groups for what a group is and the classifier that consumes these operands, Banks for the flat-indexed state table, Bank Address for the placement reasoning this chapter extends, and Activate for the encoder that carries these operands onto the interface.

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.