Skip to content
VLSI Mentor

DDR · Module 16

Bank-Level Parallelism

Sixteen banks do not give sixteen concurrent row openings. The sustained activate rate is bounded by the rolling activation window, and from verified figures that bound is the binding one in every device width and at every speed grade — never the bank count.

Chapter 5.2 already established what a bank is: an independently stateful command resource with its own open-row context, what it owns, what it still shares, and — in its §7 — that two banks can overlap useful work.

This chapter asks the question that leaves open, and the answer is quantitative:

How much concurrency do banks actually provide?

Not “can two banks overlap” — Chapter 5.2 settled that. How many row openings can a device sustain, and what is the binding limit? From verified figures the answer is sharper than expected: the limit is a rolling activation window, it binds in every device width and at every speed grade examined, and the bank count is never what constrains it.

1. Four Questions That Get Collapsed Into One

Before any arithmetic, a vocabulary problem. “Can these two operations happen together?” is not one question. It is four, and conflating them is the source of nearly every wrong belief about bank parallelism.

A — State independence. Does operation A change the architectural row state that B needs? Two accesses to different banks never do; two accesses to the same bank often do. Chapter 9.3 and 9.5 own this.

B — Resource independence. Do A and B need the same shared resource? Different banks still share one command bus and one data bus. Chapter 5.2 §3 owns this.

C — Timing legality. Does a timing relation constrain B after A even though they use different banks? This is the one people forget exists, and it is what §5 quantifies.

D — Policy. If several operations are legal, which should the controller choose? Chapter 16.2 sets this up and Module 17 owns it.

2. Three Forms of Serialization

Concurrency is bounded by three distinct mechanisms, and keeping them apart is what makes the arithmetic in §5 possible.

A — Same-bank state serialization. A row conflict forces PRECHARGE, then ACTIVATE, then a column command — three commands where a row hit needed one. Strictly sequential, owned by Chapter 9.5.

B — Shared-bus serialization. Sixteen banks do not create sixteen command buses. The command interface carries one command per cycle, so even sixteen perfectly independent banks with perfectly ready work are issued one at a time. The data bus is likewise singular, which Chapter 12.1 established occupies it for multiple transfer positions per column command.

C — Cross-bank timing serialization. Distinct banks, distinct rows, no shared state — and a timing relation still constrains the second command. tRRD spaces activates across banks; tFAW limits how many may occur in a rolling window; tCCD spaces column commands.

Form C is the subject of this chapter, because it is the only one that is neither obvious nor avoidable. Form A can be avoided by better mapping (Module 18); form B is a hard structural fact anyone can see. Form C is invisible, generation-specific, and computable — and the computation produces a surprise.

3. The Activate Budget — Verified Figures

Two constraints bound how often a row may be opened, and both are cross-bank.

Three things to notice before computing anything.

nRRD_S is constant at 4 CK for x4 and x8 across every grade, and rises from 5 to 9 for x16. nFAW rises steeply with grade and is dramatically wider on x16 — 48 cycles at DDR4-3200 against 34 for x8.

The cycle counts are device-width-dependent. That is unusual and it matters: the same speed grade gives a different activation budget depending on how wide the part is. §6 is about why.

nCCD_S is constant at 4 while nCCD_L rises to 8. Chapter 16.4 owns that comparison; it appears here only to make the point that the group relationship matters for column commands too.

4. Which Constraint Binds

Now the arithmetic, and it is simple enough to do by hand.

Two independent bounds on the sustained rate of row openings:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  tRRD bound :  at most 1 activate every nRRD_S cycles
                →  rate ≤ 1 / nRRD_S

  tFAW bound :  at most 4 activates in any window of nFAW cycles
                →  rate ≤ 4 / nFAW

The sustained rate is the smaller of the two, so the cycles per activate is the larger of nRRD_S and nFAW / 4:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  cycles per sustained activate  =  max( nRRD_S ,  nFAW / 4 )

Evaluate it for every width at DDR4-3200:

WidthBanksnRRD_SnFAW1/nRRD_S4/nFAWBindingCycles per activate
x4164160.2500.250tie4.00
x8164340.2500.118tFAW8.50
x1689480.1110.083tFAW12.00

That reframes what banks are for, and the reframing is the chapter's point.

Banks do not increase the rate at which rows can be opened. That rate is fixed by the activation window. What banks provide is somewhere for the opened rows to live independently — so that the four rows the window permits can be held open simultaneously, in different banks, serving column traffic concurrently, rather than contending for one row buffer.

Why the bank count is not the limit on row-opening concurrency. On the left, sixteen banks are shown as available independent row-state resources, all eligible. In the middle, two cross-bank timing constraints are applied: the activate-to-activate spacing permits one activation every four cycles for an eight-bit-wide part, and the rolling four-activate window permits four activations every thirty-four cycles. The smaller of the two rates binds, and the rolling window is the smaller, giving one activation every eight and a half cycles. On the right, the consequence is shown: at most four rows can be opened within any window, so the remaining twelve banks contribute independent state for those four rows to occupy rather than additional opening throughput. Below, the device width is shown changing the answer, with the sixteen-bit-wide part having both fewer banks and a wider window.16 banksall eligibletRRD_S1 per 4 CKtFAW4 per 34 CKSmaller rate bindsalways tFAW1 ACT / 8.5 CKsustained4 rows per windownot 16Banks hold rowsnot open them fasterx16: 8 banksnFAW 48 — worse12

