Skip to content
VLSI Mentor

DDR · Module 14

tCCD — CAS-to-CAS Delay

The first parameter that is not bank-local. Column spacing is constrained by resources every bank shares, and DDR4 splits it by bank group — so a per-bank state machine cannot enforce it.

Five chapters, five bank-local obligations. Each held per bank, each saying nothing about any other bank, and each correct in isolation.

tCCD is the first one that breaks the pattern, and the break matters more than the parameter:

Column-to-column spacing is constrained by resources that every bank shares. No amount of per-bank state can enforce it.

Chapter 13.3 §4 predicted this failure in the abstract and called it Bug 2 — fast and wrong, passing single-bank tests and failing more as parallelism grows. tCCD is where it becomes concrete, and DDR4 adds a second twist: the requirement is not one value but two, chosen by whether the two column commands fall in the same bank group.

1. The Obligation, in Five Parts

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  PARAMETER          tCCD   (DDR4: tCCD_S and tCCD_L — see §4)

  1 triggering event   a column command accepted
  2 constrained event  the NEXT column command
  3 resource           SHARED — not a bank.  See §2.
                       DDR4 distinguishes same-bank-group from
                       different-bank-group.  See §4.
  4 magnitude          in nCK  (a cycle count, not nanoseconds) — §6
  5 sense              MINIMUM SEPARATION
  ─────────────────────────────────────────────────────────────────
  consequence        next column command not before
                     prev_col_cycle + resolved(tCCD for that pair)

Part 3 is the whole chapter. Compare it with the five parameters before it:

ParameterResourceHeld in hardware as
tRCDbank Bone countdown per bank
tRPbank Bone countdown per bank
tRASbank Bone countdown per bank
tRCbank Bone countdown per bank
tWRbank Bone countdown per bank
tCCDshared / bank groupnot per bank — §7

The follow-up questions:

What re-arms it? Every column command, regardless of bank. That is the scope statement in operational form: an access to bank 0 arms an obligation that constrains an access to bank 12.

What can dominate it? tRCD, for a column command to a freshly activated bank. A candidate column command has both obligations in its applicable set — one bank-local, one shared — and Chapter 13.4 §3's trace showed them tying at cycle 6, which was chosen precisely because a tie hides a missing obligation.

Cycle or absolute-time component? Cycles — and §6 explains why this parameter is different from the four before it in exactly that respect.

Does generation matter? Yes, substantially. §4 and §5.

2. Why the Resource Is Not a Bank

Start from what a column command actually asks for.

Chapter 12.1 established that a single column command moves a burst of transfers across the data interface, occupying it for several clock cycles. Chapter 6.x's dq_bus_ownership made the point in hardware: the data bus has one owner at a time, and every bank shares it.

So consider two column commands to different banks, issued back to back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  cycle 10   READ bank 0      → data occupies the bus, cycles 10+CL … +burst
  cycle 11   READ bank 7      → data would occupy the bus, cycles 11+CL … +burst

Both banks are independently ready. Each bank's own tRCD expired long ago. Every bank-local obligation is satisfied. And the two data bursts overlap on wires that only one of them can drive.

And note what kind of failure this produces. Per-bank state for a shared obligation is fast and wrong. It permits too much, so it violates. And its failure rate rises with bank parallelism — single-bank traffic never exposes it, because with one bank in use that bank's counter accidentally holds the complete history of the shared resource. §11's discriminator is exactly this.

There is a second contended resource worth naming, because it explains why some column spacing exists even where data does not overlap. The command path carries one command per cycle, and the device's internal column-access machinery has finite throughput. A column command is not free at the command interface either. That is why column spacing is not simply “whatever keeps the data bursts apart” — it is a published requirement independent of the burst length you happen to be using.

3. Bank Groups — Just Enough Topology

Module 16 owns bank-group architecture. This chapter needs exactly one fact from it.

DDR4 organises banks into groups, and two accesses land differently depending on whether they fall in the same group or different groups. Accesses to banks in different groups can be spaced more closely than accesses to banks in the same group, because some resource is shared within a group and not across groups.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ┌──── bank group 0 ────┐   ┌──── bank group 1 ────┐
  │  bank 0   bank 1     │   │  bank 4   bank 5     │
  │  bank 2   bank 3     │   │  bank 6   bank 7     │
  └──────────────────────┘   └──────────────────────┘
        shared within             shared within
        the group                 the group

        different groups share less,
        so they can be accessed closer together

That is the whole of the topology this chapter asserts, and it is deliberately vague about what is shared within a group, because Module 16 owns that and I would be guessing.

The timing consequence is the part that belongs here: one parameter becomes two, selected by a property of the pair of commands rather than of either command alone. That is a new shape — every obligation so far had a magnitude that depended only on configuration, and this one depends on the relationship between two events.

