Skip to content
VLSI Mentor

DDR · Module 5

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.

Chapters 5.1 to 5.3 stayed inside one device. This chapter leaves it, and the reason is a mismatch between two numbers.

A memory controller needs a data path of a particular width. A single DRAM device supplies far less than that — commonly 4, 8 or 16 bits. So several devices must operate together, each contributing a slice of the width, and the set that does so is a rank.

The arithmetic of adding widths is the easy part and takes one section. The consequence is the interesting part: multiple ranks attach to one physical data bus, so only one of them may drive it at any moment. That makes a rank fundamentally a question of resource ownership rather than of selection — the same reasoning pattern Chapter 3.5 used for sense amplifiers, now at the scale of a board.

And it produces the fact that catches more people than anything else in this module: adding ranks adds capacity and does not add bandwidth.

1. Width Construction

A controller with a 64-bit data path and devices that are 8 bits wide needs eight devices, each supplying a different slice of every 64-bit word. All eight receive the same command and the same address; each responds on its own eight data lines.

The arithmetic, with verified organisations:

Device widthDevices per 64-bit rankDevices per 72-bit ECC rank
x41618
x889
x164

The ECC column is the one that makes the structure concrete. A DDR4 ECC DIMM carries 72 bits — 64 data plus 8 ECC, which is exactly nine x8 devices or eighteen x4 devices per rank. The ninth device is not special; it is an ordinary DRAM contributing its eight bits to a word that happens to be eight bits wider.

DDR5 reorganises this without changing the principle. Chapter 4.6 established that a DDR5 channel is split into two independent 32-bit sub-channels, and with ECC each sub-channel is 40 bits — 32 data plus 8 ECC. So a single-rank x8 DDR5 module carries ten devices: five per sub-channel, since 5 × 8 = 40. The rank still means "the devices that together supply the width" — the width just got defined per sub-channel.

Eight x8 devices operating together supply a 64-bit rank, or nine of them supply a 72-bit ECC rank. Sixteen x4 devices supply the same 64-bit width, and four x16 devices supply it with fewer, wider devices. In every case the rank width is fixed by the controller and the device count follows from the device width.x8 device8 bits each8 devicesact together64-bit rankone data word9 for ECC72 bitsx4 device4 bits each16 devicessame 64 bitsx16: 4 devicesfewer, widerneedsgivesneeds12
Figure 1 — a rank is however many devices it takes to reach the required width.

The width is fixed by the controller and the device count follows. That is the direction of causality, and it explains the device-width trade Chapter 5.1 §4 flagged: narrower devices mean more of them per rank, which costs package count, board area and electrical load — while wider devices mean fewer, which is cheaper to assemble and, as 5.1 and 5.3 both noted, comes with fewer banks and fewer bank groups each.

So device width is a system-level trade, not a detail. Sixteen x4 devices give a 64-bit rank with a great deal of aggregate bank parallelism and a heavy electrical load; four x16 devices give the same width with less parallelism and a lighter load.

2. All of Them, Together, Every Time

Something about §1 is easy to read past: every device in a rank participates in every access to that rank.

There is no mechanism for reading "just the low 8 bits" of a 64-bit rank. The command and address go to all eight devices, all eight activate the same row in the same bank, and all eight deliver their slice. A rank behaves as one wide device, and its internal structure — banks, bank groups, rows — is replicated in lockstep across every device in it.

3. Ranks Share One Bus

Now the consequence that makes ranks structurally interesting.

Multiple ranks attach to the same physical data bus. Rank 0's eight devices and rank 1's eight devices drive the same 64 data lines. That is the entire point of having ranks rather than simply wider buses: capacity is added without adding wires, because wires are the expensive resource Chapter 3.2 §4 priced.

And it means only one rank may drive the bus at a time. Two ranks driving simultaneously is not a performance problem; it is contention on a shared conductor, which produces no useful data at all.

The selection mechanism is the chip select. The command and address lines reach every rank; a per-rank select signal determines which rank acts on what it sees. Unselected ranks observe the command and do nothing — critically, they do not drive the data bus.

Rank zero and rank one both attach to a single shared data bus that connects to the controller. A per-rank chip select determines which rank acts on the command every rank can see. Because only one rank may drive the bus at a time, the bus is an owned resource, and changing owner costs a handoff.Rank 08 devicesShared data busone driver at a timeControllergrants the busRank 18 devicesChip selectpicks who actsHandoff costschanging ownerdrivesor drives12
Figure 2 — ranks are selected, but the data bus is owned: one driver at a time.

This is a resource-ownership structure, and you have seen it before. Chapter 3.5 built sense_resource_owner for sense amplifiers shared between local arrays: request, grant, hold for the duration, release. The pattern here is identical and the scale is entirely different — the resource is a board-level bus rather than on-die circuitry, and the participants are sets of packages rather than array segments.