5. Device Width Changes the Answer

§3's table has a feature worth dwelling on, because it is the kind of thing that makes a controller correct on one part and wrong on another.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  DDR4-3200      nRRD_S    nFAW    banks    cycles per ACT
    x4              4        16      16          4.00
    x8              4        34      16          8.50
    x16             9        48       8         12.00

Three widths, three different budgets, one speed grade. The x4 part sustains an activation every 4 cycles; the x16 part every 12 — three times slower, with half the banks.

And one further consequence, which connects to §4's reframing. The x16 part has 8 banks and a budget of one activation every 12 cycles. Four activations therefore occupy 48 cycles and use half its banks. A part with fewer banks is not proportionally less parallel — it is limited by the same four-activation count, just spread more thinly in time.

6. RTL — The Concurrency Budget

Collision check. Chapter 14.8 §5's activate_window_history holds the rolling-window timestamp queue and answers whether another activation fits. Chapter 14.7 §7's activate_spacing_resolver resolves the dual-term tRRD specification into cycle counts per group class. Chapter 4.5's bank_group_spacer enforces group-scoped spacing. Chapter 5.2's ddr_bank_state_table holds per-bank state.

All four are reused, and none of them computes §4's comparison. activate_window_history enforces the window; activate_spacing_resolver supplies a magnitude. Nothing asks which of the two constraints is binding, or reports the resulting sustained rate — and §4 established that the answer is both surprising and stable.

The engineering problem. Given the two cross-bank activation bounds, report the binding one, the resulting sustained rate, and how much of the budget is currently unused — so that a controller can tell “no bank is ready” apart from “banks are ready and the budget is exhausted”, which are different situations with different remedies.

Classification: controller-side budget accounting over cross-bank constraints. Counts cycles and compares integers. No physical modelling.

