DDR · Module 8
Rank Selection
Rank is extracted from the address like every other field, then does something no other field does: it leaves the address path and becomes a chip-select. Two commands can be bit-identical on the CA bus and reach different devices.
Every field so far has ended up in the same place. Column, row, bank and bank group are extracted from the system address, handed to Chapter 7.1's encoder, and placed onto the command/address interface as operands. A device decodes them.
Rank breaks that pattern, and it breaks it in a way that changes the layer model from Chapter 8.1 §2.
A rank is selected — Chapter 5.5 established what a rank is and why several of them share one bus. And the rank index is extracted from the system address, exactly like a bank index. But it never becomes an operand. Chapter 6.2 already showed where it goes instead: a command is addressed to a device by which chip-select is asserted, not by an address field.
So the chapter's question is the sharpest one in the module:
Is rank an address field at all — and if a field can leave the address path, what else can?
The answer produces a three-way taxonomy of address bits that is worth more than any single field in this module.
1. What Rank Selection Selects
A rank is a set of DRAM devices that respond together to one command. Chapter 5.5 developed this: the devices of a rank share the command/address bus and each contributes part of the data word, so a rank is the unit that answers.
Two ranks share the same physical bus and are distinguished by which one is told to listen.
That last clause is the whole chapter. Compare what the module's fields select:
| Field | Selects | Reaches the device as |
|---|---|---|
| column | part of the held row | an operand on the CA bus |
| row | which row is sensed | an operand on the CA bus |
| bank / bank group | which resource | an operand on the CA bus |
| rank | which devices respond at all | a chip-select assertion |
A rank is not a location inside a device. It is a choice of which device the command is for. And a command cannot carry "which device this is for" as an operand, because every device on the bus would have to decode the operand to discover the command was not for it — which means every device is decoding, every device is switching, and the mechanism that was supposed to save power and avoid contention has done neither.
So the selection has to happen before decode, which means it has to be a separate signal. That is what Chapter 6.2 established: a device that does not see its chip-select asserted does not have a command at all. Chapter 7.1 §3 built this into the command model as qualification, the step that turns an encoding into a command for a particular target.
This chapter's contribution is to notice that qualification has an address field behind it.
2. Three Destinations for Address Bits
Here is the taxonomy, and it replaces the intuition that "the address map produces operands."
Operand fields — column, row, bank, bank group. Encoded into CA positions by Chapter 7.1's encoder and decoded by the device. These are what people mean by "address bits," and they are a subset.
Qualification fields — rank. Extracted from the address, then converted into which chip-select is asserted. A rank index is never placed in an address position and is never decoded from one. Chapter 6.2's cs_rank_decoder performs the conversion.
Pre-interface fields — channel, and in DDR5 sub-channel. These select which interface the request is issued on, which means they are resolved before there is a command at all. Chapter 5.4 established that channels are independent, with their own command and data buses, so a channel bit does not travel on a DDR interface — it chooses between DDR interfaces. In many systems that choice is made in the interconnect, before a memory controller is even reached.
3. Rank Arrives as Qualification, Not as an Operand
The mechanical consequence is worth stating in its strongest form, because it is testable and it surprises people:
Two commands can be bit-identical on the command/address bus and reach different devices.
Take two system addresses that differ only in the rank field. Every other field is identical, so the column, row, bank and bank-group operands are identical. The encoder produces the same values in the same positions. The CA bus carries the same bits. The only difference is which chip-select goes low, and §5 puts that on a waveform.
Three things follow, and each matters to a different person:
For a controller designer: the rank is not part of the command it builds. It is part of the addressing of the command, resolved on a different set of pins, and the encoder never sees it.
For a verification engineer: a monitor watching only the CA bus cannot distinguish accesses to different ranks. It will merge them, attribute one rank's activates to another, and — because Chapter 8.2 established that the open row comes from state — produce wrong rows for every subsequent column command. The bank-state model must be per rank, and the monitor must observe CS# to know which.
For a system architect: rank switching does not cost command bandwidth, because no command bits are spent on it. It costs something else entirely — the bus is shared, and handing it from one rank's devices to another's has consequences on the electrical side. Module 14 and Module 22 own those costs, and this chapter deliberately does not quantify them. What this chapter owns is that the address map decides how often the switch happens.
4. RTL — Extracting a Field That Leaves the Address Path
The engineering problem
Partition a system address into all three destinations at once, producing CA operands, a rank index for the chip-select decoder, and a channel index for whatever selects the interface — while making the different destinations structurally visible rather than emitting one undifferentiated bundle of fields.
Why hardware needs it
A controller has to route these bits to three different places. Treating them uniformly is how a rank index ends up in an address position, or how a channel bit is handed to a command encoder that has no field for it.
Classification
SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
What it models
Extraction of operand fields, a rank index and a channel index from one system address, under a selectable rank placement, with the destination of each field declared and the bit budget of each destination reported.
What it does NOT model
CS# generation (Chapter 6.2's cs_rank_decoder). Channel extraction as a policy study with imbalance measurement — Chapter 5.4's channel_selector already does that, with a configurable select position, and this block takes the simple high-order case and points at 5.4 for the interesting one. Per-rank bank state (Chapter 5.2, instantiated per rank). Rank-to-rank timing (Module 14). Loading, termination and ODT (Module 22). Interconnect routing — the thing that actually consumes channel_index is usually not a DDR controller at all.
Interface and parameter contract
// ─────────────────────────────────────────────────────────────────────────
// rank_field_select
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// AN EXAMPLE CONTROLLER ADDRESS-MAPPING POLICY, NOT A JEDEC-MANDATED
// UNIVERSAL DDR ADDRESS MAP.
//
// MODELS: partition of one system address into THREE DESTINATIONS --
// operand fields for the CA bus, a rank index for target qualification,
// and a channel index resolved before any DDR interface exists.
//
// DOES NOT MODEL: CS# generation -- Chapter 6.2's cs_rank_decoder consumes
// rank_index and owns the active-low convention and deselect, and is NOT
// duplicated here. Also not modelled: channel policy and imbalance
// (Chapter 5.4's channel_selector owns that, with a configurable select
// position), per-rank bank state (Chapter 5.2, instantiated per rank),
// rank-to-rank timing (Module 14), loading and ODT (Module 22), or
// interconnect routing -- whatever consumes channel_index is usually not a
// DDR controller.
//
// GENERATION: GENERATION-NEUTRAL CONTROLLER MODEL. Chip-select-based
// target qualification exists in every DDR generation.
// ─────────────────────────────────────────────────────────────────────────
module rank_field_select #(
parameter int ADDR_W = 32,
parameter int OFFSET_W = 6,
parameter int NUM_COLS = 16,
// Flat bank index here -- Chapter 8.4 owns the group/bank split, and
// repeating it would obscure this chapter's subject.
parameter int NUM_BANKS = 16,
parameter int ROW_W = 16,
parameter int NUM_RANKS = 2,
parameter int NUM_CHANNELS = 2,
// 0 = RANK_LOW : rank below row -- traffic alternates ranks sooner
// 1 = RANK_HIGH : rank above row -- each rank owns a contiguous region
parameter int RANK_PLACEMENT = 1,
parameter int COLUMN_W = (NUM_COLS <= 1) ? 1 : $clog2(NUM_COLS),
parameter int BANK_W = (NUM_BANKS <= 1) ? 1 : $clog2(NUM_BANKS),
parameter int RK_W = (NUM_RANKS <= 1) ? 1 : $clog2(NUM_RANKS),
parameter int CH_W = (NUM_CHANNELS <= 1) ? 1 : $clog2(NUM_CHANNELS),
// Address bits each field OCCUPIES, which is zero for a degenerate count
// even though the signal width above stays >= 1 -- Chapter 8.4's
// distinction, reused for two more fields.
parameter int RK_MAP_W = (NUM_RANKS <= 1) ? 0 : RK_W,
parameter int CH_MAP_W = (NUM_CHANNELS <= 1) ? 0 : CH_W
) (
input logic [ADDR_W-1:0] sys_addr,
// ── DESTINATION 1: OPERANDS. These reach the device on the CA bus,
// encoded by Chapter 7.1's encoder.
output logic [COLUMN_W-1:0] col_operand,
output logic [BANK_W-1:0] bank_operand,
output logic [ROW_W-1:0] row_operand,
// ── DESTINATION 2: QUALIFICATION. An INDEX, not a chip-select, and not
// an operand. It is never placed in an address position. Chapter
// 6.2's cs_rank_decoder converts it.
output logic [RK_W-1:0] rank_index,
output logic rank_field_present,
// ── DESTINATION 3: PRE-INTERFACE. Selects WHICH interface is used, so
// it is resolved before a command exists and never appears on any
// DDR pin. Often consumed in the interconnect, upstream of here.
output logic [CH_W-1:0] channel_index,
output logic channel_field_present,
output logic addr_above_device,
output logic [ADDR_W-1:0] rank_stride_bytes,
output logic [ADDR_W-1:0] channel_stride_bytes,
// ── The taxonomy as a bit budget. Elaboration-time constants: how many
// address bits go to each destination. Section 2 as three numbers.
output logic [7:0] operand_bits,
output logic [7:0] qualify_bits,
output logic [7:0] preiface_bits
);
localparam int PLACE_RANK_LOW = 0;
localparam int PLACE_RANK_HIGH = 1;
// ── Field positions. Channel sits highest throughout: Chapter 5.4 owns
// the study of moving it, and duplicating that here would add a
// parameter without adding a lesson.
localparam int COL_LSB = OFFSET_W;
localparam int BANK_LSB = COL_LSB + COLUMN_W;
localparam int RANK_LSB = (RANK_PLACEMENT == PLACE_RANK_LOW)
? (BANK_LSB + BANK_W)
: (BANK_LSB + BANK_W + ROW_W);
localparam int ROW_LSB = (RANK_PLACEMENT == PLACE_RANK_LOW)
? (BANK_LSB + BANK_W + RK_MAP_W)
: (BANK_LSB + BANK_W);
localparam int CH_LSB = BANK_LSB + BANK_W + RK_MAP_W + ROW_W;
localparam int MAP_W = CH_LSB + CH_MAP_W;
// The rank field's FOOTPRINT as a mask, for the Section 7 property. Zero
// when the field is absent, which is what makes that property degrade to
// unsatisfiable instead of needing a special case.
localparam logic [ADDR_W-1:0] RANK_MASK =
(RK_MAP_W <= 0) ? '0 : (((ADDR_W'(1) << RK_MAP_W) - 1) << RANK_LSB);
// ── Elaboration legality.
if ((RANK_PLACEMENT != PLACE_RANK_LOW) && (RANK_PLACEMENT != PLACE_RANK_HIGH)) begin : g_place
initial $fatal(1, "rank_field_select: RANK_PLACEMENT must be 0 (RANK_LOW) or 1 (RANK_HIGH)");
end
if (OFFSET_W < 1) begin : g_ow
initial $fatal(1, "rank_field_select: OFFSET_W must be >= 1");
end
if (ROW_W < 1) begin : g_rw
initial $fatal(1, "rank_field_select: ROW_W must be >= 1");
end
if (MAP_W > ADDR_W) begin : g_fit
initial $fatal(1, "rank_field_select: fields need more bits than ADDR_W");
end
// The bit-budget outputs below are 8 bits wide, so a map wider than 255
// bits would truncate them silently. Absurd in practice, fatal here --
// a reported number that is wrong is worse than one that is absent.
if (MAP_W > 255) begin : g_budget
initial $fatal(1, "rank_field_select: MAP_W > 255 would truncate the bit-budget outputs");
end
if (NUM_COLS != (1 << COLUMN_W)) begin : g_cpow
initial $fatal(1, "rank_field_select: NUM_COLS must be a power of two");
end
if (NUM_BANKS != (1 << BANK_W)) begin : g_bpow
initial $fatal(1, "rank_field_select: NUM_BANKS must be a power of two");
end
if ((NUM_RANKS > 1) && (NUM_RANKS != (1 << RK_W))) begin : g_rkpow
initial $fatal(1, "rank_field_select: NUM_RANKS must be a power of two");
end
if ((NUM_CHANNELS > 1) && (NUM_CHANNELS != (1 << CH_W))) begin : g_chpow
initial $fatal(1, "rank_field_select: NUM_CHANNELS must be a power of two");
end
// ── DESTINATION 1. Operands.
assign col_operand = sys_addr[COL_LSB +: COLUMN_W];
assign bank_operand = sys_addr[BANK_LSB +: BANK_W];
assign row_operand = sys_addr[ROW_LSB +: ROW_W];
// ── DESTINATION 2. Qualification index. Present only when there is
// more than one rank to choose between; a single-rank system still
// asserts a chip-select, but no ADDRESS BIT decides which.
if (NUM_RANKS <= 1) begin : g_norank
assign rank_index = '0;
assign rank_field_present = 1'b0;
assign rank_stride_bytes = '0;
end else begin : g_rank
assign rank_index = sys_addr[RANK_LSB +: RK_W];
assign rank_field_present = 1'b1;
assign rank_stride_bytes = ADDR_W'(1) << RANK_LSB;
end
// ── DESTINATION 3. Pre-interface selection.
if (NUM_CHANNELS <= 1) begin : g_nochan
assign channel_index = '0;
assign channel_field_present = 1'b0;
assign channel_stride_bytes = '0;
end else begin : g_chan
assign channel_index = sys_addr[CH_LSB +: CH_W];
assign channel_field_present = 1'b1;
assign channel_stride_bytes = ADDR_W'(1) << CH_LSB;
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
// ── The bit budget. NOTE THAT OFFSET_W IS IN NONE OF THESE: Chapter 8.2
// Section 3 established that those bits reach no DDR mechanism at
// all, so they have a fourth destination -- nowhere.
assign operand_bits = 8'(COLUMN_W + BANK_W + ROW_W);
assign qualify_bits = 8'(RK_MAP_W);
assign preiface_bits = 8'(CH_MAP_W);
endmoduleState and sequential behaviour
Neither. Every destination is a function of the address and the parameters.
Combinational behaviour
Five constant slices, one OR reduction, two constant shifts, three constant sums. Wiring and literals.
Bit-level derivation
At the defaults — 16 columns, 16 banks, 65,536 rows, 2 ranks, 2 channels, 64-byte column access:
| COL | BANK | ROW | RANK | CH | MAP_W | |
|---|---|---|---|---|---|---|
| RANK_HIGH | 9:6 | 13:10 | 29:14 | 30 | 31 | 32 |
| RANK_LOW | 9:6 | 13:10 | 30:15 | 14 | 31 | 32 |
MAP_W is exactly 32, which is worth checking by capacity: 64 B × 16 columns = 1 KB per row; × 65,536 rows = 64 MB per bank; × 16 banks = 1 GB per rank; × 2 ranks = 2 GB per channel; × 2 channels = 4 GB = 2^32. The map consumes every address bit, so addr_above_device is hard-wired low through the g_nohi branch — the configuration that exercises that branch, which the other chapters' defaults do not.
And the strides:
rank_stride_bytes | channel_stride_bytes | |
|---|---|---|
| RANK_HIGH | 2^30 = 1 GB | 2^31 = 2 GB |
| RANK_LOW | 2^14 = 16 KB | 2^31 = 2 GB |
Bit budget, either placement: operand_bits = 4 + 4 + 16 = 24, qualify_bits = 1, preiface_bits = 1. Plus 6 offset bits that go nowhere. 24 of 32 address bits become operands; 2 do not; 6 reach no DDR mechanism at all. That is §2 as arithmetic, and it is a more honest picture of "the DDR address" than any diagram of address pins.
Cycle example — the one that matters
Two addresses differing only in bit 30, under RANK_HIGH:
0x00104000 | 0x40104000 | |
|---|---|---|
col_operand | 0 | 0 |
bank_operand | 0 | 0 |
row_operand | 0x0041 | 0x0041 |
channel_index | 0 | 0 |
rank_index | 0 | 1 |
Every operand is identical. The encoder will produce the same CA values, in the same positions, for both. The commands are indistinguishable on the command/address bus — and they reach different physical devices.
How to simulate, and expected output
sys_addr | placement | rank_index | row_operand | note |
|---|---|---|---|---|
0x00104000 | HIGH | 0 | 0x0041 | baseline |
0x40104000 | HIGH | 1 | 0x0041 | identical operands |
0x00100000 | LOW | 0 | 0x0020 | same row as below |
0x00104000 | LOW | 1 | 0x0020 | 16 KB apart, rank flips |
0x80104000 | either | 0 | 0x0041 | channel 1 |
Rows 3 and 4 are the pair to study. Under RANK_LOW the rank field sits below the row, so ROW_LSB moves from 14 to 15 and the same address yields a different row operand — 0x00104000 >> 15 = 0x20. The placement does not only move the rank; it moves every field above it, which is the recurring lesson from Chapters 8.3 and 8.4. And because the rank bit is now bit 14, these two addresses differ by 16 KB, share a row, and land on different ranks.
Also elaborate NUM_RANKS = 1 and check that rank_field_present is low, rank_stride_bytes is zero, and MAP_W is one bit smaller.
Synthesis implication
Zero gates. And a structural observation that is easy to miss: because the rank index does not enter the encoder, the CA bus width is independent of the rank count. Doubling the ranks costs a chip-select pin and nothing on the address path — which is precisely why qualification is the right mechanism for this selection, and why Chapter 6.2 noted that chip-select count scales with rank count.
Parameter corner cases
NUM_RANKS == 1 gives RK_MAP_W == 0, so the rank field occupies no address bits, rank_field_present is low, and the map is one bit narrower. A single-rank system still asserts a chip-select — it simply does not use an address bit to decide which, and conflating "no rank field" with "no chip-select" is a mistake this flag exists to prevent. NUM_CHANNELS == 1 behaves the same way. Non-power-of-two rank or channel counts do not elaborate; note the guards are skipped for counts of 1, since a field that occupies no bits needs no bijection. MAP_W > ADDR_W does not elaborate, and at the defaults MAP_W == ADDR_W exactly, so both g_hi and g_nohi are reachable across the parameter space and both need a test.
Reset behaviour
No state, no reset. The reset-adjacent hazard is per-rank state: Chapter 5.2's table must be instantiated per rank, because each rank's banks hold their own rows. A single shared table indexed only by bank will attribute one rank's open row to another, and every column command to the other rank then resolves against the wrong row — Chapter 8.2 §5's failure, caused by a missing dimension rather than a missing activate.
Debugging clues
rank_field_present low on a multi-rank platform means the IP was elaborated for one rank, and every access is going to rank 0 while half the memory is unreachable. Two models agreeing on every operand but disagreeing about which device responded means they disagree about the rank field, and no amount of CA-bus evidence will settle it — CS# is the only witness. A workload touching only one rank when you expected alternation means rank_stride_bytes exceeds its footprint.
Limitations
One channel placement (Chapter 5.4 owns the study). Two rank placements. Flat bank index. No CS# generation, no per-rank state, no hashing, and no opinion about the cost of switching rank — which is real, is the main reason placement matters here, and belongs to Modules 14 and 22.
5. Identical Commands, Different Devices
rank_field_select — qualification is not an operand
8 cyclesRead the CA: bank/row row at cycles 0 and 2. 0/0041 at both. The command, the bank operand and the row operand are identical. Nothing on the command/address bus separates them.
Now read cs_n[1:0]. At cycle 0, bit 0 is low — rank 0 has a command. At cycle 2, bit 1 is low — rank 1 has a command. Chapter 6.2 established the convention and that all-ones is deselect, which is what cycles 1, 3, 6 and 7 show.
Cycles 4 and 5 repeat it for reads, with identical column and bank operands. So the pattern is not an artefact of activate — it holds for every command, because qualification is orthogonal to the command's content.
Three conclusions, which are the chapter:
cs_n is not driven by this block. It comes from Chapter 6.2's cs_rank_decoder, fed the rank_index shown above. The waveform shows the composition, not one module.
A CA-only monitor is blind here. It would see ACT 0/0041 twice and record a duplicate activate to one bank — then attribute both ranks' subsequent reads to a single open row. The rank must be sampled from CS# and must index the state model, and the model must be per rank.
And the address is genuinely distributed across two different kinds of wire. Chapter 8.2 showed it distributed across time; this chapter shows it distributed across signal groups. Reconstruction needs both.
Representative educational cycles. The spacing implies no timing requirement, and in particular nothing here says anything about how soon a command to a different rank may follow one to another — that is Module 14's, and it is a real constraint that this trace deliberately does not model.
6. Channel Is Resolved Even Earlier
The third destination deserves its own treatment, because it is the one people are most tempted to call an address field.
A channel is an independent DDR interface. Chapter 5.4 established that: its own command bus, its own data bus, its own devices. Two channels do not share pins.
So a channel bit cannot travel on a DDR interface, because it is what decides which DDR interface exists for this request. By the time a command is being encoded, the channel has already been chosen — the request is at a controller, and that controller drives one channel.
Where the choice happens varies, and that is the point. It may be made in the interconnect, in a memory-side address decoder, or in a multi-channel controller's front end. In none of those places is it a DDR command. Chapter 5.4's channel_selector models the extraction and the resulting load imbalance across channels, which is the interesting question about channel bits and is why this chapter does not re-open it.
DDR5 sharpens this with a verified example. Chapter 4.7 covered it: verified — a DDR5 channel is split into two independent sub-channels, each with its own command/address bus. Chapter 5.4 and Chapter 6.1 both noted the consequence.
So in DDR5 there is a selection that chooses between two CA buses on the same module. A sub-channel index is therefore pre-interface in exactly the sense above: it never appears as an operand, and it is not qualification either — it does not choose which device listens to a bus, it chooses which bus is used.
| Selection | Mechanism | Appears on a DDR pin? |
|---|---|---|
| column, row, bank, BG | operand on the CA bus | yes, as address/command bits |
| rank | which CS# is asserted | yes, as a dedicated signal |
| sub-channel (DDR5) | which CA bus is used | no — it selects the bus |
| channel | which interface is used | no — resolved upstream |
7. Four Assertions Worth Writing
// P1 -- the chapter as a property. Two addresses differing ONLY in the
// rank field must produce identical operands. This is what a rank field
// that has leaked into an operand position breaks, and it is not provable
// by inspecting any single transaction.
// RANK_MASK is the localparam added in Section 4: the rank field's footprint
// in the address. It is ZERO when the rank field is absent, which makes the
// antecedent unsatisfiable -- the correct behaviour, and the reason this is
// written as a mask rather than as a cast to the field width, which would be
// a zero-width cast and therefore illegal.
property p_rank_does_not_disturb_operands;
@(posedge clk)
(((sys_addr ^ $past(sys_addr)) & ~RANK_MASK) == '0)
&& ((sys_addr ^ $past(sys_addr)) != '0)
|-> (col_operand == $past(col_operand))
&& (bank_operand == $past(bank_operand))
&& (row_operand == $past(row_operand));
endproperty
assert property (p_rank_does_not_disturb_operands);
// P2 -- an absent rank field reads as a safe zero AND says so. Chapter
// 8.4's lesson applied to a second optional field: the value is legal, so
// only the flag carries the information.
property p_absent_rank_is_flagged;
@(posedge clk)
!rank_field_present |-> (rank_index == '0) && (rank_stride_bytes == '0);
endproperty
assert property (p_absent_rank_is_flagged);
// P3 -- the bit budget accounts for every mapped bit. Section 2's taxonomy
// as an arithmetic invariant, and the property that catches a field
// silently dropped from the map: a gap would make the sum fall short of
// MAP_W - OFFSET_W.
property p_bit_budget_is_complete;
@(posedge clk)
(operand_bits + qualify_bits + preiface_bits)
== 8'(MAP_W - OFFSET_W);
endproperty
assert property (p_bit_budget_is_complete);
// P4 -- the offset bits reach no destination. Stated as insensitivity:
// every output field is unchanged when only offset bits move. This is the
// Chapter 8.2 Section 3 contract extended to all three destinations.
property p_offset_reaches_nothing;
@(posedge clk)
((sys_addr >> OFFSET_W) == ($past(sys_addr) >> OFFSET_W))
|-> (col_operand == $past(col_operand))
&& (bank_operand == $past(bank_operand))
&& (row_operand == $past(row_operand))
&& (rank_index == $past(rank_index))
&& (channel_index == $past(channel_index));
endproperty
assert property (p_offset_reaches_nothing);What these prove. P1 is the chapter's thesis made checkable, and note that it requires two transactions — a single command can never demonstrate it. P2 makes an absent field safe to consume. P3 turns the taxonomy into an invariant that catches a dropped or overlapping field by arithmetic rather than by inspection. P4 proves the offset bits are inert across all three destinations, which is stronger than Chapter 8.2 §7's version.
What they do not prove. Nothing here proves a command reached the right device, because cs_n is not in this block — that requires Chapter 6.2's decoder and a property spanning both, which is the right place for it. Nothing proves the channel was routed correctly, since whatever consumes channel_index is outside this block and usually outside the controller. Nothing says anything about rank-switching cost or legality — Modules 14 and 22. And P1 holds under both placements while saying nothing about which is appropriate (Module 18).
8. DV — Observing a Field That Is Not in the Command
This chapter creates the module's hardest verification requirement, and it is structural rather than a matter of effort.
The rank is on a different wire from the command, so a monitor needs two sampling points that must agree. Chapter 6.2 established that a command exists only when its chip-select qualifies it — so the monitor's very first decision, before decoding anything, is which rank is this for, and that decision comes from CS#.
Get it wrong and the damage compounds. Recall the chain: Chapter 8.2 showed that a column command's row comes from state. If the rank is misattributed, the activate goes into the wrong rank's state model, and every subsequent column command to either rank resolves against a wrong row. One mis-sampled qualification signal corrupts an unbounded number of later reconstructions.
Which yields four concrete requirements:
The bank-state model must be indexed by {rank, bank}, not by bank. This is the most common structural omission, because a single-rank bring-up works perfectly and the defect appears only when the second rank is populated. Chapter 5.2's table is instantiated per rank; it is not extended.
Qualification must be sampled at the command event, not near it. Chapter 6.1 and Chapter 7.1 §2 established that a command is defined at a sampling event. The chip-select and the encoding must be sampled at the same event, or a monitor will occasionally pair one command with a neighbouring cycle's qualification — a rare, non-reproducible misattribution that is far harder to find than a systematic one.
The reconstructor must be told its channel. Nothing observable on a channel identifies it, so the channel is configuration for the monitor. A multi-channel environment with one shared reconstructor and no channel tagging will merge traffic from independent interfaces, and the resulting address stream will be self-contradictory in a way that looks like a DUT bug.
Coverage must include rank alternation, deliberately. Under §4's RANK_HIGH placement the rank stride is 1 GB, so a test with a small footprint never changes rank at all and every rank-related path is untested while the regression looks healthy. A directed test stepping by rank_stride_bytes is worth more here than any amount of random traffic, and it is the test that catches a shared state table.
9. Debugging — The Right Command to the Wrong Device
Symptom. Data corruption that appears only when both ranks are populated. A single-rank configuration of the same system is clean. The protocol trace shows legal, well-formed commands, and every operand matches the reference model.
Candidate mechanisms.
- The rank field is extracted from the wrong bit, so accesses go to the wrong rank — half the address space aliases onto the other half.
- The rank index is correct but the chip-select decode is wrong, so the right index selects the wrong device. This is Chapter 6.2's layer, not this one.
- The bank-state model is shared across ranks rather than instantiated per rank, so one rank's activate is believed to have opened a row in the other.
NUM_RANKSis 1 in the IP and 2 on the board, so every access goes to rank 0 and the upper half of memory is unreachable rather than corrupt.- The rank field overlaps the row field because a placement expression is wrong, so changing rank also changes row.
Evidence to collect. For failing accesses: the system address, the extracted rank_index, the observed cs_n pattern at the command event, the reference model's rank, and rank_field_present from the DUT's instance. The CS# capture is the item most often missing from an existing trace, and without it mechanisms 1 and 2 are indistinguishable.
Discriminator.
- Failures appear only with two ranks populated: this already excludes everything that does not involve the rank dimension, and points at 1, 3 or 5 — a genuinely useful first observation, because it is available before any waveform.
rank_indexdisagrees with the reference model: mechanism 1. Checkrank_stride_bytesagainst the address interval between aliasing failures — a rank field at the wrong bit produces aliasing at2^(wrong position).rank_indexagrees butcs_nselects another device: mechanism 2, and the layer boundary is the useful part — hand it to Chapter 6.2's decoder, because nothing in the address map is at fault.- Operands and rank both correct, corruption persists, and failures follow an activate to the other rank: mechanism 3. The tell is the correlation with the other rank's activity, and it is why the trace must include both ranks rather than only the failing one.
rank_field_presentlow, or the upper half of memory reading back as the lower half: mechanism 4. Not corruption at all — an aliased address space, and the signature is that writes toXandX + rank_strideoverwrite each other.- Changing only the rank bit also changes the row operand: mechanism 5, and P1 in §7 fails immediately on that transition.
Responsible layer. Mechanisms 1 and 5 are layer B — this chapter. Mechanism 2 is layer D and belongs to CS#. Mechanism 3 is verification-environment structure, not the design at all — and it is the one most likely to consume a day, because the DUT is innocent and every operand checks out. Mechanism 4 is configuration.
Fix. Per mechanism; and in all cases add the rank-alternating directed test, so that a configuration which never exercises the second rank cannot pass a regression again.
10. Common Misconceptions
"Rank selection is just another address field."
Why it is tempting: it is extracted from the address exactly like a bank index, and it appears in the same tuple when people write (channel, rank, bank, row, column).
Concrete failure: a monitor decodes the rank from CA-bus bits, finds whatever happens to be in those positions, and misattributes activates. Every subsequent column command resolves against the wrong row.
Correct model: the rank is extracted from the address and then leaves the address path. It becomes a chip-select assertion. §5 shows two commands identical on the CA bus reaching different devices.
Prevention: sample the rank from CS# at the command event, and index the state model by {rank, bank}.
"Channel selection is just another CA address field."
Why it is tempting: channel appears alongside rank and bank in every capacity calculation.
Concrete failure: an engineer looks for channel bits on the command/address bus and concludes the map is broken when they are not there.
Correct model: a channel is an independent interface. The channel bit chooses which interface, so it is resolved before a command exists — often in the interconnect — and never appears on a DDR pin.
Prevention: §6's table. Ask which pins the selection would travel on; for a channel there is no answer, and that is the answer.
"Rank selection is always a normal address field."
Why it is tempting: in most systems a contiguous slice of the address does select the rank, so the extraction half of the statement is true.
Concrete failure: the conclusion that a command therefore carries the rank — and a reference model that expects to read it out of the command.
Correct model: extraction and transmission are different steps. Rank is address-derived and pin-qualified. Both halves matter, and the second is what makes it different from every other field.
Prevention: hold the three destinations from §2 separate. Extraction tells you where a field comes from; destination tells you where to look for it.
"A single-rank system has no rank selection."
Why it is tempting: with one rank there is nothing to choose, and rank_field_present is low.
Concrete failure: an engineer assumes CS# is unnecessary and is surprised that a device still requires qualification — or a monitor skips CS# sampling entirely and then breaks when a second rank appears.
Correct model: a single-rank system still asserts a chip-select, and deselect still means no command. What is absent is the address bit that chooses, not the qualification.
Prevention: read the flag as "no address bit decides the rank," never as "no chip-select."
"Switching rank costs command bandwidth."
Why it is tempting: every other kind of switching in DDR costs something on the command path.
Concrete failure: a performance model accounts for rank switching as extra command slots and cannot explain measurements that differ from its prediction.
Correct model: no command bits are spent on the rank at all — §4's synthesis note shows the CA bus width is independent of the rank count. The cost is elsewhere, on the shared electrical bus, and Modules 14 and 22 own it.
Prevention: separate "what does it cost on the command path" from "what does it cost on the bus." They are different questions with different owners.
"A per-bank state model is enough."
Why it is tempting: it works perfectly during single-rank bring-up, which is when the model is written.
Concrete failure: with two ranks populated, one rank's activate updates the entry the other rank's column commands read. Corruption appears late, in a configuration the model was never exercised against.
Correct model: each rank's banks hold their own rows. State is indexed by {rank, bank}.
Prevention: instantiate Chapter 5.2's table per rank from the start, and run the rank-alternating test from §8 even when only one rank is populated.
11. Interview Reasoning
"Why is channel selection different from a normal DDR address operand?"
Because a channel is an independent interface with its own command and data buses — so a channel bit chooses which DDR interface handles the request, and by the time a command is being formed that choice has already been made. There is no pin it could travel on, because the pins are what it selects between. Practically, this means a channel is implicit in which bus you are probing, and a monitor has to be told its channel because nothing observable will reveal it. DDR5 makes the same point one level down, with a verified split of a channel into two sub-channels, each with its own CA bus.
"Is rank an address field?"
Half of one, and the interesting half is the other half. It is extracted from the system address like any other field — a contiguous slice, at a position the controller chose. But it is never encoded as an operand: it becomes a chip-select assertion, because a target selection has to be resolved before decode or every device on the bus would have to decode every command to learn it was not the recipient. So rank is address-derived and pin-qualified, and the practical consequence is that two commands can be bit-identical on the CA bus and reach different devices.
"What information does a monitor need to reconstruct a system address on a two-rank, two-channel system?"
Everything from Chapter 8.2 — operands, its own activate history, the map — plus two things this chapter adds: the chip-select pattern sampled at the command event, because the rank is nowhere in the command, and the channel as configuration, because nothing on the interface identifies it. And it still cannot recover the low-order offset bits. So the reconstruction draws on three observation sources and one configuration input, and the count is worth stating plainly in an interview because it demonstrates that addressing is a modelling problem rather than a decode problem.
"Your design works with one rank and corrupts data with two. Where do you look?"
At whether the bank-state model is indexed by rank. That single question covers the most likely cause, and it has the useful property of pointing at the verification environment as readily as at the design — a shared per-bank model is a classic structural omission, because single-rank bring-up never exercises it. Then check the rank extraction and the CS# decode, in that order, since they are distinguishable only if the trace captured the chip-selects.
"Why does doubling the rank count not widen the command bus?"
Because the rank is not in the command. It costs a chip-select signal per rank, and nothing on the address path — which is the architectural reason qualification is the right mechanism for this particular selection. It is also why rank is a comparatively cheap way to add capacity on the interface side, and why the cost shows up instead as electrical loading on a shared bus, which is Module 22's subject.
12. Engineering Exercise
Config from §4: OFFSET_W = 6, NUM_COLS = 16, NUM_BANKS = 16, ROW_W = 16, NUM_RANKS = 2, NUM_CHANNELS = 2, ADDR_W = 32.
1. Give every field's bit position under both rank placements, and confirm MAP_W.
2. Verify by capacity that this map describes exactly 4 GB.
3. Under RANK_HIGH, which pairs of addresses differ only in rank? Give one pair.
4. Under RANK_LOW, what is the rank stride, and what does a 4 KB-strided stream do?
5. Classify each field as operand, qualification or pre-interface, and state which pins each reaches.
6. A monitor is built with one bank-state table indexed by bank alone. Construct the shortest command sequence that makes it report a wrong row.
13. Summary
A rank is a set of devices that respond together, and rank selection chooses which devices have a command at all. It is not a location inside a device.
Rank is extracted from the address and then leaves the address path. It becomes a chip-select assertion rather than an operand — because a target selection resolved by decode would force every device on the bus to decode every command to discover it was not the recipient.
Three destinations, not one. Operand fields (column, row, bank, bank group) reach the device on the CA bus. Qualification (rank) reaches it on CS#. Pre-interface fields (channel, and DDR5's verified sub-channel split) reach no DDR pin at all, because they select which interface is used. Plus a fourth category that reaches nothing: the transfer offset.
So two commands can be bit-identical on the command/address bus and reach different devices. A monitor watching only the CA bus cannot tell them apart — and because a column command's row comes from state, one mis-sampled qualification corrupts every later reconstruction in that model.
Which makes {rank, bank} the correct index for bank state. A per-bank model works flawlessly during single-rank bring-up and fails when the second rank is populated, which is the worst possible time to discover a structural omission.
And the rank costs nothing on the command path. The CA bus width is independent of the rank count; the cost is electrical, on a shared bus, and belongs to Modules 14 and 22.
14. What Comes Next
Five chapters have each taken one field, and each has used a slightly different educational layout to suit what it was teaching. Chapter 8.6 — Physical Mapping assembles them into one parameterised map and then asks the question none of the individual chapters could:
Is the mapping lossless — and can it be inverted?
The first question is a round trip: decompose an address into fields, reassemble the fields, and demand the original back. It is the only check that catches a dropped bit, an overlapping slice, or a wrong concatenation order, and it catches all three with one property.
The second is harder and is what Chapters 8.1, 8.2 and 8.5 have each deferred to it: reconstructing a system address from observed commands, with the row coming from history, the rank from a different set of pins, the channel from configuration, and the offset unrecoverable. That is the monitor this module has been building toward, and it is where all five fields and all four destinations finally have to agree.
Return to Ranks for what a rank is, CS# for the chip-select decoder that consumes this chapter's index, Channels for the channel selector and its imbalance study, DDR5 for the verified sub-channel split, and Activate for the qualification step in the command model.
Continue learning
Related tutorials
- Related topic
CS# — Chip Select
The command bus reaches every rank, so every rank sees every command. Chip select decides which one acts — and because selecting nothing is legal and common, it is at most one, not exactly one.
- Related topic
Ranks
A rank is the set of devices that together supply the controller's data width. Because ranks share one physical data bus, only one may drive it at a time — which makes a rank a question of resource ownership, and means adding ranks adds capacity without adding bandwidth.
- Related topic
CKE — Clock Enable
CKE decides whether a device samples commands at all. It qualifies using its previous value rather than its current one — and in DDR5 the function survives while the dedicated pin does not.
- Related topic
The Refresh Requirement
Leakage produces a rule about the passage of time rather than about any operation. What the maintenance operation actually does, why it costs device availability, and how a digital design tracks a deadline, arbitrates it against traffic, and proves it never silently drops the obligation.
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.