Recognising the repeat is worth more than either instance. Whenever a resource is shared and cannot be used by two parties at once, the structure is ownership, and it always brings the same four obligations: exclusive grant, stability for the duration of use, explicit release, and a defined cost for changing owner. Chapter 4.2 §3's bidirectional strobe turnaround is a third instance of the same thing.

4. Capacity Without Bandwidth

Here is the fact that surprises people, and it follows directly from §3.

Adding a rank adds capacity. It does not add a data bus.

Two ranks share the same 64 lines, so the peak rate at which data can move is exactly what it was with one rank. The second rank doubles how much can be stored and adds nothing at all to how fast it can be accessed.

What a second rank does add is a modest amount of overlap, and it is worth being precise about what kind. While rank 0 is using the data bus, rank 1 can be doing internal work — activating rows, restoring — because that work does not need the bus. So ranks overlap the same thing banks overlap: the slow internal part, and they are strictly worse at it than banks because a rank change also costs a bus handoff.

And the handoff is a real cost. Changing which rank drives the bus means one set of drivers releasing it and another taking it, on a bus with multiple attached loads. That transition takes time during which nothing useful moves. Modules 13 and 14 quantify it; the structural point is that rank changes are not free and consecutive accesses to the same rank are cheaper than alternating.

5. RTL — Ownership of a Shared Bus

Engineering problem

Several ranks share one data bus. The controller must grant it to exactly one rank at a time, hold that grant stable for the entire duration of a transfer, ensure no unselected rank drives, release it cleanly, and account for the cost when the owner or the direction changes.

Classification

SYNTHESIZABLE RTL. A resource-ownership arbiter with transfer-duration stability — real controller logic, and the structure Module 17's scheduler sits on top of.

It models the ownership contract, not the electrical bus. There is no modelling of drivers, termination, bus turnaround time, signal integrity or the physical handoff — those are Modules 19 to 22' subject and are analog. turnaround here is a flag saying a handoff occurred, not a duration, and the block deliberately assigns it no cost, because the cost is a timing parameter and this is a structural model.

Interface

req_valid with req_rank, req_beats and req_is_write asks for the bus. grant awards it. owner_rank, owner_valid and beats_left report the current transfer. drive_enable is the one-hot vector that makes the exclusion property directly checkable. turnaround flags a change of owner or direction; illegal_overlap reports a second rank asking while the bus is busy.

State

The current owner, whether the bus is busy, the remaining beats, and the current direction. drive_enable is derived from the owner rather than held separately, so there is exactly one source of truth about who is driving — a design decision that makes the mutual-exclusion property trivially true by construction rather than something to maintain.

Combinational logic

Grant eligibility, the one-hot drive vector, and the turnaround detection.

Sequential logic

Ownership is taken on a grant and held until the beat counter expires. Nothing can change it in between, which is §6's P2 and the property the whole block exists to guarantee.

Simulation

vlog rank_bus_owner.sv tb_rank_bus_owner.sv then vsim -c tb_rank_bus_owner -do "run -all"; VCS vcs -sverilog rank_bus_owner.sv tb_rank_bus_owner.sv && ./simv; Xcelium xrun -sv rank_bus_owner.sv tb_rank_bus_owner.sv.