What it does not model. No array internals, no analog behaviour, no activation current — §5 is explicit that the cause of the window is not asserted here. No bank state (Chapter 5.2 owns it), no row classification (Chapter 9.3), no selection policy (Module 17). It reports a budget and chooses nothing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────
//  activate_concurrency_budget
//
//  CLASSIFICATION
//    Controller-side budget accounting over CROSS-BANK activation
//    constraints. Counts cycles, compares integers.
//
//  WHAT IT MODELS
//    §4's comparison, made explicit in hardware:
//      tRRD bound :  1 activate per RRD_CYCLES
//      tFAW bound :  FAW_COUNT activates per FAW_CYCLES
//      binding    :  whichever yields the SMALLER sustained rate
//    and the distinction §6 argues a controller needs: is progress
//    blocked because no bank is ready, or because the shared
//    activation budget is exhausted while banks ARE ready?
//
//  WHAT IT DOES NOT MODEL
//    No array internals, no analog behaviour, and NO activation
//    current -- §5 declines to assert the cause of the window because
//    the source publishes numbers and not a reason.
//    No per-bank state (Chapter 5.2's ddr_bank_state_table owns it),
//    no row classification (Chapter 9.3's row_request_classifier),
//    no selection policy (Module 17). It reports; it never chooses.
//
//  RELATIONSHIP TO EXISTING RTL
//    Chapter 14.8's activate_window_history ENFORCES the rolling
//    window and is the right block for that; Chapter 14.7's
//    activate_spacing_resolver SUPPLIES the tRRD magnitude. This block
//    consumes both kinds of information and computes which one is
//    binding -- a question neither asks. A design would instantiate
//    the window tracker for enforcement and this for observability.
//
//  THE COMPARISON -- why it is a cross-multiply
//    "1/RRD versus FAW_COUNT/FAW" compared directly needs division.
//    Cross-multiplying gives FAW_COUNT * RRD_CYCLES versus FAW_CYCLES,
//    which is exact in integers and needs no rounding decision. This
//    is the same restructuring habit as Chapter 15.1 §6's
//    multiply-and-compare.
//
//  CONVENTION (Chapter 13.2 §6)
//    An activate accepted on cycle N with spacing D forbids the next
//    activate on cycles N .. N+D-1 and permits it from N+D. One
//    command per cycle on the command bus, so the spacing countdown
//    loads D-1.
//
//  SIMULTANEITY
//    activate accepted while spacing outstanding -> REPORTED on
//        spacing_violated, never suppressed. This block advises.
//    window full and spacing clear -> budget_exhausted asserts and
//        spacing_ok stays high; the two are published SEPARATELY
//        because §12's discriminator depends on telling them apart.
//    reset -> budget clear, no history. The device's initialisation
//        sequence owns establishing a real starting state.
//
//  GENERATION SCOPE
//    Parameters carry §3's VERIFIED DDR4 figures, which are
//    DEVICE-WIDTH and PAGE-SIZE dependent (§5). They are parameters,
//    never constants, because a table copied across widths is
//    §12's fifth failure.
// ─────────────────────────────────────────────────────────────────────
module activate_concurrency_budget #(
  // §3's nRRD_S, in cycles. Width- and grade-dependent.
  parameter int RRD_CYCLES = 4,
  // §3's rolling window: FAW_COUNT activates per FAW_CYCLES.
  parameter int FAW_COUNT  = 4,
  parameter int FAW_CYCLES = 34,
  // Banks present, for the observability comparison of §4. Does NOT
  // participate in the budget -- that is the chapter's whole point.
  parameter int NUM_BANKS  = 16,
  parameter int SPC_W = (RRD_CYCLES <= 1) ? 1 : $clog2(RRD_CYCLES + 1),
  parameter int CNT_W = (FAW_COUNT  <= 1) ? 1 : $clog2(FAW_COUNT + 1),
  parameter int TS_W  = $clog2(FAW_CYCLES + 2)
) (
  input  logic                clk,
  input  logic                rst_n,

  // ── An ACTIVATE was accepted this cycle, to any bank.
  input  logic                act_accepted,
  // ── At least one bank has work that is otherwise ready. AN INPUT:
  //    Chapter 16.2 computes this and Chapter 5.2 owns the state it
  //    comes from. This block cannot infer it.
  input  logic                a_bank_is_ready,

  // ── Spacing side: is the tRRD-style separation satisfied?
  output logic                spacing_ok,
  output logic [SPC_W-1:0]    spacing_remaining,

  // ── Window side: how much of the rolling budget is in use?
  output logic [CNT_W-1:0]    window_used,
  output logic                budget_exhausted,
  output logic [TS_W-1:0]     cycles_until_credit,

  // ── The combined verdict for a candidate ACTIVATE.
  output logic                activate_permitted,

  // ── §4's observability: WHICH constraint is binding, computed once
  //    at elaboration. 0 = tRRD binds, 1 = tFAW binds, 2 = exactly
  //    balanced. The tie code matters: for x4 parts the two are equal
  //    (nFAW == FAW_COUNT * nRRD_S) and reporting either alone would
  //    mislead a reader trying to decide what to change.
  output logic [1:0]          binding_constraint,

  // ── THE distinction of §6: banks are ready and the shared budget
  //    is what is stopping progress. A controller told only "blocked"
  //    cannot tell this from "nothing to do".
  output logic                blocked_by_budget,

  // ── An ACTIVATE accepted while spacing was outstanding. Reported,
  //    never suppressed.
  output logic                spacing_violated
);

  // ── Elaboration guards.
  if (RRD_CYCLES < 1) begin : g_rrd
    initial $fatal(1, "activate_concurrency_budget: RRD_CYCLES must be >= 1");
  end
  if (FAW_COUNT < 1) begin : g_cnt
    initial $fatal(1, "activate_concurrency_budget: FAW_COUNT must be >= 1");
  end
  if (FAW_CYCLES < 1) begin : g_faw
    initial $fatal(1, "activate_concurrency_budget: FAW_CYCLES must be >= 1");
  end
  if (NUM_BANKS < 1) begin : g_banks
    initial $fatal(1, "activate_concurrency_budget: NUM_BANKS must be >= 1");
  end
  // A window that cannot hold the permitted count is a contradictory
  // configuration: it would forbid the very activations it permits.
  if (FAW_CYCLES < FAW_COUNT) begin : g_consistent
    initial $fatal(1, "activate_concurrency_budget: FAW_CYCLES must be >= FAW_COUNT");
  end

  // ── §4's comparison, cross-multiplied so there is no division.
  //    rate_rrd = 1/RRD, rate_faw = COUNT/FAW.
  //    rate_rrd < rate_faw  <=>  FAW < COUNT * RRD.
  localparam int RRD_SCALED = FAW_COUNT * RRD_CYCLES;
  localparam logic [1:0] BIND =
      (FAW_CYCLES <  RRD_SCALED) ? 2'd0    // tRRD binds
    : (FAW_CYCLES >  RRD_SCALED) ? 2'd1    // tFAW binds
    :                              2'd2;   // exactly balanced

  // ── Spacing countdown.
  logic [SPC_W-1:0] spc;

  // ── Rolling window, as a timestamp queue. This deliberately mirrors
  //    Chapter 14.8's structure rather than inventing a new one: the
  //    storage is proportional to the COUNT, not the window length,
  //    which is that chapter's insight and is reused here.
  logic [TS_W-1:0] ts   [FAW_COUNT];
  logic [CNT_W-1:0] occ;
  logic [TS_W-1:0]  now;
  logic             retiring;
  logic [CNT_W-1:0] occ_eff;
  logic [TS_W-1:0]  oldest_age;

  // Circular pointers.
  localparam int PTR_W = (FAW_COUNT <= 1) ? 1 : $clog2(FAW_COUNT);
  logic [PTR_W-1:0] head, tail;

  always_comb begin
    binding_constraint = BIND;

    spacing_remaining = spc;
    spacing_ok        = (spc == '0);

    // Wrap-tolerant age of the oldest counted activation, per
    // Chapter 13.3 §7's signed-difference discipline.
    oldest_age = (occ != '0) ? (now - ts[tail]) : '0;
    retiring   = (occ != '0)
              && ($signed(now - ts[tail]) >= $signed(TS_W'(FAW_CYCLES)));
    occ_eff    = retiring ? (occ - CNT_W'(1)) : occ;

    window_used      = occ_eff;
    budget_exhausted = (occ_eff >= CNT_W'(FAW_COUNT));

    cycles_until_credit = budget_exhausted
                        ? (TS_W'(FAW_CYCLES) - oldest_age)
                        : '0;

    activate_permitted = spacing_ok && !budget_exhausted;

    // §6's distinction. Banks are ready; the shared budget is the
    // obstacle. Published separately from activate_permitted because
    // a consumer needs the REASON, not just the verdict.
    blocked_by_budget = a_bank_is_ready && !activate_permitted;

    spacing_violated = act_accepted && !spacing_ok;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      now  <= '0;
      spc  <= '0;
      occ  <= '0;
      head <= '0;
      tail <= '0;
      for (int unsigned i = 0; i < FAW_COUNT; i++) begin
        ts[i] <= '0;
      end
    end else begin
      now <= now + 1'b1;

      // Spacing: age first, then arm, so a fresh activate is not
      // decremented on its own arming cycle.
      if (spc != '0) begin
        spc <= spc - 1'b1;
      end
      if (act_accepted) begin
        spc <= (RRD_CYCLES >= 1) ? SPC_W'(RRD_CYCLES - 1) : '0;
      end

      // Window: retire the aged-out oldest, push the new activation.
      if (retiring) begin
        tail <= tail + 1'b1;
      end
      if (act_accepted && !budget_exhausted) begin
        ts[head] <= now;
        head     <= head + 1'b1;
      end

      // Single occupancy update over all four combinations, so the
      // retire and push paths cannot disagree.
      case ({retiring, (act_accepted && !budget_exhausted)})
        2'b10:   occ <= occ - CNT_W'(1);
        2'b01:   occ <= occ + CNT_W'(1);
        default: occ <= occ;
      endcase
    end
  end

endmodule

Interface contract. a_bank_is_ready is an input — this block has no bank state and cannot infer readiness; Chapter 16.2 computes it from Chapter 5.2's state. activate_permitted is the verdict and blocked_by_budget is the reason, published separately because §12's discriminator turns entirely on telling “nothing ready” from “ready and budgeted out”.

Parameter contract. All four carry §3's verified figures and are width- and grade-dependent. NUM_BANKS is present only for observability and deliberately does not participate in the budget — which is §4's result expressed in the module's structure. FAW_CYCLES >= FAW_COUNT is enforced, since a window shorter than the count it permits is self-contradictory.

Why the binding constraint is a localparam. FAW_COUNT × RRD_CYCLES against FAW_CYCLES is a comparison of constants, so it is resolved at elaboration and costs nothing at runtime. The tie code matters: §4 showed x4 parts sit exactly on it, and reporting either constraint alone would mislead a reader deciding what to change — for a tie, both must improve together.

Corner cases. FAW_COUNT == 1: the window degenerates to a spacing of FAW_CYCLES, and PTR_W is 1 by the guard. RRD_CYCLES == 1: spacing forbids nothing observable, since the arming cycle cannot host a competing command. FAW_CYCLES == RRD_SCALED: the tie, reported as code 2 — the x4 case. NUM_BANKS == 1: legal, and the configuration in which no cross-bank concurrency exists at all, so blocked_by_budget becomes the only interesting output. An activate while the budget is exhausted: not pushed, and the spacing still arms — deliberate, because the command was accepted somewhere even if this block would have refused it.

Synthesis implications. One spacing counter, FAW_COUNT timestamps, two pointers, an occupancy counter, one subtractor and two comparators. For a four-activation window with 6-bit timestamps that is roughly 35 flops. The comparison that produces §4's headline costs nothing — it is constant folding.

Failure modes. Omitting the window and enforcing only spacing — permits activations at 1/nRRD_S where §4 shows the true bound is 4/nFAW, which at x8 DDR4-3200 is more than double the legal rate. Copying an x8 parameter table onto an x16 part — §5's violation on every fourth activation. Folding blocked_by_budget into a single busy signal — destroys the discriminator that makes §12 tractable. Unsigned age comparisonChapter 13.3 §11's wraparound failure, correct until the counter wraps.

7. The Budget, in Cycles

Registry annotation for this chapter carries a diagram and no waveform, so the budget is shown as a solved table — which suits it, since the interesting quantity is a running comparison.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  RRD = 2, FAW = 4 per 12 cycles, 8 banks.
  binding: FAW_CYCLES 12 > FAW_COUNT*RRD 8  →  tFAW binds
  sustained: 4 per 12 = 1 ACT per 3 cycles (not per 2)

  cyc  act  spc  win  exhausted  ready  permitted  blocked_by_budget
  ────────────────────────────────────────────────────────────────────
   0   ACT   -    1       no       Y       yes            no
   1    ·    1    1       no       Y        no            YES  ← spacing
   2   ACT   0    2       no       Y       yes            no
   3    ·    1    2       no       Y        no            YES  ← spacing
   4   ACT   0    3       no       Y       yes            no
   5    ·    1    3       no       Y        no            YES
   6   ACT   0    4      YES      Y       yes            no
   7    ·    1    4      YES      Y        no            YES  ← BUDGET
   8    ·    0    4      YES      Y        no            YES  ← BUDGET
  11    ·    0    4      YES      Y        no            YES  ← BUDGET
  12    ·    0    3       no       Y       yes            no   ← cyc-0 aged out
  ────────────────────────────────────────────────────────────────────
  4 activations in 12 cycles. 8 banks were ready throughout.

Three observations, each a §8 property.

Cycles 7 through 11 are the chapter. Eight banks ready, spacing satisfied, and no activation permitted — because the rolling budget is full. A controller reporting only “blocked” here would send a debugger to look at bank state, which is entirely healthy.

The sustained rate is 1 per 3 cycles, not 1 per 2. Spacing alone would have permitted six activations in 12 cycles; the window permits four. Enforcing only spacing over-permits by 50 % in this configuration — and by more than 100 % at the verified x8 DDR4-3200 figures.

Cycle 12 recovers with nothing happening. The activation from cycle 0 aged out of the window, which is Chapter 14.8 §3's ageing semantics — permission returns from the passage of time rather than from any action.

8. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── P1. THE safety property: an activate is permitted only when BOTH
//    cross-bank constraints allow it. Catches the §6 headline failure
//    -- enforcing spacing and omitting the window, which over-permits
//    by more than 100 % at the verified x8 DDR4-3200 figures.
property p_permitted_requires_both;
  @(posedge clk) disable iff (!rst_n)
    activate_permitted |-> (spacing_ok && !budget_exhausted);
endproperty
a_permitted_requires_both: assert property (p_permitted_requires_both);

// ── P2. §6's distinction is well formed: blocked_by_budget asserts
//    exactly when work is ready and the shared constraints refuse it.
//    Catches a design that folds the reason into the verdict, which
//    destroys §12's discriminator.
property p_blocked_reason_is_exact;
  @(posedge clk) disable iff (!rst_n)
    blocked_by_budget == (a_bank_is_ready && !activate_permitted);
endproperty
a_blocked_reason_is_exact: assert property (p_blocked_reason_is_exact);

// ── P3. The window never holds more than the permitted count. The
//    rolling-window property, stated over the OCCUPANCY the block
//    reports so a consumer's view is the one being checked.
property p_window_never_over_count;
  @(posedge clk) disable iff (!rst_n)
    (window_used <= CNT_W'(FAW_COUNT));
endproperty
a_window_never_over_count: assert property (p_window_never_over_count);

// ── P4. The binding-constraint report matches §4's cross-multiplied
//    comparison, INCLUDING the tie. The tie is not hypothetical --
//    §4 showed x4 parts sit exactly on it, and reporting one
//    constraint there would send a reader to change something that
//    cannot help alone.
property p_binding_report_is_correct;
  @(posedge clk) disable iff (!rst_n)
    ( ((FAW_CYCLES <  FAW_COUNT * RRD_CYCLES) |-> (binding_constraint == 2'd0))
    and ((FAW_CYCLES >  FAW_COUNT * RRD_CYCLES) |-> (binding_constraint == 2'd1))
    and ((FAW_CYCLES == FAW_COUNT * RRD_CYCLES) |-> (binding_constraint == 2'd2)) );
endproperty
a_binding_report_is_correct: assert property (p_binding_report_is_correct);

// ── C1. The interesting states are REACHED. The budget-blocked cover
//    is the one people omit, and it is the state the chapter exists
//    for -- a run that never fills the window has only tested spacing.
c_budget_blocked: cover property (@(posedge clk) disable iff (!rst_n)
                                    blocked_by_budget && spacing_ok);
c_window_full:    cover property (@(posedge clk) disable iff (!rst_n)
                                    budget_exhausted);

What these prove. That permission requires both constraints; that the blocked-reason output is exactly the conjunction §6 defines; that the window respects its count; and that the binding report matches the comparison including the tie.

What these do not prove. Three gaps, and the second is the chapter's most important.

Nothing proves the parameters match the device. They are §3's verified figures, and §5 established they are width- and page-size dependent. A block configured from an x8 table on an x16 part satisfies every property and permits activations at 8.5-cycle spacing where 12 is required. That is a datasheet-and-part-number check no simulation discharges.

Nothing proves a_bank_is_ready is honest. It is an input, and a design that never asserts it makes blocked_by_budget permanently low — so the monitor reports that the budget never obstructed anything, which is the most reassuring possible wrong answer. C1's first cover is the only thing that says whether it was driven.

And nothing here is a performance measurement. The block bounds a rate; whether the workload wanted that rate is Module 23's question.

Vacuity. P4's three conjuncts are mutually exclusive by construction, so exactly one is non-vacuous per configuration — which is fine and worth knowing when reading a coverage report. C1's covers require the window to actually fill.

9. DV — Reconstructing the Budget Independently

Invert the representation. The block holds a timestamp queue and a countdown. A checker should hold a plain list of every observed activate cycle and, on each candidate, count how many fall within the trailing window and measure the gap to the most recent — a recount rather than an accumulation, so it cannot share a pointer or ageing bug. Chapter 14.8 §10 made the same argument.

Derive the parameters from the part, not the design. This is the chapter-specific obligation and §8's first limitation is why: the figures depend on device width and page size, so a checker should select them from the part's own organization — which it can obtain from the device's identifying information — rather than from the design's parameters. A design and a checker sharing a wrong table agree perfectly.

Report the budget, not just the violation. A violation report that names the constraint and the remaining credit is actionable in a way a bare timestamp is not.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  CROSS-BANK ACTIVATION VIOLATION
    generation        : DDR4
    part organisation : x8, 1KB page, 16 banks, 4 bank groups
    grade             : DDR4-3200                 [nRRD_S=4, nFAW=34]
    binding constraint: tFAW        (34 > 4 × 4 = 16)
    ─────────────────────────────────────────────────────────────────
    candidate ACT     : bank 11, cycle 4188
    spacing since last: 5 cycles     → tRRD_S satisfied (needs 4)
    window contents   : cycles 4157, 4166, 4175, 4183   → 4 of 4 USED
    oldest ages out at: cycle 4191
    earliest legal    : cycle 4191
    early by          : 3 cycles
    banks ready       : 11, 3, 14     ← three banks were eligible
    root question     : was the design enforcing only tRRD_S?

The two lines to steal are binding constraint with its arithmetic, and banks ready. The first pre-empts the assumption that spacing was the issue — it shows the other constraint is the binding one and shows the multiplication. The second is the diagnostic that makes §4's lesson concrete: three banks were eligible and none could be used, so a designer cannot conclude that more banks would have helped.

10. Debugging

Symptom. Traffic spread across many banks still achieves far less concurrency than the bank count suggests.

Candidate mechanismEvidenceDiscriminator
The rolling window is the binding limit, and correctly soActivation rate matches FAW_COUNT / FAW_CYCLES; banks readyThe decisive check: compute 4 / nFAW for the part and grade and compare against the measured rate. If they match, nothing is broken — §4 says this is the expected ceiling, and more banks will not help.
Only spacing enforced, window omittedRate matches 1 / nRRD_S; violations under sustained load§6's headline failure. At x8 DDR4-3200 that is 4 cycles against a required 8.5 — over-permitting by more than 100 %.
Parameter table from the wrong device widthRate plausible; violations on a different part with no code change§5. Compare the configured nFAW against the part's width — 16 / 34 / 48 at DDR4-3200 for x4 / x8 / x16.
Per-bank timer used for a cross-bank constraintRate scales with bank count, which it should notChapter 13.3 §4's Bug 2. tRRD and tFAW are not per-bank; a per-bank implementation permits N times too much.
Cross-bank timer used per rank when it should be per bankDifferent banks serialise completelyThe mirror. Chapter 5.2 §4's failure. Run single-bank against all-bank traffic.
Blocked reason not publishedCannot tell "nothing ready" from "budget exhausted"§6. Without blocked_by_budget the investigation starts at bank state, which is healthy.
Command bus saturationRate matches one command per cycle across all types§2's form B. Count total commands, not just activates.

The discriminator that defines this chapter is whether the measured activation rate matches FAW_COUNT / FAW_CYCLES. If it does, the design is correct and the ceiling is the device's — and that is a genuinely useful negative result, because it redirects effort from the controller to the access pattern or the part choice. If the rate is higher, the window is not being enforced, which is a violation. If it is lower, look at the command bus or at bank readiness.

The second discriminator is blocked_by_budget. It separates the two situations that look identical from outside — no work available versus work available and budgeted out — and they lead to opposite conclusions about whether more banks or better mapping would help.

Responsible layer. If the rate matches the window bound and the workload needs more, the answer is not in the controller. It is in reducing how many row openings the workload needs — which is row locality (Chapter 16.3) and address mapping (Module 18).

11. Common Misconceptions

“Each bank is an independent memory channel.” Tempting because banks are described as independent and channels are the other thing that is independent. Why it is wrong: Chapter 5.2 §2 settles the distinction — a bank owns row state and shares the command bus, the data bus and the activation budget. A channel owns its own interface. Consequence: a bandwidth model that multiplies by the bank count. Replacement model: banks multiply state, channels multiply interface. Debugging clue: a capacity estimate that is off by roughly the bank count.

“Different banks can always execute commands simultaneously.” Tempting because state independence is real and is the first thing you learn about banks. Why it is wrong: §2's form B — one command bus, one command per cycle. Nothing executes simultaneously at the interface; operations overlap internally after being issued sequentially. Consequence: a design expecting two commands in one cycle. Replacement model: sequential issue, overlapping execution. Debugging clue: a scheduler that tries to select more than one command per cycle.

“Different bank means next-cycle legal.” Tempting because it follows from state independence and it is the cheap comparison to implement. Why it is wrong: §1's question C. tRRD spaces cross-bank activates and tFAW bounds them in a window — both are cross-bank constraints that a bank comparison cannot see. Consequence: a controller that replaces timing legality with bank != previous_bank, which is the most common bank-parallelism bug and passes every functional test. Replacement model: state legality and timing legality are separate filters, and Chapter 16.2 applies both. Debugging clue: activates issued at one-cycle spacing under load.

“More banks mean proportionally more parallelism.” Tempting because that is the obvious reading of the word parallelism. Why it is wrong: §4 — the sustained activation rate is 4 / nFAW, which does not contain the bank count at all. A DDR4-3200 x8 part with 16 banks sustains one row opening every 8.5 cycles. Consequence: a part or mapping chosen for bank count in the expectation of throughput that the window forbids. Replacement model: banks provide independent places for the four permitted rows, not more openings. Debugging clue: activation throughput unchanged when the mapping spreads traffic across more banks.

“tFAW disappears if the activates target different banks.” Tempting because most timing constraints do relax across banks, and the four-activate name suggests something about banks. Why it is wrong: Chapter 14.8 established tFAW is a rank-wide rolling window over events, not a bank-scoped constraint. Different banks is exactly the case it was written for. Consequence: a design that applies tFAW per bank, permitting N times the legal activation rate. Replacement model: a density limit over a shared resource, counted by events. Debugging clue: activation rate scaling with bank count.

“tRRD only matters for the same bank.” Tempting because the name pairs row with row, and same-bank activate-to-activate is the obvious conflict. Why it is wrong: Chapter 14.7 §2 — same-bank activate-to-activate is tRC; tRRD is specifically the cross-bank constraint. Consequence: cross-bank activates left unconstrained, which is the traffic tRRD exists for. Replacement model: tRC same bank, tRRD different bank, tFAW rolling across all. Debugging clue: violations on activates to banks that had never been activated.

“Bank-level parallelism means several data transfers happen at once.” Tempting because parallelism and bandwidth are casually equated. Why it is wrong: §2's form B — there is one data bus, and Chapter 12.1 established a single column command occupies it for several transfer positions. Banks do not multiply the data path. Consequence: a bandwidth figure computed as bus width times bank count. Replacement model: one data bus, time-multiplexed among banks. Debugging clue: a peak-bandwidth number that exceeds bus width times transfer rate.

“The controller needs to know DRAM internals to exploit banks.” Tempting because the constraints originate inside the device. Why it is wrong: §5 declines to assert the cause of the activation window precisely because the datasheet publishes numbers and not a mechanism — and a controller needs only the numbers. Chapter 13.2 §1's boundary applies unchanged. Consequence: speculation substituting for a table, and RTL claiming to model internals. Replacement model: the specification publishes bounds; the controller counts. Debugging clue: a design decision justified by an internal mechanism nobody can cite.

12. Interview Reasoning

“Why does DRAM have multiple banks?” Because an access needs a row to be open, and a single row buffer would force every access to a different row to close and reopen. Banks give independent row state, so several rows can be open at once and accesses to different banks avoid that serialization. The part worth adding is what banks do not provide: they do not give more command bus, more data bus, or a higher rate of row openings — that rate is bounded by a rolling activation window that contains no bank term at all.

“What does bank-level parallelism actually parallelize?” Internal resource state, not the interface. Commands are still issued one per cycle on a single command bus and data still crosses a single data bus; what overlaps is the execution of operations after issue — one bank's row opening progressing while another bank serves column traffic. So the right phrase is overlapping execution with sequential issue.

“Can two activates to different banks always be issued back to back?” No, and there are two separate reasons. Activate-to-activate spacing applies across banks — for a verified DDR4-3200 x8 part, four cycles minimum. And a rolling window limits four activations per window, which at that part is 34 cycles. Working the comparison, the window is the binding one: four per 34 cycles is one per 8.5, well below the one per four that spacing alone would allow. So back-to-back is not legal, and even the spacing limit is not the real ceiling.

“With sixteen banks, how many rows can you open concurrently?” Four, within any activation window — because that is what the window permits, and it is the binding constraint. The sixteen banks matter for where those four rows live: they can be four different banks whose column traffic then proceeds independently. The number that surprises people is the rate — one row opening every 8.5 cycles at DDR4-3200 x8 — and it does not improve with more banks.

“How does device width change this?” Substantially, and it is a real portability trap. At DDR4-3200 the activation window is 16 cycles for x4, 34 for x8 and 48 for x16, while the permitted count stays at four — so the sustained rate is one activation per 4, 8.5 and 12 cycles respectively. The x16 part is three times slower at opening rows than the x4 and has half the banks. A controller configured from one width's table and deployed on another over-permits, and nothing refuses it.

“What bug occurs if a cross-bank constraint is implemented as a per-bank timer?” It permits roughly N times the legal rate, where N is the bank count, because each bank's timer knows only its own history while the constraint governs a shared resource. The signature is memorable: the activation rate scales with how many banks the traffic touches, so the design fails more the better the mapping spreads work. The mirror bug — a genuinely per-bank constraint implemented rank-globally — is safe and serializes independent banks, and is caught by comparing single-bank against all-bank throughput.

“Your system uses many banks and gets far less concurrency than expected. Is it broken?” Compute the window bound first — permitted count divided by window length — and compare against the measured activation rate. If they match, nothing is broken; you have found the device's ceiling, and the remedy is to need fewer row openings rather than to schedule better. If the measured rate is higher, the window is not being enforced and that is a violation. The output that makes this quick is a blocked-reason signal separating "no bank ready" from "banks ready and budget exhausted" — they look identical from outside and lead to opposite conclusions.

13. Engineering Exercises

1. Compute the budget across widths. Using §3's verified DDR4-3200 figures, give the sustained cycles-per-activation for x4, x8 and x16, and name the binding constraint in each case.

Worked: x4 — max(4, 16/4) = max(4, 4) = 4.00, a tie. x8 — max(4, 34/4) = max(4, 8.5) = 8.50, tFAW. x16 — max(9, 48/4) = max(9, 12) = 12.00, tFAW. The x4 tie is the instructive case: there, and only there, reducing either constraint alone would not help.

2. Find the over-permitting design. A controller enforces only activate-to-activate spacing on an x8 DDR4-3200 part. By what factor does it over-permit, and when does it violate?

Worked: it permits one activation per 4 cycles where 8.5 is required — over-permitting by 2.125×. It violates only under sustained activation load, because a burst of up to four activations at 4-cycle spacing fits the window; the fifth is the first violation. That is why the bug survives short tests: it needs five consecutive activations to appear.

3. Trace the window. With FAW_COUNT 4 and FAW_CYCLES 12, activations occur at cycles 0, 3, 6 and 9. When is the fifth legal, and which activation determines it?

Worked: the window is full from cycle 9. The oldest activation is at cycle 0, so it ages out when its age reaches 12 — at cycle 12. The fifth activation is legal at 12, determined by the oldest and not the most recent. Note that spacing of 2 was satisfied throughout and is irrelevant to the answer.

4. Diagnose a width port. A controller correct on x8 is deployed on x16 at DDR4-3200 with the same parameters. Give both errors and say which is worse.

Worked: the spacing is configured at 4 where 9 is required, and the window at 34 where 48 is required. Both over-permit. The window error is worse in effect: spacing is not the binding constraint on either part, so the spacing error is masked, while the window error directly sets the rate — permitting one activation per 8.5 cycles against a required 12, a 41 % over-permit that applies continuously rather than only in bursts.

5. Separate the four questions. For each pair, say which of §1's questions A–D are engaged: (a) READ bank 0 then READ bank 0, same row; (b) ACTIVATE bank 0 then ACTIVATE bank 5; (c) two column commands to different banks; (d) two requests both legal, one a row hit.

Worked: (a) A — same bank, same row, so state-independent in the sense that the second needs no transition; the row hit is Chapter 9.3's. (b) B and C — one command bus, plus tRRD and tFAW. (c) B and C — the data bus and tCCD. (d) D only — both are legal, so what remains is policy, which is Module 17's. Recognising that (d) engages no legality question is the point.

6. Argue against the observability output. §6 publishes blocked_by_budget separately from activate_permitted. Make the case that one busy signal is sufficient, then rebut using §10.

7. Size the trap. Explain why a design that enforces the window correctly but takes FAW_COUNT from a different generation would be dangerous, and why no property in §8 catches it.

14. Summary

Chapter 5.2 established what a bank owns and that two banks can overlap. This chapter asked how much concurrency banks actually provide, and answered it with verified arithmetic.

Four questions must stay separate: state independence, resource independence, timing legality, and policy. Different banks answer the first yes and the second and third no — and a controller that replaces timing legality with bank != previous_bank has answered the wrong question with a cheap comparison that passes every functional test.

Three forms of serialization bound concurrency: same-bank state transitions, the single command and data buses, and cross-bank timing — the only one that is neither obvious nor avoidable, and the one this chapter quantifies.

The central result. Two cross-bank bounds govern row openings: spacing permits one activation per nRRD_S cycles, and the rolling window permits four per nFAW. The sustained rate is the smaller, so cycles per activation is max(nRRD_S, nFAW/4). Evaluated against §3's verified figures across all three device widths and all seven speed grades, the rolling window is the binding constraint in every case — and for x4 parts the two sit exactly on a tie. The bank count appears nowhere in the bound.

At DDR4-3200 that gives one row opening every 4.00, 8.50 and 12.00 cycles for x4, x8 and x16 — with 16, 16 and 8 banks respectively. So banks do not increase the rate at which rows can be opened. They provide independent places for the four permitted rows to live, so that those four can serve column traffic concurrently instead of contending for one row buffer. That is what bank-level parallelism is.

Device width changes the answer by a factor of three at one speed grade, which makes a parameter table copied across widths a violation that nothing refuses.

activate_concurrency_budget computes the comparison — a cross-multiply, resolved at elaboration, so §4's headline costs no gates — reports the binding constraint including the tie, and publishes blocked_by_budget separately from the verdict. That separation is what lets a debugger tell “nothing ready” from “ready and budgeted out”, which are indistinguishable from outside and lead to opposite conclusions.

And the discriminator generalises: measure the activation rate against FAW_COUNT / FAW_CYCLES. Matching means the design is correct and the ceiling is the device's — a useful negative result. Higher means the window is not enforced.

15. What Comes Next

This chapter bounded how much concurrency exists. It said nothing about which of several ready banks a controller should use, and deliberately treated a_bank_is_ready as an input it could not compute.

Chapter 16.2 computes it, and the discipline it introduces is the one §1's four questions were building toward: legality is determined before policy, and the two must not be collapsed. Chapter 13.1's command_legality_layers evaluated one candidate against three filters. A bank-aware controller must evaluate every bank's candidate at once and produce a legal-candidate vector — a different shape, and the thing that makes bank awareness possible at all.

It is also where §11's third misconception gets taken apart properly, because the vector makes visible exactly what a bank comparison cannot see.

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.