Why a per-bank countdown cannot enforce a shared obligation. A column command to one bank is accepted and arms an obligation whose resource is the shared column path, not that bank. A per-bank tracker records the event only against the bank that was accessed, so a second bank's own countdown remains at zero and that bank concludes it may issue. The shared tracker, by contrast, was armed by the first command regardless of which bank it targeted, so it correctly refuses the second command. The diagram shows the two trackers reaching opposite conclusions from the same command stream, with the per-bank tracker permitting a command that violates the requirement.Column cmdsPer-bank stateShared stateVerdictsREAD bank 0 — armsbank 0READ bank 0 — armssharedREAD bank 7 — bank 7idlepermits — WRONGREAD bank 7 — sharedbusyrefuses — correct

4. The DDR4 Split — Verified

So the magnitude selection is a function of the pair:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  same bank group       →  the LONGER requirement   (tCCD_L)
  different bank group  →  the SHORTER requirement  (tCCD_S)

  and  tCCD_L  >  tCCD_S    — which is what "long" and "short" mean

The architectural consequence is worth stating explicitly, because it is the reason bank groups exist at all. If a controller can steer consecutive accesses into different bank groups, it pays the shorter spacing and issues column commands more often. If its access pattern concentrates into one group, it pays the longer spacing. The same workload, mapped differently onto banks, gets different column throughput — which is why Module 18's address mapping is a performance lever and not just a decoding detail.

5. DDR5 — What I Could Not Confirm

6. Units — Why This One Is in Cycles

Every parameter so far has been published in nanoseconds. tCCD is different, and the reason is Chapter 13.2 §5's two-kinds-of-requirement argument arriving in a concrete case.

tRCD, tRP, tRAS and tRC are all published in nanoseconds — verified in Chapter 14.1 §4 from Micron's speed-bin table headings — because each arises from a physical process whose duration is indifferent to the clock.

Column spacing is not like that. It arises from structural contention: the data bus can carry one burst at a time, the command path moves one command per cycle, and the device's column machinery has a pipeline depth. Those are all counted in cycles, and they do not become fewer cycles because the clock slowed down. So the requirement is naturally expressed in nCK.

Chapter 13.2 §5 also verified that some DDR4 parameters use the max(nCK, ns) form — DDR4's tRRD_S being the verified example, which Chapter 14.7 owns. Whether tCCD carries an absolute-time term as well as a cycle floor, I did not verify, and §4's callout says so. The structural argument for a cycle-based requirement stands regardless.

7. RTL — Reusing What Exists, and Extending It

Collision check, and this time the answer is that the block already exists.

Chapter 4.5's bank_group_spacer implements exactly this: GROUPS bank groups, a SAME_GAP and a DIFF_GAP abstract separation, per-group countdowns, ready/valid flow control, and — notably — distinct stall reasons, stall_same and stall_diff, with the comment that distinguishing them is the point because they lead to opposite conclusions about whether rescheduling would help.

That block is the right implementation of this parameter and this chapter reuses it. Building a second one would be exactly the ten-timers architecture Chapter 14.4 §6 argued against, and it would be worse than redundant — two blocks tracking one shared obligation would have to be kept consistent.

So what is left to build? §4 verified something bank_group_spacer does not cover: the short/long split also applies to write-to-read turnaround, which means the magnitude depends not only on the group relationship but on the operation pair — read-after-read, write-after-write, write-after-read, read-after-write.

The engineering problem. Given a previous column command's operation and group, and a candidate's operation and group, select the applicable magnitude and report the remaining wait — with the selection logic explicit enough to audit.