Expected output: §7's sequence grants three transfers, refuses one mid-transfer request with illegal_overlap, and flags two turnarounds — one that changes both rank and direction, one that changes only direction.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// RANK BUS OWNER.  Classification: SYNTHESIZABLE RTL.
//
// Several ranks share ONE data bus, so it is an OWNED resource: granted to
// exactly one rank, held stable for the whole transfer, released, and
// costly to hand over. The same pattern as Chapter 3.5's
// sense_resource_owner at a completely different scale -- and the same four
// obligations: exclusive grant, stability for the duration, explicit
// release, a defined cost for changing owner.
//
// MODELS THE OWNERSHIP CONTRACT, NOT THE ELECTRICAL BUS. No drivers, no
// termination, no turnaround TIME, no signal integrity. `turnaround` is a
// FLAG saying a handoff happened, not a duration -- the duration is a
// timing parameter (Modules 13/14) and the electrical behaviour is
// Modules 19-22. Giving it a cycle count here would bake one generation's
// values into a structural model.
//
// `drive_enable` is DERIVED from the owner rather than held separately, so
// two ranks driving at once is unrepresentable rather than merely forbidden.
// ─────────────────────────────────────────────────────────────────────────
module rank_bus_owner #(
  parameter int NUM_RANKS = 2,
  parameter int MAX_BEATS = 8,
  parameter int ACC_W     = 16,
  parameter int RK_W      = (NUM_RANKS <= 1) ? 1 : $clog2(NUM_RANKS),
  parameter int BEAT_W    = $clog2(MAX_BEATS + 1)
) (
  input  logic                 clk,
  input  logic                 rst_n,

  input  logic                 req_valid,
  input  logic [RK_W-1:0]      req_rank,
  // Beats this transfer occupies the bus for. 1..MAX_BEATS.
  input  logic [BEAT_W-1:0]    req_beats,
  input  logic                 req_is_write,

  output logic                 grant,
  output logic                 owner_valid,
  output logic [RK_W-1:0]      owner_rank,
  output logic [BEAT_W-1:0]    beats_left,
  // One-hot: exactly the owning rank drives, and only while owning.
  output logic [NUM_RANKS-1:0] drive_enable,

  // A handoff occurred: the owner changed, the direction changed, or both.
  // A FLAG, not a cost.
  output logic                 turnaround,
  // A rank asked for the bus while another rank held it. Reported, never
  // queued here -- queueing is the scheduler's job (Module 17), and
  // silently absorbing it would hide a requester that does not respect
  // ownership.
  output logic                 illegal_overlap,
  output logic                 rank_invalid,

  output logic [ACC_W-1:0]     cnt_grants,
  output logic [ACC_W-1:0]     cnt_turnarounds
);

  // ── COMPILE-TIME legality.
  if (NUM_RANKS < 1) begin : g_nr_min
    initial $fatal(1, "rank_bus_owner: NUM_RANKS must be >= 1");
  end
  if (MAX_BEATS < 1) begin : g_mb_min
    initial $fatal(1, "rank_bus_owner: MAX_BEATS must be >= 1");
  end

  // ── Rank index range check, Chapter 5.1's pattern: NOT a cast of the
  //    count to the field's own width, which truncates for powers of two.
  logic rank_bad;
  if (NUM_RANKS >= (1 << RK_W)) begin : g_rk_full
    assign rank_bad = 1'b0;
  end else begin : g_rk_check
    assign rank_bad = ({1'b0, req_rank} >= (RK_W+1)'(NUM_RANKS));
  end
  assign rank_invalid = req_valid && rank_bad;

  logic [RK_W-1:0]   owner_q;
  logic              busy_q;
  logic [BEAT_W-1:0] beats_q;
  logic              dir_q;        // 1 = write
  logic              prev_valid_q; // a previous owner exists at all

  assign owner_valid = busy_q;
  assign owner_rank  = owner_q;
  assign beats_left  = beats_q;

  logic beats_ok, can_grant;
  assign beats_ok  = (req_beats != '0) && (req_beats <= BEAT_W'(MAX_BEATS));
  // The bus is free on the final beat's cycle, so a back-to-back grant is
  // possible without an idle cycle -- but only to a transfer that is
  // actually legal.
  assign can_grant = req_valid && !rank_bad && beats_ok
                  && (!busy_q || (beats_q == BEAT_W'(1)));

  assign grant = can_grant;

  // ── Exclusion, by construction. drive_enable is a one-hot decode of the
  //    owner, gated by busy. There is no way to express two simultaneous
  //    drivers, which is stronger than forbidding it with a check.
  always_comb begin
    drive_enable = '0;
    if (busy_q) drive_enable[owner_q] = 1'b1;
  end

  // ── Turnaround: granting to a different rank, or the same rank in the
  //    other direction. Both cost real time on hardware; both are flagged
  //    identically here because the STRUCTURE is the same and the costs
  //    differ, which is Modules 13/14's distinction to make.
  assign turnaround = can_grant && prev_valid_q
                   && ((req_rank != owner_q) || (req_is_write != dir_q));

  assign illegal_overlap = req_valid && !rank_bad && busy_q
                        && (beats_q != BEAT_W'(1)) && (req_rank != owner_q);

  logic [ACC_W:0] g_sum, t_sum;
  always_comb begin
    g_sum = {1'b0, cnt_grants}      + (ACC_W+1)'(1);
    t_sum = {1'b0, cnt_turnarounds} + (ACC_W+1)'(1);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      owner_q         <= '0;
      busy_q          <= 1'b0;
      beats_q         <= '0;
      dir_q           <= 1'b0;
      prev_valid_q    <= 1'b0;
      cnt_grants      <= '0;
      cnt_turnarounds <= '0;
    end else begin
      if (can_grant) begin
        // Take ownership. Everything that defines the transfer is captured
        // in one place and one cycle -- owner, duration and direction --
        // because a grant that captured them separately could be
        // interrupted between them.
        owner_q      <= req_rank;
        busy_q       <= 1'b1;
        beats_q      <= req_beats;
        dir_q        <= req_is_write;
        prev_valid_q <= 1'b1;

        cnt_grants <= g_sum[ACC_W] ? {ACC_W{1'b1}} : g_sum[ACC_W-1:0];
        if (turnaround) begin
          cnt_turnarounds <= t_sum[ACC_W] ? {ACC_W{1'b1}} : t_sum[ACC_W-1:0];
        end
      end else if (busy_q) begin
        // Count down. OWNERSHIP IS NOT RECONSIDERED HERE -- there is
        // deliberately no path that changes owner_q while beats remain.
        // That is what makes Section 6's stability property hold by
        // construction rather than by careful maintenance.
        if (beats_q <= BEAT_W'(1)) begin
          busy_q  <= 1'b0;
          beats_q <= '0;
          // owner_q and dir_q are LEFT ALONE: with busy low they select
          // nothing, and keeping them makes the NEXT turnaround comparison
          // meaningful. Clearing them would make every grant after an idle
          // period look like a turnaround against rank 0.
        end else begin
          beats_q <= beats_q - BEAT_W'(1);
        end
      end
    end
  end

endmodule

Cycle trace

NUM_RANKS = 2, three-beat transfers:

CycleRequestbusyownerbeats_leftdrive_enableEvent
0rank 0, 3, read0000grant
110301
2rank 1, 3, write10201illegal_overlap
310101final beat
4rank 1, 3, write0000grant + turnaround
511310
8rank 1, 2, read0000grant + turnaround

Cycle 2 is the refusal. Rank 1 asks while rank 0 owns the bus with two beats remaining. No grant, illegal_overlap reported, and — the important part — owner_q and drive_enable are completely unaffected.

Cycle 4 changes both rank and direction; cycle 8 changes only direction, and both are flagged. On hardware those cost different amounts, and distinguishing them is Modules 13 and 14' job.

Waveform expectation

§7. Watch drive_enable: it must be one-hot whenever the bus is busy, all-zero when idle, and never change while beats_left is above one.

Synthesis implication

A small counter, a rank register, a direction bit, a one-hot decoder and two saturating counters — tens of flops. The one-hot decode grows with rank count and is trivial at realistic counts. A real controller's version differs mainly in that the grant decision is made by a scheduler choosing among many pending requests, where this block simply accepts or refuses the one it is offered.

Corner cases

NUM_RANKS == 1 makes turnaround reduce to direction changes only, which is correct — a single-rank system still pays read/write turnaround, and that is worth seeing at the degenerate parameter. RK_W == 1 then has one legal encoding of two, so the range check materialises, which is 5.1 §5's case again. req_beats == 0 is refused rather than treated as a zero-length transfer. A request on the final beat is granted back-to-back with no idle cycle, which is deliberate and is why can_grant tests beats_q == 1 rather than !busy_q. A same-rank request mid-transfer is not illegal_overlap — it is simply not granted, because a rank asking for the bus it already holds is a scheduling inefficiency rather than a protocol violation.

Verification

What DV must prove: drive_enable is one-hot when busy and zero when idle; ownership never changes while beats remain — the central property; no grant to an invalid rank; illegal_overlap on exactly the cross-rank mid-transfer case and never on a same-rank one; back-to-back grants on the final beat; turnaround flagged on rank change, direction change, and both; and counters matching an independent tally.

Debugging

If two ranks appear to drive at once, look upstream — this block cannot express it, so the fault is in whatever consumes drive_enable or in a second driver outside the model. If a grant after a long idle period is always flagged as a turnaround, check that owner_q and dir_q are not cleared when the bus goes idle; clearing them makes every post-idle grant compare against rank 0 and direction read. If back-to-back transfers stall for a cycle, check can_grant's beats_q == 1 term. If illegal_overlap fires for same-rank requests, the req_rank != owner_q term is missing and the block is reporting a scheduling inefficiency as a protocol error.

Limitations

No turnaround cost — the flag has no duration, by design. No queueing or arbitration among multiple pending requests, which is the scheduler's value and Module 17's subject. No bank or row state: this block does not know or care what the transfer is reading. No electrical modelling whatsoever. And no notion of the command bus, which is shared by all ranks and has its own occupancy — a real controller must schedule commands as well as data, and they are different resources with different constraints.

6. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY, inside rank_bus_owner.

// P1 -- MUTUAL EXCLUSION. At most one rank drives, and only while the bus
// is busy. The safety property: two drivers on a shared conductor produce
// no useful data at all, so this is not a performance property.
property p_one_driver;
  @(posedge clk) disable iff (!rst_n)
    $onehot0(drive_enable) && (busy_q || (drive_enable == '0));
endproperty
assert property (p_one_driver);

// P2 -- OWNERSHIP IS STABLE FOR THE DURATION. The chapter's property: a
// transfer that changed owner part-way would deliver half its beats from
// each of two ranks, and nothing downstream would detect it because each
// beat individually looks well-formed.
property p_owner_stable_mid_transfer;
  @(posedge clk) disable iff (!rst_n)
    (busy_q && (beats_q > BEAT_W'(1)))
      |=> (owner_q == $past(owner_q)) && busy_q;
endproperty
assert property (p_owner_stable_mid_transfer);

// P3 -- an unselected rank is never granted, and an invalid one never
// becomes the owner.
property p_no_grant_to_invalid;
  @(posedge clk) disable iff (!rst_n)
    rank_invalid |-> !grant;
endproperty
assert property (p_no_grant_to_invalid);

property p_owner_matches_grant;
  @(posedge clk) disable iff (!rst_n)
    grant |=> (owner_q == $past(req_rank));
endproperty
assert property (p_owner_matches_grant);

// P4 -- release. A transfer of N beats occupies the bus for exactly N
// cycles and then frees it. Without this, a design that never released
// would satisfy P1 and P2 perfectly -- the vacuity guard again, and the
// fourth time this module has needed one.
// NOTE ON THE FORM. The tempting version captures the beat count in a
// local variable and asserts release after exactly that many cycles:
//
//   (grant, n = req_beats) |=> ##(n-1) (beats_q == 1);
//
// That does not work: SVA delay ranges must be elaboration-time constants,
// and `##(n-1)` with a local variable is not legal. Capturing `n` and then
// not using it -- which is the easy mistake -- produces a property that
// LOOKS count-aware and is not. The honest form is a bounded eventuality
// with no local variable at all.
property p_releases_after_beats;
  @(posedge clk) disable iff (!rst_n)
    grant |=> ##[0:MAX_BEATS-1] (busy_q && (beats_q == BEAT_W'(1)));
endproperty
assert property (p_releases_after_beats);

// P5 -- a refused request disturbs nothing. Report, never repair.
property p_overlap_changes_nothing;
  @(posedge clk) disable iff (!rst_n)
    (illegal_overlap && !grant)
      |=> (owner_q == $past(owner_q)) && (beats_q == $past(beats_q) - BEAT_W'(1));
endproperty
assert property (p_overlap_changes_nothing);

P2 is the property this block exists for, and it is worth dwelling on why it matters more than P1. P1 forbids something that is already unrepresentabledrive_enable is a decode of a single register, so two drivers cannot be expressed. P1 is therefore nearly free and nearly worthless, and it is included only because the derivation could be changed later.

P2 forbids something that is entirely representable: an arbiter that re-evaluates its grant every cycle would change owner mid-transfer, and that is a completely natural way to write an arbiter. The failure it prevents is also uniquely nasty — half the beats from one rank and half from another, with every individual beat well-formed and nothing downstream detecting it.

The general lesson: assess each property by asking what a plausible wrong implementation would look like. A property guarding something the structure already prevents is documentation; a property guarding something a reasonable engineer might actually write is verification.

P4 is the release companion. Three chapters running, this module has needed a liveness-flavoured property to stop a restrictive set from being satisfied by a dead design, and this is the fourth instance overall. It is not a coincidence — it is what resource-ownership and state-tracking blocks look like.

What none of them prove. Nothing about the electrical handoff: whether the previous driver has actually stopped driving before the next begins is an analog question with a real duration, and it is the actual engineering difficulty of rank switching. No assertion in any simulation reaches it — that is Modules 19 to 22' territory and is verified by analog methods. Nothing about the command bus. And nothing about whether the data is correct, which needs a scoreboard.

7. A Bus Changing Hands

rank_bus_owner — two ranks sharing one data bus over ten cycles

10 cycles
Ten cycles with two ranks. Rank zero is granted the bus for a three-beat read and drives it exclusively. Rank one requests mid-transfer and is refused with an illegal overlap, leaving the owner unchanged. After rank zero releases, rank one is granted a three-beat write, flagged as a turnaround because both the rank and the direction changed. A later grant to rank one for a read is also flagged as a turnaround, this time for the direction alone.rank 0 owns — readrank 0 owns — readrank 1 owns — writerank 1 owns — writerank 1 — readrank 1 —readrank 0 takes the busrank 0 takes the busrefused mid-transferrefused mid-transferhandoff: rank and directionhandoff: rank and directionclkreq_rank0--1--1------1--grantowner_rank--000--111--1beats_left0321032102drive_enable0111022202turnaroundillegal_overlapt0t1t2t3t4t5t6t7t8t9
Figure 3 — grant, hold, refuse, release, hand over: the four obligations of ownership.

drive_enable is the signal to read. It is 01 throughout rank 0's transfer, 10 throughout rank 1's, and 0 between them. It is never anything else, and it changes only at transfer boundaries.

Cycle 2 is the refusal, and the important observation is what did not move. Rank 1 asked, illegal_overlap fired, and owner_rank, drive_enable and beats_left all continued exactly as if nothing had happened. An arbiter that re-evaluated its grant here would have handed the bus over with one beat still owed, and the resulting transfer would have been half from each rank with no signal anywhere indicating a problem.

Cycles 4 and 8 are both turnarounds and they are not the same kind. Cycle 4 changes rank and direction; cycle 8 changes direction only, with the same owner. They cost different amounts on real hardware — and this block flags them identically because the structure is the same, leaving the cost distinction to the timing modules. That layering is deliberate and is the same discipline 5.3 §4 applied.

What the figure does not claim. No handoff duration — the transitions here are instantaneous and on hardware they are not. Nothing about the command bus, which every rank shares and which this model ignores entirely.

8. The Rank, Answered Systematically

QuestionAnswer for a rank
What does it contain?The devices that together supply the controller's data width
What resource does it share?The data bus and the command bus, with every other rank
What can operate in parallel with it?Another rank's internal work — never another rank's data transfer
How is it selected?A per-rank chip select; all ranks see the command, one acts
What must the controller track?Which rank owns the bus, and the direction
What opportunity does it create?Capacity, plus overlap of internal work across ranks
What conflict does it create?Every data transfer, always — and a handoff cost on every change
What must DV verify?Exclusion, and ownership stability for the transfer's duration

The "what can operate in parallel" row is the one that distinguishes a rank from every level below it. Banks and bank groups overlap some work and serialise the rest. Ranks serialise every single data transfer, because there is one bus. That is why the capacity-without-bandwidth result of §4 is structural rather than incidental.

9. Common Misconceptions

"A rank is one DRAM chip." Wrong mental model: rank and device are the same thing. Engineering action: reasoning about rank width as device width; expecting a rank's capacity to be one device's capacity; misreading module configurations entirely. Observable failure / bad conclusion: capacity and width calculations wrong by the device count — a factor of 4 to 18 — and an inability to interpret any real configuration, since a "1Rx8" module is one rank built from eight or nine x8 devices, not one device. Correct model: a rank is the set of devices that together supply the required data width. A 64-bit rank is 8 x8 devices, 16 x4, or 4 x16; with ECC, 9 x8 or 18 x4. All of them participate in every access. Prevention: divide the rank width by the device width. That number is the device count, and it is never 1 for a realistic rank.

"A rank of eight devices has eight times the banks of one device." Wrong mental model: devices in a rank add their resources. Engineering action: modelling concurrency as device count times bank count; expecting a rank to offer far more parallelism than it does. Observable failure / bad conclusion: a concurrency model wrong by the device count, which over-predicts achievable overlap by a factor of 8 or 16 and makes every scheduling conclusion drawn from it unreliable. Correct model: devices in a rank operate in lockstep. Activating bank 3, row 500 activates that bank and row in every device, and the eight resulting rows form one wide row. A rank's bank count equals one device's bank count; the devices add width and nothing else. More banks requires another rank or another channel. Prevention: ask what the bank field selects. It selects a bank in every device simultaneously, not one device's bank.

"Dual-rank means dual-channel." Wrong mental model: ranks and channels are both ways of adding memory in parallel. Engineering action: sizing a system by rank count when bandwidth is the requirement; expecting a second rank to double throughput; recommending more ranks to fix a bandwidth shortfall. Observable failure / bad conclusion: a configuration that cannot meet its bandwidth target regardless of tuning, because the data wires are simply not there. Two ranks share one bus; two channels have two. Correct model: ranks share one command bus and one data bus and add capacity, with only one driving at a time and a handoff cost on every change. Channels have separate command and data buses and add capacity and genuinely parallel transfers. Prevention: ask whether adding one doubles the number of data wires between controller and memory. Channels yes; ranks no.

"Adding ranks increases bandwidth." Wrong mental model: more memory devices means more throughput. Engineering action: expecting a linear or even partial bandwidth gain from populating a second rank; attributing a measured gain to the rank itself. Observable failure / bad conclusion: a bandwidth projection that is wrong by the rank count, and confusion when a second rank produces a small improvement rather than a large one. Any gain that does appear comes from overlapping internal work across ranks — the same thing banks do, done worse, because a rank change also costs a handoff. Correct model: ranks share the data bus, so peak transfer rate is unchanged by adding one. A second rank adds capacity and a modest overlap of activate-and-restore work, offset by handoff costs on every alternation. Prevention: count the data wires. If they did not increase, the peak rate did not increase.

10. Debugging — Data Is Correct From One Rank and Corrupt From Another

Symptom. A multi-rank configuration works correctly when only one rank is populated or exercised. With both active, data from one rank is intermittently wrong — often the rank that is not the one used at initialisation.

Rank-dependent corruption in a system that works single-rank points at something that only exists when ranks alternate, which is a small set of mechanisms.

Mechanism 1 — per-rank trained state is missing or wrong. Inspect: whether training and calibration ran for every populated rank, and whether the results differ per rank. Expected evidence: one rank with default or absent trained values. Discriminator: read back the per-rank training results, not just the pass/fail. Chapter 4.4 §3 established that DDR3-and-later interfaces are trained per rank because each rank's path differs, and Chapter 4.5 §3 noted that per-device addressability exists precisely so per-rank results can be applied. A rank trained with another rank's values is marginal by construction. This is first because it is the most common cause and costs nothing to check.

Mechanism 2 — ownership changes mid-transfer. Inspect: whether the arbiter can re-evaluate a grant while beats remain. Expected evidence: transfers whose early beats are correct and later beats come from the wrong rank — corruption with a positional signature within a burst. Discriminator: is the corruption positional within a transfer? §6's P2 exists for exactly this, and the signature is distinctive: the first beats are right and the tail is wrong, rather than the whole transfer being wrong.

Mechanism 3 — the handoff has no time budget. Inspect: whether the controller allows any interval between one rank releasing the bus and another driving it. Expected evidence: corruption concentrated on the first beats after a rank change, and disappearing if rank alternation is reduced. Discriminator: is the corruption at the start of a transfer, and only after a rank change? Cleanly opposite to mechanism 2's signature. This is the electrical cost §5's model deliberately omits, and it is a timing configuration problem rather than a logic one.

Mechanism 4 — an unselected rank is driving. Inspect: whether chip select is reaching every rank correctly and whether any rank drives when not selected. Expected evidence: corruption that worsens with more ranks populated and does not correlate with which rank was intended. Discriminator: does the corruption depend on how many ranks are present rather than on which is targeted? This is contention rather than a timing margin, and it usually indicates a select-signal problem rather than a controller logic one.

Mechanism 5 — not rank-related: the second rank exposed an existing marginality. Inspect: whether adding a rank changed the electrical loading, and whether the single-rank case was itself marginal. Expected evidence: errors on both ranks under sufficient stress, with the second rank merely making it visible. Discriminator: stress the single-rank configuration harder before concluding the fault is rank-specific. Adding a rank adds load to a shared bus, so it can reduce margin everywhere — the new rank is the trigger, not the cause, and chasing it wastes the investigation.

Discrimination, cheapest first. Read back per-rank trained values — free, and it resolves mechanism 1 outright. Then ask whether the corruption is positional within a transfer: tail-corrupt means mechanism 2, head-corrupt-after-a-change means mechanism 3, and those two are opposite and conclusive. Then vary the population. Then stress single-rank.

The reasoning lesson. Where the corruption sits inside a transfer identifies which obligation of ownership was broken. Ownership has four obligations — exclusive grant, stability for the duration, explicit release, and a cost for handover — and each one fails with its own positional signature: instability corrupts the tail, an unbudgeted handoff corrupts the head, and a failure of exclusion corrupts unpredictably. A trace that records only "this transfer was wrong" throws all of that away. Record the beat index of the first bad beat, and the mechanism usually names itself.

11. Interview Reasoning

"What is a rank?" The set of DRAM devices that together supply the controller's data width. A single device is only 4, 8 or 16 bits wide, so a 64-bit data path needs 16, 8 or 4 of them respectively, and with ECC a 72-bit path needs 18 x4 or 9 x8 devices. Every device in the rank receives the same command and address and contributes its slice of every word — they operate in lockstep, so a rank behaves as one wide device. The important structural consequence is that multiple ranks attach to the same physical data bus, so only one may drive it at a time.

"How can several narrow devices create a wider rank?" By each driving a different subset of the data lines while receiving identical commands and addresses. When the controller activates bank 3 row 500, every device in the rank activates its own bank 3 row 500, and the slices together form one wide row. There is no mechanism to access part of a rank — the whole rank participates in every access. That is also why a rank's bank count equals a single device's bank count rather than the sum across devices: the bank field selects a bank in every device simultaneously.

"Why does adding a rank increase capacity without proportionally increasing bandwidth?" Because ranks share the data bus. The second rank's devices attach to the same wires as the first's, so the peak rate at which data can move is exactly what it was. That is the whole reason ranks exist rather than simply wider buses — capacity is added without adding wires, and wires are the expensive resource. What a second rank does add is the ability to overlap internal work: while one rank uses the bus, another can be activating rows. But that is the same thing banks already do, done worse, because a rank change also costs a bus handoff.

"What resource is shared by ranks on a channel, and what does that imply?" The data bus and the command bus. Sharing the data bus means it is an owned resource: granted to exactly one rank, held for the whole transfer, released, with a cost to hand over. That is the same ownership pattern as sense amplifiers shared between local arrays, and it brings the same four obligations — exclusive grant, stability for the duration, explicit release, and a defined handover cost. The most important one to verify is stability, because an arbiter that re-evaluates its grant mid-transfer delivers part of a burst from each of two ranks and every individual beat still looks well-formed.

"Data is corrupt from one rank and fine from the other. How do you narrow it down?" First read back the per-rank trained and calibrated values, because DDR3-and-later interfaces are trained per rank — each rank's electrical path differs — and a rank running with another rank's values is marginal by construction with no functional symptom. Then ask where in the transfer the corruption sits, because that identifies which ownership obligation broke: corruption in the tail of a burst means ownership changed mid-transfer, while corruption in the first beats after a rank change means the handoff had no time budget. Those two signatures are opposite and conclusive. If corruption instead scales with how many ranks are populated rather than with which is targeted, it is contention — likely a chip-select problem, with an unselected rank driving. And it is worth stressing the single-rank case hard before concluding anything is rank-specific, because adding a rank adds electrical load and can expose marginality that was always there.

12. Engineering Check

A controller has a 64-bit data path. Verified device organisations from §1.

1. How many x8 devices form one rank? How many x4? How many x16? 64 ÷ 8 = 8; 64 ÷ 4 = 16; 64 ÷ 16 = 4. The rank width is fixed and the device count follows.

2. With ECC, the path is 72 bits. Device counts? 72 ÷ 8 = 9 and 72 ÷ 4 = 18. The ninth x8 device is an ordinary DRAM contributing eight bits to a word that is eight bits wider — there is no "ECC device", only a wider word.

3. Each x8 device has 16 banks. How many banks does the 8-device rank have? 16. Not 128. The devices operate in lockstep, so the bank field selects a bank in every device at once and the eight banks numbered 3 form one wide bank 3. Getting this wrong over-predicts concurrency eightfold.

4. A second rank is added. What changes — capacity, banks available to the controller, peak bandwidth? Capacity doubles. Banks the controller can have open doubles, to 32 across the two ranks — real additional parallelism for internal work. Peak bandwidth is unchanged, because both ranks drive the same 64 wires. Two of the three doubled and the one people care about did not.

5. A workload alternates every access between rank 0 and rank 1. What has it done? Paid a bus handoff on every single access, while gaining nothing — both ranks' internal work could have been overlapped without alternating the transfers. This is strictly worse than staying on one rank, and it is a realistic outcome of an address mapping that puts the rank field on a rapidly changing bit. Contrast with 5.3, where alternating groups is exactly what you want: the same mapping instinct is right for bank groups and wrong for ranks, because groups replicate a resource and ranks share one.

6. A DDR5 module is described as 1Rx8 with ECC. How many devices, and why? Ten. Chapter 4.6 established DDR5 splits a channel into two independent 32-bit sub-channels; with ECC each is 40 bits, so each needs 40 ÷ 8 = 5 x8 devices, and 2 × 5 = 10. One rank, ten devices, two sub-channels — and the rank still means exactly what §1 said, with the width now defined per sub-channel.

13. Summary

A rank is the set of devices that together supply the controller's data width. A device is 4, 8 or 16 bits wide, so a 64-bit path needs 16, 8 or 4 devices, and a 72-bit ECC path needs 18 x4 or 9 x8. A DDR5 module's rank spans two 40-bit sub-channels, so a single-rank x8 ECC module carries ten devices.

The width is fixed by the controller and the device count follows — which makes device width a system trade: narrower means more devices, more aggregate bank parallelism and heavier electrical load; wider means fewer devices, fewer banks each, and a lighter load.

Every device in a rank participates in every access, in lockstep. Activating a bank and row activates that bank and row in every device. So a rank's bank count equals one device's bank count — the devices add width, never banks. More banks requires another rank or channel.

Ranks share one data bus, which is exactly why they exist: capacity without additional wires. So only one rank may drive at a time, making the bus an owned resource with the four obligations ownership always carries — exclusive grant, stability for the duration of a transfer, explicit release, and a cost to hand over. That is the same pattern as sense amplifiers shared between local arrays, at board scale.

Adding a rank adds capacity and does not add bandwidth. The peak transfer rate is set by the wires, and the wires did not change. A second rank adds open banks and overlap of internal work, offset by a handoff on every alternation — so alternating ranks per access is strictly worse than staying put, which is the opposite of the right instinct for bank groups.

And dual-rank is not dual-channel. Ranks share a command bus and a data bus; channels share nothing. The diagnostic question is whether adding one doubles the data wires: channels yes, ranks no.

14. What Comes Next

Chapter 5.5 is the level where the answer to that question becomes yes.

A channel is a largely independent interface path — its own command bus, its own data bus, its own scheduling. Two channels do not contend for anything, so they deliver genuinely parallel transfers rather than overlapped internal work, and there is no handoff between them because there is nothing to hand over.

The chapter's real content is the consequence: because channels share nothing, which channel a request lands on is decided entirely by the address, and that makes address mapping the whole ballgame. A workload whose addresses concentrate on one channel gets one channel's bandwidth, with the other sitting idle and nothing malfunctioning.

Return to Bank Groups for the classification whose mapping instinct is opposite to this chapter's, Sense Amplifiers for the ownership pattern at array scale, or DDR5 for the sub-channel split §1's device count follows from. The full path is on the DDR tutorials index.

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.