Classification: controller-side shared-resource timing bookkeeping. One countdown for the shared resource, plus per-group state. No physical modelling, no PHY modelling.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────
//  column_turnaround_matrix
//
//  CLASSIFICATION
//    Controller-side shared-resource timing bookkeeping. Counts cycles
//    and selects a magnitude from a small table.
//
//  WHAT IT MODELS
//    Column-to-column spacing where the magnitude depends on BOTH:
//      - the group relationship  (same bank group / different)
//      - the operation pair      (RD→RD, WR→WR, WR→RD, RD→WR)
//    trigger     = a column command accepted, ANY bank
//    constrained = the next column command, ANY bank
//    scope       = SHARED  (plus per-group refinement)
//    sense       = MINIMUM SEPARATION
//
//  WHAT IT DOES NOT MODEL
//    Any physical process. Any PHY, DQS, electrical or bus-turnaround
//    analog behaviour -- Modules 19-22 own that. This block holds cycle
//    counts selected from a configuration table.
//    It also does NOT model tRCD: a column command to a freshly
//    activated bank is ALSO constrained bank-locally (Chapter 14.1),
//    and a consumer must take the maximum. Hence the output name.
//
//  RELATIONSHIP TO EXISTING RTL
//    Chapter 4.5's bank_group_spacer owns the same/different-group
//    countdown with flow control and stall reasons, and is the right
//    block for that job. THIS block does not replace it: it adds the
//    OPERATION-PAIR dimension that bank_group_spacer does not carry,
//    and it reports only a verdict rather than arbitrating. A design
//    would use one or the other, not both -- see the header note in
//    §7's prose about why two trackers of one shared obligation is
//    worse than redundant.
//
//  SCOPE IS STRUCTURAL
//    The shared countdown is a SINGLE register, not an array indexed by
//    bank. That is the whole defence against Chapter 13.3 §4's Bug 2.
//    Per-group state exists, but there is deliberately NO per-BANK
//    state anywhere in this block, because the obligation has no
//    per-bank component and offering one would invite misuse.
//
//  CONVENTION (Chapter 13.2 §6)
//    Column command accepted on cycle N with selected magnitude D
//    forbids the next column command on N .. N+D-1, permits from N+D.
//    One command per cycle on the command bus, so the countdown loads
//    D-1.
//
//  SIMULTANEITY
//    column command and expiry same cycle -> COMMAND WINS, re-arms
//        with the magnitude selected for THAT pair.
//    reset -> obligation abandoned; no history to honour.
// ─────────────────────────────────────────────────────────────────────
module column_turnaround_matrix #(
  parameter int GROUPS = 4,
  parameter int MAX_CYCLES = 32,
  parameter int GRP_W = (GROUPS     <= 1) ? 1 : $clog2(GROUPS),
  parameter int CNT_W = (MAX_CYCLES <= 1) ? 1 : $clog2(MAX_CYCLES + 1)
) (
  input  logic              clk,
  input  logic              rst_n,

  // ── The magnitude table. EIGHT entries: four operation pairs by two
  //    group relationships. Supplied as inputs because they are
  //    configuration, and because §4 verified the DDR4 split exists
  //    without my being able to verify its VALUES -- so a design must
  //    program them from its own device's table.
  //    Naming follows DDR4 convention: _S = different group (short),
  //    _L = same group (long). LABELLED DDR4; see §5 on DDR5.
  input  logic [CNT_W-1:0]  rd_rd_s, rd_rd_l,
  input  logic [CNT_W-1:0]  wr_wr_s, wr_wr_l,
  input  logic [CNT_W-1:0]  wr_rd_s, wr_rd_l,
  input  logic [CNT_W-1:0]  rd_wr_s, rd_wr_l,

  // ── A column command being accepted this cycle.
  input  logic              col_accepted,
  input  logic              col_is_write,
  input  logic [GRP_W-1:0]  col_group,

  // ── The candidate column command being evaluated.
  input  logic              cand_valid,
  input  logic              cand_is_write,
  input  logic [GRP_W-1:0]  cand_group,

  // ── Verdict for the SHARED obligation only. Named for its scope,
  //    not its purpose: tRCD is also applicable to a column command
  //    and lives elsewhere (Chapter 14.1).
  output logic              shared_timing_ok,
  output logic [CNT_W-1:0]  shared_remaining,

  // ── Which magnitude WOULD apply to this candidate. Published so a
  //    scheduler can ask "would a different group be cheaper?" -- the
  //    §4 architectural lever -- and so §10's debugging can see the
  //    selection without re-deriving it.
  output logic [CNT_W-1:0]  selected_magnitude,
  output logic              selection_is_same_group,
  // The turnaround class, for diagnostics: 0 RD→RD, 1 WR→WR,
  // 2 WR→RD, 3 RD→WR.
  output logic [1:0]        turnaround_class
);

  if (GROUPS < 1) begin : g_groups
    initial $fatal(1, "column_turnaround_matrix: GROUPS must be >= 1");
  end
  if (MAX_CYCLES < 0) begin : g_max
    initial $fatal(1, "column_turnaround_matrix: MAX_CYCLES must be >= 0");
  end

  // ── ONE countdown for the shared obligation. Not an array. §2.
  logic [CNT_W-1:0] shared_cnt;
  // Last column command's properties, needed because the magnitude
  // depends on the PAIR.
  logic             last_was_write;
  logic [GRP_W-1:0] last_group;
  logic             have_last;

  // ── Magnitude selection for a candidate, given the last command.
  logic             same_grp;
  logic [1:0]       tclass;
  logic [CNT_W-1:0] mag;

  always_comb begin
    same_grp = have_last && (cand_group == last_group);

    // Turnaround class from the operation pair.
    if      (!last_was_write && !cand_is_write) tclass = 2'd0;  // RD→RD
    else if ( last_was_write &&  cand_is_write) tclass = 2'd1;  // WR→WR
    else if ( last_was_write && !cand_is_write) tclass = 2'd2;  // WR→RD
    else                                        tclass = 2'd3;  // RD→WR

    // The 2 x 4 selection. Written as an explicit table rather than
    // arithmetic so it can be audited against a datasheet line by
    // line -- which is the only way anyone will ever check it.
    unique case (tclass)
      2'd0: mag = same_grp ? rd_rd_l : rd_rd_s;
      2'd1: mag = same_grp ? wr_wr_l : wr_wr_s;
      2'd2: mag = same_grp ? wr_rd_l : wr_rd_s;
      2'd3: mag = same_grp ? rd_wr_l : rd_wr_s;
      default: mag = '0;
    endcase

    selected_magnitude      = mag;
    selection_is_same_group = same_grp;
    turnaround_class        = tclass;

    shared_remaining = shared_cnt;
    shared_timing_ok = (shared_cnt == '0);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      shared_cnt     <= '0;
      last_was_write <= 1'b0;
      last_group     <= '0;
      have_last      <= 1'b0;
    end else begin
      if (shared_cnt != '0) begin
        shared_cnt <= shared_cnt - 1'b1;
      end

      if (col_accepted) begin
        // Re-arm using the magnitude for the pair (last → this). Note
        // this uses the magnitude selected for the command BEING
        // ACCEPTED, which requires recomputing the selection against
        // the accepted command rather than the candidate. Using the
        // candidate's selection here would be a real bug -- the
        // candidate may never issue.
        shared_cnt <= arm_magnitude(col_is_write, col_group);

        last_was_write <= col_is_write;
        last_group     <= col_group;
        have_last      <= 1'b1;
      end
    end
  end

  // ── The same selection, evaluated for a command being accepted.
  //    A function rather than duplicated logic, so the table exists
  //    once and the candidate path and the arming path cannot drift
  //    apart -- a real failure mode when the two are written twice.
  function automatic [CNT_W-1:0] arm_magnitude(logic is_wr,
                                               logic [GRP_W-1:0] grp);
    logic sg;
    logic [CNT_W-1:0] m;
    begin
      sg = have_last && (grp == last_group);
      if      (!last_was_write && !is_wr) m = sg ? rd_rd_l : rd_rd_s;
      else if ( last_was_write &&  is_wr) m = sg ? wr_wr_l : wr_wr_s;
      else if ( last_was_write && !is_wr) m = sg ? wr_rd_l : wr_rd_s;
      else                                m = sg ? rd_wr_l : rd_wr_s;
      // Load D-1 per the convention; guarded against underflow.
      arm_magnitude = (m >= CNT_W'(1)) ? (m - CNT_W'(1)) : '0;
    end
  endfunction

endmodule

Interface contract. shared_timing_ok covers only the shared obligation. A column command to a freshly activated bank is also constrained by tRCD, and a consumer must take the maximum — the name says shared_ for that reason, following the discipline Chapter 14.4 §6 established.

Parameter contract. Eight magnitude inputs rather than two, because §4 verified the split covers turnaround as well as same-operation pairs. They are inputs rather than parameters because a design must program them from its own device's table — §4 was explicit that I verified the split's existence and not its values.

Internal state, and the scope decision. shared_cnt is one register. There is no array indexed by bank anywhere in this block, and that absence is the design. §2's argument says a per-bank countdown cannot express this obligation; the structural expression of that argument is to not have one.

Why the selection is a function. The candidate path and the arming path must use the same table. Writing the selection twice is a real drift hazard — one gets updated and the other does not — so it lives in arm_magnitude and is called from the sequential block.

A subtlety in the arming path. shared_cnt is re-armed using the magnitude for the pair (last accepted, now accepted), not the candidate's selection. Using the candidate's value would be wrong because the candidate may never be issued. This is easy to get wrong when the combinational selection is sitting right there looking reusable.

Corner cases. have_last low at reset: same_grp is forced low, so the shorter different-group magnitude is selected. That is the unsafe default and it is chosen deliberately, because with no previous column command there is no obligation at all and shared_cnt is zero — the magnitude is not used. A design that instead defaulted same_grp high would be conservative for no reason. GROUPS == 1: every access is same-group, GRP_W is 1 by the guard, and only the _L magnitudes are ever selected — which is correct and is also the configuration where the whole bank-group mechanism is invisible. Any magnitude of 0 or 1: forbids nothing observable, as elsewhere.

Synthesis implications. One countdown, a small register for the last command's properties, and an 8-entry mux with a 2-bit selector plus a comparator. The unique case synthesises to a mux; the function is inlined.

Failure modes. Per-bank countdowns — the category error of §2, producing violations that scale with bank parallelism. Selecting _S where _L belongs: violations only on same-group traffic, so an access pattern that happens to alternate groups passes. Arming with the candidate's magnitude: wrong whenever the candidate differs from what was actually accepted. Duplicated selection logic drifting between the two paths: the candidate path reports one magnitude and the countdown enforces another, which produces violations that contradict the design's own diagnostics.

8. Same Group Against Different Group

column_turnaround_matrix — the group relationship selects the magnitude

10 cycles
Ten cycles with educational magnitudes of two for different bank group and five for same bank group. A read to bank group zero is accepted at cycle one, arming the shared countdown. A second read to bank group one is a different-group pair, so the shorter magnitude of two applies and the command is legal at cycle three. That command re-arms the shared countdown. A third read to bank group one is now a same-group pair relative to the previous command, so the longer magnitude of five applies, and a candidate offered at cycle five is refused while the same command at cycle eight is legal. The trace shows that the magnitude depends on the relationship between consecutive commands rather than on either command alone, and that a single shared countdown is re-armed by every column command regardless of bank.short spacingshort spacinglong spacing — same grouplong spacing — same groupdifferent group — short, legaldifferent group — short,legalsame group — long, refusedsame group — long, refusedlong magnitude satisfiedlong magnitude satisfiedCKcol_acceptedcol_group0G0G0G1G1G1G1G1G1G1last_group00G0G0G1G1G1G1G1G1selected mag0022555555same_groupshared_cnt0010432104shared_okcandidate000G1G1G1G1G1G1G1t0t1t2t3t4t5t6t7t8t9

Three observations, each a §9 property.

The magnitude changes at cycle 4 without any configuration changing. It changed because the previous command changed — the pair relationship is different. No parameter before this one behaved that way.

shared_cnt is one signal. Commands to group 0 and group 1 both arm it. That row is the visual form of shared scope, and it is the row that a per-bank design would have replicated into an array, wrongly.

The candidate at cycle 5 is refused even though it targets a group that has not been accessed since cycle 3. Its own group's history is irrelevant; what matters is the most recent column command anywhere and its relationship to this one.

9. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── P1. No column command accepted while the shared obligation is
//    outstanding. The safety property, and the one a per-bank design
//    fails as soon as traffic spans banks.
property p_no_column_while_shared_busy;
  @(posedge clk) disable iff (!rst_n)
    col_accepted |-> (shared_cnt == '0);
endproperty
a_no_column_while_shared_busy: assert property (p_no_column_while_shared_busy);

// ── P2. SCOPE, the positive direction. A column command to ANY group
//    arms the shared obligation. This is the property for Chapter 13.3
//    §4's Bug 2: a per-bank or per-group-only design would leave the
//    shared count idle after a command to some other bank, and this
//    fires. The mirror of Chapter 14.1's bank-locality property, and
//    it points the OTHER way -- which is the whole distinction.
property p_any_group_arms_shared;
  @(posedge clk) disable iff (!rst_n)
    ( $past(col_accepted, 1) && ($past(selected_magnitude, 1) >= CNT_W'(2)) )
      |-> (shared_cnt != '0);
endproperty
a_any_group_arms_shared: assert property (p_any_group_arms_shared);

// ── P3. The selected magnitude matches the documented 2x4 table.
//    Written against the TABLE rather than against the RTL's own case
//    statement, so it checks the intent and not the implementation.
//    Catches an _S/_L swap, which is the highest-consequence
//    selection error: it under-waits on same-group traffic.
property p_selection_matches_table;
  @(posedge clk) disable iff (!rst_n)
    cand_valid |->
      ( selected_magnitude ==
          ( selection_is_same_group
              ? ( (turnaround_class == 2'd0) ? rd_rd_l
                : (turnaround_class == 2'd1) ? wr_wr_l
                : (turnaround_class == 2'd2) ? wr_rd_l : rd_wr_l )
              : ( (turnaround_class == 2'd0) ? rd_rd_s
                : (turnaround_class == 2'd1) ? wr_wr_s
                : (turnaround_class == 2'd2) ? wr_rd_s : rd_wr_s ) ) );
endproperty
a_selection_matches_table: assert property (p_selection_matches_table);

// ── P4. A CONFIGURATION property: long must exceed short, for every
//    operation pair. Not a property of the logic -- the logic works
//    for any values -- but a mis-programmed table produces violations
//    that look like logic bugs, and catching it where the
//    configuration arrives is far cheaper. Chapter 14.3 §7 made the
//    same argument for tRAS against tRCD.
property p_long_exceeds_short;
  @(posedge clk) disable iff (!rst_n)
    (rd_rd_l >= rd_rd_s) && (wr_wr_l >= wr_wr_s)
 && (wr_rd_l >= wr_rd_s) && (rd_wr_l >= rd_wr_s);
endproperty
a_long_exceeds_short: assert property (p_long_exceeds_short);

What these prove. That the shared obligation is honoured; that it is armed by commands to any group, which is the scope property pointing the opposite way from every bank-local chapter's; that the 2 × 4 selection matches the documented table; and that the configuration respects _L >= _S.

What these do not prove. That the eight magnitudes are the right values — inputs, and §4 was explicit that I could not verify them, so this is a datasheet-reading obligation that no simulation discharges. That tRCD was also consulted: a design using only this block would violate tRCD on a column command to a freshly activated bank. And nothing about whether the group decode is correct — if cand_group is computed wrongly from the address, every property here passes and the wrong magnitude is selected, which is Chapter 8.4's territory.

Vacuity. P2 requires a magnitude of at least 2; P3 requires cand_valid. Cover both, and cover all eight table entries being selected — a run that never produces a write-to-read turnaround in the same group has not tested a quarter of the table.

10. DV — Independent Reconstruction

Invert the representation. The design counts down one shared register; the checker records last_col_cycle, last_col_was_write and last_col_group as observed facts and computes the deadline by addition when a candidate appears.

Recompute the selection from the observed pair, independently. The checker must derive same-group-ness from the addresses it observed, not from the design's cand_group port — because a group-decode bug is otherwise invisible to both. This is the tCCD-specific version of Chapter 14.1 §10's independent-resolution rule, and the stakes are higher because the group decode is an address-mapping function rather than a single register.

Cover the whole table deliberately. Eight entries; random traffic will hit read-after-read in different groups constantly and same-group write-to-read rarely. Constrain the stimulus.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  TIMING VIOLATION
    command        : READ, bank 5  (bank group 1)
    issued at      : cycle 205
    resource       : SHARED column path
    previous       : WRITE, bank 7 (bank group 1), cycle 202
    relationship   : SAME bank group
    turnaround     : WR → RD
    selected       : wr_rd_l                CONTROLLING
    requirement    : 8 cycles  (educational)
    legal from     : cycle 210
    short by       : 5 cycles
    note           : had the previous command been in a different
                     bank group, wr_rd_s would have applied and this
                     command would have been legal at cycle 205

That closing note is the one worth stealing from this chapter. It does not just report the violation — it reports the counterfactual, and the counterfactual is actionable in a way the violation alone is not. §4 said address mapping is a performance lever; a report that says “a different group mapping would have made this legal” turns that abstract claim into a specific finding about a specific access pattern.

11. Debugging

Symptom. Column commands violate spacing, or column throughput is below the model.

Candidate mechanismEvidenceDiscriminator
Per-bank state for a shared obligationSingle-bank tests pass; violation rate rises with bank parallelismThe discriminator for this whole chapter. Run one-bank traffic, then all-bank traffic. If violations appear only in the second, the obligation is indexed by the wrong thing.
_S selected where _L belongsViolations only on same-bank-group pairsCorrelate the violating pairs' groups. Different-group traffic passes cleanly.
Group decode wrongViolations correlate with address bits, not with bank numbersChapter 8.4. The checker must decode independently — §10.
One turnaround class unprogrammedViolations only on one operation pair, e.g. only write-to-readturnaround_class on the violating cycle. Four classes; check all four are programmed.
Candidate's magnitude used for armingViolations after a candidate that was evaluated but not issued§7's arming subtlety.
Only shared obligation checked, tRCD ignoredViolations on the first column command after an activatetRCD. The applicable set was too small.
Magnitude stored in ns and re-resolvedRequirement changes with frequency when it should not§6 — tCCD is a cycle count and has no frequency dependence to resolve.

The discriminator that defines this chapter is bank parallelism. Every other chapter's scope bug was safe and slow — a bank-local obligation held globally. This chapter's is fast and wrong in the other direction, and it has an unmistakable signature: the design fails more the better it does its job. One simulation at one bank and one at all banks separates it from everything else in the table.

Responsible layer. If the spacing is being honoured correctly and column throughput is still short, the answer is likely the access pattern concentrating into one bank group — which is real, is Module 18's address mapping, and is what §10's counterfactual note is designed to surface.

12. Common Misconceptions

“If two banks are different, their commands are independent.” Tempting because five previous chapters were all bank-local, and bank independence is DDR's whole selling point. Why it is wrong: banks are independent in state and share the data path, the command path and the column machinery. §2. Consequence: per-bank state for a shared obligation — violations that scale with parallelism, passing every single-bank test. Replacement model: independence in state, contention in resources. Debugging clue: single-bank passes, multi-bank fails.

“tCCD is universally one value.” Tempting because it has one name. Why it is wrong: §4 verified that DDR4 splits it by bank group, and the split also covers turnaround pairs — so one name covers several requirements. Consequence: programming a single value; if the short one, violations on same-group traffic, and if the long one, throughput given away on different-group traffic. Replacement model: a magnitude selected by the command pair. Debugging clue: violations or slowness correlating with the group relationship.

_S and _L exist in every DDR generation.” Tempting because the naming is ubiquitous in DDR4 material and tuning tools. Why it is wrong: it is DDR4-era naming for a DDR4 mechanism, and §5 says plainly that I could not confirm what DDR5 does. Names surviving is not semantics surviving. Consequence: a controller ported on the assumption of identical semantics. Replacement model: verify the split's existence, naming and scope per generation. Debugging clue: a port that works except on one class of access pattern.

“tCCD tells the controller when the data will arrive.” Tempting because it concerns column commands and data. Why it is wrong: tCCD constrains when the next column command may issue. CL describes when data follows an accepted one. Different edges, and different classes — one is a minimum separation, the other a fixed latency. Consequence: a data-capture path timed off column spacing rather than off CL. Replacement model: two separate edges; Chapter 14.9 §2 separates them. Debugging clue: capture failures that move when spacing changes.

“tCCD is in nanoseconds like tRCD.” Tempting because the four parameters before it are. Why it is wrong: §6 — it arises from structural contention counted in cycles, not from a physical process measured in time. Consequence: a design that re-resolves it on every frequency change, inventing a frequency dependence it does not have, and getting a different cycle count at each speed grade for a requirement that should be constant. Replacement model: physical requirements in ns, structural requirements in nCK. Debugging clue: the column spacing requirement changing across speed grades when it should not.

“A longer column spacing is always safe.” Tempting because it is a Class A minimum. Why it is wrong: it is functionally safe and it caps column throughput directly — and because the obligation is shared, over-programming it throttles every bank at once rather than one. Consequence: the largest available throughput loss from a single conservative value in this module. Replacement model: margin on a shared obligation costs more than margin on a bank-local one, because nothing can be done in parallel to hide it. Debugging clue: aggregate bandwidth short of the model with no violations and no bank-level explanation.

13. Interview Reasoning

“What is the difference between tCCD and CL?” Different edges and different classes. tCCD is a minimum separation governing whether the next column command may be issued; CL is a fixed, programmed latency governing when data appears after an accepted column command. One is a permission with a lower bound and no upper bound; the other is a two-sided commitment. Conflating them times the capture path off the wrong parameter.

“Why can a per-bank state machine not enforce tCCD?” Because the resource is shared and the state is indexed by the wrong thing. Bank 7's countdown knows bank 7's history and has never been told about bank 0, so when it reads zero bank 7 concludes it may issue — and the thing blocking it happened elsewhere. It is a category error rather than an arithmetic one. The signature is memorable: single-bank tests pass and the violation rate rises with bank parallelism, so the design fails more the better it does its job.

“Why do bank groups create multiple timing cases?” Because some resource is shared within a group and not across groups, so two accesses in different groups can be spaced more closely than two in the same group. DDR4 publishes that as two parameters — short for different groups, long for the same — and the split also covers write-to-read turnaround. The architectural consequence is the interesting part: the same workload mapped differently onto banks gets different column throughput, which is what makes address mapping a performance lever rather than a decoding detail.

“Is tCCD in nanoseconds or cycles, and why does it matter?” Cycles, and the why is the good half. tRCD, tRP, tRAS and tRC arise from physical processes and are published in nanoseconds, so their cycle cost rises with clock frequency. Column spacing arises from structural contention — one burst on the bus at a time, one command per cycle, a pipeline depth — and those are counted in cycles regardless of the clock. So as parts get faster, the nanosecond parameters cost more cycles while tCCD costs the same cycles and less absolute time. That is why latency-limited and bandwidth-limited workloads respond differently to a faster part.

“You program a single tCCD value. What breaks?” Depends which value. Program the short one and same-bank-group traffic violates, with the failure rate depending entirely on how the access pattern maps onto groups — so it can look non-deterministic. Program the long one and nothing violates, and different-group traffic gives away throughput silently. The second is worse in practice because no test reports it.

“Where would you look first if column throughput is below model with no violations?” At whether the access pattern is concentrating into one bank group, because same-group pairs pay the longer spacing. A violation report that also states the counterfactual — this would have been legal in a different group — turns that from a hypothesis into a measurement. And because the obligation is shared, over-programming it throttles every bank simultaneously, so it is the single conservative value with the largest throughput consequence in the module.

14. Engineering Exercises

1. Classify the scope. For each pair, say whether the column-spacing obligation applies and which magnitude: (a) READ bank 0 then READ bank 1, both group 0; (b) READ bank 0 group 0 then READ bank 4 group 1; (c) WRITE bank 2 group 0 then READ bank 2 group 0; (d) READ bank 0 group 0 then ACTIVATE bank 5.

Worked: (a) applies, same group → the long read-to-read magnitude. (b) applies, different group → short read-to-read. (c) applies, same group, write-to-read turnaround → the long turnaround magnitude, which is a different table entry from (a). (d) does not apply — an ACTIVATE is not a column command, so this obligation has nothing to say about it; tRRD is the relevant one. Recognising (d) as out of scope is the point.

2. Size the per-bank bug. A design holds column spacing per bank. With educational magnitudes _S = 2 and _L = 5, construct the shortest traffic pattern that violates, and explain why an identical pattern confined to one bank does not.

Worked: READ bank 0 at cycle 10, READ bank 1 at cycle 11. Bank 1's own counter has never been armed, so it reads zero and the design issues — violating the different-group requirement of 2. Confined to one bank, the second read arms the same counter that the first armed, so the per-bank counter accidentally holds the complete shared history and the design is correct. Single-bank traffic makes the wrong indexing indistinguishable from the right one.

3. Find the swapped selection. A design swaps _S and _L. On which traffic does it violate, and on which does it merely lose throughput? Which is more likely to reach production?

Worked: it violates on same-group pairs, where it applies the short magnitude to a case needing the long one. It loses throughput on different-group pairs, applying the long magnitude unnecessarily. The violation reaches production only if the regression's access patterns never concentrate into one group — which is plausible for a striped address map, making this a real escape.

4. Cover the table. List all eight entries of the 2 × 4 table and, for each, write the shortest command pair that selects it. Which entries will random traffic hit rarely, and why does that matter?

Worked: the four turnaround classes × two group relationships. Random traffic hits read-after-read in different groups constantly. It hits same-group write-to-read rarely, because it requires both a turnaround and a group collision. That matters because §9's P3 is an implication over cand_valid and will pass for entries never selected — so a suite reporting P3 clean may have exercised a quarter of the table.

5. Reason about units. A design stores column spacing in picoseconds and resolves it to cycles with a ceiling on each frequency change. Using two speed grades, show the requirement it computes and explain why the behaviour is wrong even though no violation occurs.

Worked: suppose the design stores _L as 5 cycles at DDR4-3200, i.e. 3.125 ns, then at DDR4-2400 resolves ceil(3.125 / 0.8333) = 4 cycles — one cycle short of the 5 the parameter actually requires at any frequency. That is a violation, so the exercise's premise is instructive in itself: converting a cycle-based requirement through absolute time can go either way, and here it goes the unsafe way. The general rule from §6 is that a cycle-based requirement must not be resolved at all.

6. Write the counterfactual. §10's report states what would have happened in a different bank group. Write the logic that computes it, and say why it is worth the gates in a verification environment but not in a controller.

7. Argue the reuse decision. §7 reuses Chapter 4.5's bank_group_spacer rather than rebuilding it, and adds a separate block for the operation-pair dimension. Argue instead for extending bank_group_spacer in place, then say what that would cost the earlier chapter.

15. Summary

tCCD is the module's first non-bank-local obligation. A column command accepted on any bank constrains the next column command on any bank, because the resources involved — the data path, the command path, the device's column machinery — are shared by every bank. It is a minimum separation, expressed in cycles rather than nanoseconds, because it arises from structural contention rather than from a physical process.

The scope consequence is the chapter. A per-bank countdown cannot express this obligation, and the failure is not arithmetic but a category error: the state is indexed by the wrong thing. That produces Chapter 13.3 §4's Bug 2 — fast and wrong — with an unmistakable signature: single-bank tests pass and the violation rate rises with bank parallelism, so the design fails more the better it does its job. One simulation at one bank and one at all banks distinguishes it from everything else.

DDR4 splits the requirement by bank group, verified: two parameters conventionally written tCCD_S and tCCD_L, short for different groups and long for the same group, with the split covering read bursts, write bursts and write-to-read turnaround. The values I could not verify and therefore do not publish. _S and _L are labelled DDR4 throughout, and §5 states plainly that I could not confirm what DDR5 does — the third DDR5 question this curriculum has left open, which is itself the lesson about how differently the two generations are documented in public.

The split makes the magnitude a function of the pair of commands rather than of either one — a constraint shape no earlier chapter had — and it turns address mapping into a performance lever, because the same workload mapped differently onto groups gets different column throughput.

Chapter 4.5's bank_group_spacer already implements the group-scoped countdown and is reused, not rebuilt. column_turnaround_matrix adds the dimension it lacks — the operation pair — as an explicit 2 × 4 table with a single shared countdown and no per-bank state anywhere, which is the scope argument made structural. Its most useful diagnostic is the counterfactual of §10: reporting that a violation would not have occurred had the previous access been in a different bank group.

And the unit point generalises: physical requirements are published in nanoseconds and cost more cycles at a faster clock; structural requirements are published in cycles and cost the same cycles at every clock. That single distinction predicts why latency-limited and bandwidth-limited workloads respond differently to a faster part.

16. What Comes Next

This chapter moved from bank-local to shared scope for column commands. Chapter 14.7 does the same for activates.

The parameter is tRRD, and it makes a point this chapter could only gesture at: two different banks does not mean unconstrained parallelism. Activating bank 0 and activating bank 9 are operations on independent banks, and they still contend — and DDR4 splits that spacing by bank group too, with the verified max(nCK, ns) form that Chapter 13.2 §5 introduced and no parameter in this module has yet owned.

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.