Skip to content
VLSI Mentor

DDR · Module 14

CL — CAS Latency

Eight parameters have said not before. CL says exactly then. The module's first two-sided obligation, where arriving late fails as surely as arriving early.

Eight parameters, and every one has been one-sided. tRCD, tRP, tRAS, tRC, tWR, tCCD and tRRD all say not before, with no upper bound. tFAW says not too many, also with no upper bound.

CL says something none of them says:

Data appears at the configured offset. Not at least — exactly. And being ready late fails as surely as sampling early.

Chapter 10.2 already owns what CAS latency is and why reads have latency at all. This chapter's job is narrower and, for a controller designer, sharper: to place CL precisely in the parameter vocabulary the last eight chapters built, and to show that it belongs to a different class with different consequences.

1. The Obligation, in Five Parts

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  PARAMETER          CL   (CAS latency)

  1 triggering event   a READ command accepted
  2 constrained event  the FIRST returned data beat
  3 resource           the device's read path and the data interface
  4 magnitude          in CK CYCLES — programmed, not physical.  §4
  5 sense              FIXED LATENCY  —  two-sided.  §3
  ─────────────────────────────────────────────────────────────────
  consequence        first data beat occurs AT
                     read_cycle + CL       — not at or after

Parts 4 and 5 are both new, and they are connected.

The follow-up questions:

What re-arms it? Nothing, in the minimum-separation sense. Each accepted READ creates its own expectation, and several can be outstanding at once — which is a pipeline, not a countdown, and §7's RTL reflects that.

What can dominate it? Nothing. This is the structural break from the previous eight chapters: a fixed latency is not a lower bound that composes into Chapter 13.3 §3's maximum. It does not participate in the applicable set for an issue decision at all, because it is not about issuing. §2 is entirely about this.

Cycle or absolute-time component? Cycles, and §4 verifies it from the datasheet's own column headings.

Does generation matter? Not for the structure. The programmed values differ by speed grade, as all of these do.

2. Two Different Edges

The most common confusion about CL is not about CL's value. It is about which question CL answers, and the confusion is worth taking apart with tRCD beside it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ACTIVATE ──── tRCD ────▶  READ may ISSUE from here onward
                            (minimum separation, one-sided)

  READ accepted ──── CL ────▶  data APPEARS here
                               (fixed latency, two-sided)

These are different edges with different endpoints and different classes.

tRCD's constrained event is a command the controller chooses to issue. The controller is the agent; the obligation restricts what it may do; and because it is a minimum, the controller may always wait longer.

CL's constrained event is data the device will produce. The controller is not the agent — it is the recipient. Once the READ is accepted, the data is coming, at the configured offset, whether or not anything is ready to receive it.

And the corollary, which is where real designs get hurt: there is nothing to wait for. With tRCD, a controller that is not ready simply does not issue. With CL, the readiness deadline has already passed the moment the READ was accepted — the capture path must be ready CL cycles later, and the only way to influence that is to not have issued the read.

3. Two-Sided, and What That Costs

Chapter 13.3 §2 defined class B as a two-sided commitment. Here is what each side means concretely.

The device's side. The data will be driven at the configured offset. The device does not check whether the controller is ready, and it has no mechanism to delay.

The controller's side. The capture path must be ready at that offset. Not before, not after.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  early sampling  →  captures the cycle before the data.  WRONG DATA.
  late  sampling  →  the beat has gone.                   WRONG DATA.

Both directions produce wrong data rather than a stall, which is the practical difference from every minimum-separation obligation in this module. Violate tRCD and the device's guarantees lapse; violate CL in either direction and the controller reads the wrong cycle of a bus that was behaving perfectly.

4. Units — The Table Heading, Cashed In

Chapter 14.1 §4 quoted a verified column heading and promised it would matter. This is where it pays off.

Three consequences follow, and they are the practical content of §4.

CL has no ns-to-cycle conversion, so it has no conversion bug. Every parameter in chapters 14.1 through 14.4 could be got wrong by truncating instead of ceiling. CL cannot — there is no division. Chapter 14.1 §11's speed-grade off-by-one simply does not exist for this parameter.

CL is programmed, so the controller and the device must agree. A physical requirement is a property of the device that a controller reads and honours. CL is a value written into the device and simultaneously used by the controller's capture path. Two places, one number, and if they disagree the data is captured on the wrong cycle. That is a failure mode none of the earlier parameters has.

The same CL in cycles means different absolute latency at different speeds. A CL of 22 is 13.75 ns at DDR4-3200 and 18.33 ns at DDR4-2400. The cycle count is the configured thing, so faster parts deliver the same cycle latency in less time — which is the opposite relationship from Chapter 13.2 §4's nanosecond parameters, whose cycle counts grow with frequency.

That last point explains something that otherwise looks strange about memory marketing: CL numbers rise across generations while actual latency stays roughly flat. A CL of 22 at DDR4-3200 and a CL of 14 at DDR4-1600 are 13.75 ns and 17.5 ns respectively — the larger CL is the faster part in absolute terms. A CL compared across data rates is meaningless without the rate attached.

How a fixed latency differs from a minimum separation in the flow of control. A minimum separation governs a command the controller chooses to issue: the controller asks whether enough time has passed, the obligation answers, and if the answer is no the controller simply waits. A fixed latency governs data the device will produce: once the read command is accepted, the device commits to driving data at the configured offset, and the controller's capture path must be ready at that exact cycle. The diagram shows that the controller is the agent in the first case and the recipient in the second, and that after acceptance there is nothing left to wait for.ControllerMin separationDeviceCapture pathmay I issue READ?not yet — so waitnow legal —one-sidedREAD accepteddata AT read + CLno waiting possiblenow

5. The Exact Boundary — Both Sides

Every previous chapter stated a one-sided contract. This one needs two.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  READ accepted (sampled) on cycle  N
  programmed latency                L  cycles

  first data beat occurs on cycle   M  =  N + L        EXACTLY

  capture must be armed for cycle   N + L
  capture at N + L - 1   →  wrong data (the beat has not arrived)
  capture at N + L + 1   →  wrong data (the beat has gone)

Note the shape is the familiar N + L — the same as Chapter 13.2 §6's earliest-legal formula. The arithmetic did not change; the quantifier did. Where the minimum-separation chapters said legal from N + D onward, this says occurs at N + L, and only there.

And with a burst, the obligation extends: the first beat at N + L, and subsequent beats on following transfer positions for the burst's duration (Chapter 12.1). §7's block tracks the whole window for that reason.

6. Several Outstanding at Once

One more structural difference, and it is the one that shapes the RTL.

A minimum-separation obligation is one at a time per resource: bank 3 has one tRCD countdown, and a second ACTIVATE to bank 3 replaces it. A fixed latency is pipelined: a controller may issue several reads before the first one's data returns, and each carries its own expectation.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  READ A at cycle 10,  CL = 5   →  data A at 15
  READ B at cycle 12,  CL = 5   →  data B at 17
  READ C at cycle 14,  CL = 5   →  data C at 19

  at cycle 13:  THREE outstanding expectations, none satisfied yet

So the state is a pipeline, not a counter — which is why Chapter 6.5's cas_to_data_window already models a window with a pending flag and a collision output rather than a countdown, and why §7 extends that shape rather than the countdown shape of chapters 14.1 through 14.4.

Note also what bounds the number outstanding: tCCD, the column spacing of Chapter 14.6. The pipeline depth a controller must support is ceil(CL / tCCD) plus the burst, which is a genuinely useful sizing result and one that connects two chapters that otherwise look unrelated.

7. RTL — A Two-Sided Contract Checker

Collision check. Chapter 6.5's cas_to_data_window models a column command's data window with a latency parameter, a pending flag, a beat_index and a collision output. That block owns the window and this chapter reuses it.

Chapter 10.x's read_return_checker and read_return_pipeline own read-data return. Chapters 14.1 through 14.8 are all one-sided countdowns or a history queue.

What nothing in the corpus does is check a two-sided obligation in both directions and distinguish the two failures. Every existing block asks has enough time passed. None asks did this arrive exactly when it should have, and none distinguishes early from late — which §3 argued is the defining requirement of class B.

The engineering problem. Track each outstanding read's expected data cycle, and when data appears, classify it as on-time, early or late — reporting which, because Chapter 14.1 §11's debugging tables have all turned on distinguishing failure directions and here the two directions have different causes.

Classification: verification-oriented controller-side expectation tracking. Counts cycles, compares cycle numbers, classifies. Suitable for a checker or for controller-side diagnostics; the classification outputs are not a functional path.

What it does not model. Any electrical behaviour. No DQS, no strobe, no skew, no eye, no training, no propagation. Modules 19–21 own all of that. This block knows only that a beat was observed on some cycle and what cycle was expected — and the honest consequence is stated in §8: it cannot distinguish a controller-side configuration error from a PHY-side capture error, because both present as a beat on the wrong cycle.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────
//  read_latency_contract
//
//  CLASSIFICATION
//    Verification-oriented controller-side expectation tracking.
//    Counts cycles and compares cycle numbers. The classification
//    outputs are diagnostics, not a functional data path.
//
//  WHAT IT MODELS
//    §1's obligation as a TWO-SIDED contract:
//      trigger     = READ accepted on cycle N
//      constrained = first data beat, AT cycle N + CL
//      sense       = FIXED LATENCY (class B) -- both directions
//    pipelined, because §6 shows several reads can be outstanding.
//
//  WHAT IT DOES NOT MODEL
//    Any electrical or PHY behaviour. No DQS, strobe, skew, eye,
//    training or propagation delay -- Modules 19-21 own those. This
//    block is told that a beat was observed and on which cycle. It
//    therefore CANNOT distinguish a wrong CL setting from a PHY
//    capture error: both appear as a beat on an unexpected cycle, and
//    §8 states that limitation rather than hiding it.
//
//  WHY BOTH DIRECTIONS
//    Every block in Chapters 14.1-14.8 tests "has enough time passed",
//    because a minimum separation has a safe direction. A fixed
//    latency has NONE: early sampling reads a beat that has not
//    arrived, late sampling reads one that has gone, and both produce
//    WRONG DATA rather than a stall. So this block classifies rather
//    than gates.
//
//  CONVENTION (§5)
//    READ accepted on cycle N with programmed latency CL_CYCLES:
//    the first beat occurs ON cycle N + CL_CYCLES, and subsequent
//    beats on the following BURST_CYCLES-1 cycles.
//
//  SIMULTANEITY
//    a READ accepted on the same cycle a previous read's data begins
//        -> both handled; the pipeline holds several expectations.
//    two expectations landing on the SAME cycle -> impossible if tCCD
//        (Chapter 14.6) is honoured, since two reads cannot be closer
//        than tCCD and both have the same CL. REPORTED on
//        expectation_collision rather than assumed away.
//    reset -> all expectations abandoned. Correct: in-flight data
//        belongs to commands the controller no longer knows about.
// ─────────────────────────────────────────────────────────────────────
module read_latency_contract #(
  // Maximum reads outstanding. §6: ceil(CL / tCCD) plus burst is the
  // sizing argument, and undersizing silently drops expectations.
  parameter int DEPTH      = 8,
  parameter int TS_W       = 12,
  parameter int BURST_CYCLES = 4,
  parameter int PTR_W = (DEPTH <= 1) ? 1 : $clog2(DEPTH),
  // Occupancy is a COUNT and can equal DEPTH: $clog2(DEPTH+1).
  parameter int OCC_W = (DEPTH <= 1) ? 1 : $clog2(DEPTH + 1)
) (
  input  logic              clk,
  input  logic              rst_n,

  // ── The PROGRAMMED latency, in cycles. No conversion: §4 verified
  //    CL is published as a cycle count, so there is no ns-to-cycle
  //    step and no ceiling. A resolver on this port would be wrong.
  input  logic [TS_W-1:0]   cl_cycles,

  input  logic              read_accepted,

  // ── Observation: a data beat was seen this cycle. In a testbench
  //    this comes from the monitor; in a controller, from the capture
  //    path's own valid.
  input  logic              beat_observed,
  input  logic              beat_is_first,

  output logic [TS_W-1:0]   now,

  // ── The next expected first-beat cycle, and whether one is pending.
  output logic              expect_pending,
  output logic [TS_W-1:0]   expect_cycle,
  output logic [OCC_W-1:0]  outstanding,

  // ── THE two-sided classification. Exactly one is high when a first
  //    beat is observed. This is what no earlier block in the module
  //    produces, and §10's debugging turns entirely on it.
  output logic              beat_on_time,
  output logic              beat_early,
  output logic              beat_late,
  // Signed cycle error: negative early, positive late, zero on time.
  output logic signed [TS_W-1:0] beat_error,

  // ── An expected beat never arrived: `now` has passed the expected
  //    cycle with nothing observed. Distinct from "late" because a
  //    beat that never comes at all is a different failure from one
  //    that comes on the wrong cycle.
  output logic              expect_missed,

  // ── Two expectations for the same cycle. Should be impossible if
  //    tCCD is honoured (see the header); reported, not assumed.
  output logic              expectation_collision,
  // A read accepted with the pipeline full. Reported, never absorbed.
  output logic              overflow
);

  if (DEPTH < 1) begin : g_depth
    initial $fatal(1, "read_latency_contract: DEPTH must be >= 1");
  end
  if (TS_W < 2) begin : g_ts
    initial $fatal(1, "read_latency_contract: TS_W must be >= 2");
  end
  if (BURST_CYCLES < 1) begin : g_burst
    initial $fatal(1, "read_latency_contract: BURST_CYCLES must be >= 1");
  end

  // ── The expectation pipeline: one expected first-beat cycle per
  //    outstanding read, in order. A FIFO rather than a counter,
  //    because §6's several-outstanding case is the normal one.
  logic [TS_W-1:0]  exp_ts [DEPTH];
  logic [PTR_W-1:0] head, tail;
  logic [OCC_W-1:0] occ;

  logic signed [TS_W-1:0] err;
  logic                   have_exp;

  always_comb begin
    have_exp       = (occ != '0);
    expect_pending = have_exp;
    expect_cycle   = have_exp ? exp_ts[tail] : '0;
    outstanding    = occ;

    // Wrap-tolerant signed error, per Chapter 13.3 §7's discipline.
    err        = have_exp ? $signed(now - exp_ts[tail]) : '0;
    beat_error = err;

    // ── The two-sided classification. Only meaningful on the cycle a
    //    FIRST beat is observed; a mid-burst beat is not classified
    //    here because its position follows from the first.
    beat_on_time = beat_observed && beat_is_first && have_exp && (err == '0);
    beat_early   = beat_observed && beat_is_first && have_exp
                                 && ($signed(err) < 0);
    beat_late    = beat_observed && beat_is_first && have_exp
                                 && ($signed(err) > 0);

    // An expectation whose cycle has passed with no first beat seen.
    // Checked one cycle past the expected cycle so that a beat
    // arriving ON the expected cycle is never called missed.
    expect_missed = have_exp && ($signed(err) > 0)
                             && !(beat_observed && beat_is_first);

    overflow = read_accepted && (occ == OCC_W'(DEPTH));

    // Two outstanding expectations sharing a cycle. Compares the two
    // oldest, which suffices because expectations are created in
    // order with a common latency, so any collision involves
    // neighbours.
    expectation_collision = (occ >= OCC_W'(2))
                         && (exp_ts[tail] == exp_ts[(tail + PTR_W'(1))]);
  end

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

      // Push an expectation on an accepted read. NOTE: now + cl_cycles
      // with no ceiling and no conversion -- §4.
      if (read_accepted && (occ != OCC_W'(DEPTH))) begin
        exp_ts[head] <= now + cl_cycles;
        head         <= head + 1'b1;
      end

      // Retire the oldest expectation once its first beat is seen, or
      // once it has demonstrably been missed. Retiring on a miss is
      // what keeps one lost beat from misaligning every later
      // expectation -- a real failure mode in a FIFO-based checker.
      if (have_exp && ((beat_observed && beat_is_first)
                       || ($signed(err) > 0))) begin
        tail <= tail + 1'b1;
      end

      // Single occupancy update covering all four combinations, so the
      // push and retire paths cannot disagree.
      case ({ (read_accepted && (occ != OCC_W'(DEPTH))),
              (have_exp && ((beat_observed && beat_is_first)
                            || ($signed(err) > 0))) })
        2'b10:   occ <= occ + OCC_W'(1);
        2'b01:   occ <= occ - OCC_W'(1);
        default: occ <= occ;
      endcase
    end
  end

endmodule

Interface contract. cl_cycles is the programmed latency in cycles, with no conversion — §4 verified that CL is published as a cycle count, so a resolver on this port would be inventing a frequency dependence CL does not have. beat_on_time, beat_early and beat_late are mutually exclusive and meaningful only on a first-beat cycle.

Parameter contract, and the sizing argument. DEPTH must cover the maximum reads in flight, which §6 gives as roughly ceil(CL / tCCD) plus the burst. Undersizing it does not stall — it drops expectations, and a dropped expectation makes every later classification wrong, so overflow is reported rather than absorbed. OCC_W is $clog2(DEPTH + 1) because occupancy can equal DEPTH; PTR_W is $clog2(DEPTH) because an index cannot — the same ten-character distinction Chapter 14.8 §5 flagged.

Why retire on a miss. If an expected beat never arrives and the FIFO waits for it, every subsequent beat is matched against the wrong expectation and the error classification is garbage from then on. Retiring a demonstrably-missed expectation keeps one lost beat from corrupting the whole run — which matters because a checker that produces a cascade of spurious errors after the first real one is much harder to use than one that reports a single failure.

Corner cases. cl_cycles == 0: the expected cycle is the read cycle itself, which is not physically meaningful but is arithmetically handled. DEPTH == 1: PTR_W is 1 by the guard and a second read while one is outstanding reports overflow — correct for a non-pipelined configuration. Beat observed with no expectation pending: none of the three classifications asserts, because all three require have_exp; a spurious beat is not classified as early. Reset with data in flight: expectations abandoned, and in-flight beats will be unclassified.

Synthesis implications. DEPTH timestamp registers, two pointers, an occupancy counter, one subtractor and a comparator. Note that the signed comparisons are on the subtraction result, so there is one adder and three sign/zero tests rather than three comparators.

Failure modes. Gating on >= instead of == — the most consequential, because it converts a two-sided obligation into a one-sided one and silently accepts every late beat, which is precisely the class error §3 is about. Retiring only on an observed beat: one lost beat misaligns everything after. Putting a ns-to-cycle resolver on cl_cycles: invents a conversion and gets a different CL at each speed grade for a value that is configured in cycles. Sizing OCC_W as $clog2(DEPTH): occupancy of DEPTH truncates and the FIFO never reports full.

8. Four Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── P1. THE two-sided property: a first beat arrives EXACTLY on its
//    expected cycle. An equality, not an inequality -- which is the
//    whole difference from Chapters 14.1-14.8, every one of whose
//    safety properties was a one-sided comparison.
//    Catches a >= where == belongs, which silently tolerates late
//    data and is the class error of §3.
property p_first_beat_exactly_on_time;
  @(posedge clk) disable iff (!rst_n)
    (beat_observed && beat_is_first && expect_pending)
      |-> (now == expect_cycle);
endproperty
a_first_beat_exactly_on_time: assert property (p_first_beat_exactly_on_time);

// ── P2. The expectation is computed as read + CL with NO conversion.
//    Catches a resolver or a ceiling inserted on cl_cycles -- §4's
//    point that CL has no absolute-time term and therefore nothing to
//    resolve. $past with an explicit 1.
property p_expectation_is_read_plus_cl;
  @(posedge clk) disable iff (!rst_n)
    ( $past(read_accepted, 1) && ($past(outstanding, 1) == '0) )
      |-> (expect_cycle == ($past(now, 1) + $past(cl_cycles, 1)));
endproperty
a_expectation_is_read_plus_cl: assert property (p_expectation_is_read_plus_cl);

// ── P3. The three classifications are mutually exclusive and one of
//    them fires on every classified first beat. Catches overlapping
//    conditions, which would make a diagnostic report two
//    contradictory things -- worse than reporting nothing.
property p_classification_is_exclusive;
  @(posedge clk) disable iff (!rst_n)
    (beat_observed && beat_is_first && expect_pending)
      |-> ($countones({beat_on_time, beat_early, beat_late}) == 1);
endproperty
a_classification_is_exclusive: assert property (p_classification_is_exclusive);

// ── P4. No expectation is silently dropped: an accepted read either
//    enters the pipeline or is reported as an overflow. Catches the
//    "absorb it quietly" shortcut, which corrupts every later
//    classification rather than just this one.
property p_no_silent_expectation_drop;
  @(posedge clk) disable iff (!rst_n)
    read_accepted |-> ((outstanding < OCC_W'(DEPTH)) || overflow);
endproperty
a_no_silent_expectation_drop: assert property (p_no_silent_expectation_drop);

What these prove. That first beats land exactly on their expected cycle — an equality, which is the structural signature of a class-B obligation; that the expectation is read + CL with no conversion; that the classification is well-formed; and that no expectation is dropped.

What these do not prove, and the first item is a genuine and important limitation.

They cannot tell a wrong CL from a PHY capture error. Both present as a beat on an unexpected cycle. If the controller is programmed with a CL of 22 and the device with 21, P1 fires — and it fires identically if CL is correct everywhere and the capture path samples a cycle late. This block sees cycle numbers, not causes. Distinguishing them requires Modules 19–21's PHY visibility, and §10's debugging table says which evidence separates them.

They do not prove the programmed CL is the right value, nor that the controller and device agree — which §4 identified as a failure mode unique to programmed parameters. Checking that requires reading back the device's mode register and comparing, which is a configuration check rather than a timing one.

Vacuity. P1 and P3 require an observed first beat with an expectation pending; P2 additionally requires an empty pipeline. A run that never issues a read passes all four. Cover beat_on_time, and deliberately inject an early and a late beat — a classifier never shown to fire on a wrong cycle is not known to work.

9. The Contract, in Cycles

read_latency_contract — exactly then, three deep

10 cycles
Ten cycles with an educational CAS latency of four. Reads are accepted at cycles one, three and five, and each pushes an expectation of its own read cycle plus four, giving expected first-beat cycles of five, seven and nine. Outstanding expectations rise to two and then three as the reads pipeline. The first data beat for the first read arrives exactly at cycle five and is classified on time with a zero cycle error. The second read's first beat arrives at cycle seven, also on time. The third read's beat arrives at cycle eight, one cycle before its expected cycle of nine, and is classified early with a cycle error of minus one — which in a real system means the capture path would read a beat that has not arrived.pipelining readspipelining readsbeats on timebeats on timewrong cyclewrongcycleread@1 + CL 4 = 5 — on timeread@1 + CL 4 = 5 — on timeread@3 + 4 = 7 — on timeread@3 + 4 = 7 — on timeexpected 9 — EARLY by 1expected 9 — EARLY by 1CKnow0123456789read_accexpect_cycle0055557799outstanding0011232211first beaton_timeearlybeat_error00000000-10t0t1t2t3t4t5t6t7t8t9

Three observations.

Cycle 5 and cycle 7 are on_time because the arithmetic is an equality. Nothing in this trace is “early enough” or “within the window” — the beat is on its cycle or it is not.

Three expectations are outstanding at cycle 5. That is §6's pipeline, and it is the normal operating condition rather than an unusual one. A countdown-shaped block could not represent it.

Cycle 8 is the case that no earlier chapter could produce. A beat arrives before its expected cycle, with beat_error of −1. In every one of chapters 14.1 through 14.8, arriving later than required was safe and arriving early was the only failure. Here early is a failure and so is late, and the classifier reports which.

10. Debugging

Symptom. Read data is wrong — not missing, wrong. The bus was active, the commands were legal, and the captured values do not match what was written.

Candidate mechanismEvidenceDiscriminator
Controller and device CL disagreeEvery read off by the same constant; beat_error identical on allRead the device's mode register back and compare with the controller's configured value. §4's programmed-parameter failure mode. Constant error across all reads is the fingerprint.
Capture path off by one cyclebeat_error constant at ±1Indistinguishable from the above inside this block — see §8. The discriminator is the mode-register readback: if the two agree, the error is in the capture path.
CL treated as a minimum (>= not ==)Late beats silently accepted; errors only when a beat is early§7's headline failure. Check whether beat_late ever fires — a design that never reports late beats may not be testing for them.
CL put in the issue-legality conjunctionReads delayed by CL for no reason; no data errors§2. Throughput short by roughly CL per read with everything functionally correct.
A resolver applied to cl_cyclesError changes with speed grade§4 — CL has no conversion. If the configured value differs across grades in a way the table does not, something is resolving it.
DEPTH undersizedErrors begin only under sustained read burstsoverflow. A dropped expectation misaligns everything after it.
Expectation not retired on a missOne real error followed by a cascade§7. The first error is genuine; the rest are artefacts. Fix the first and re-run before investigating the rest.

The discriminator that matters most is whether beat_error is constant. A constant error across every read means a configuration disagreement — the controller and the device have different numbers, or the capture path has a fixed offset. A varying error means something dynamic: a dropped expectation, an undersized pipeline, or a genuine data-path problem. Constant points at configuration, varying points at flow control, and that splits the table in half on one glance.

And the second: does beat_late ever fire? A design that has never observed a late beat may simply have no way to detect one. §8's vacuity note says to inject one deliberately, and this row is why.

Responsible layer. If beat_error is zero throughout and data is still wrong, the problem is not CL — it is the data path, the mask, or the address, and this block has correctly exonerated the latency contract. That is a useful negative result, and it is worth checking early because CL is where people look first.

11. Common Misconceptions

“CL tells the controller when a READ may issue.” Tempting because CL is the most-quoted memory timing number and sits beside tRCD in every table. Why it is wrong: §2 — CL's constrained event is data the device produces, not a command the controller issues. Issue legality involves tRCD, tCCD and bank state; CL is not a term. Consequence: a controller that delays each read by CL for no reason — functionally correct, throughput short by roughly CL per read. Replacement model: tRCD says when you may act; CL says what will happen to you. Debugging clue: no data errors, and read throughput short by a constant per-read amount.

“CL is a minimum, so data arriving later is fine.” Tempting because the previous eight parameters were all minimums and “latency” sounds like a bound. Why it is wrong: §3 — it is two-sided. The device drives at the configured offset regardless of readiness; a capture path merely ready by that cycle misses the beat. Consequence: wrong data rather than a stall, presenting as intermittent corruption that looks like a signal-integrity problem. Replacement model: class B, an equality, with no safe direction. Debugging clue: corruption rather than stalls, and a design whose late-beat detector has never fired.

“CL is a physical property of the device.” Tempting because it appears in the speed-bin table alongside genuinely physical parameters. Why it is wrong: §4 — it is published as a bare cycle count, not in nanoseconds, because it is a configured pipeline depth. It is written into the device. Consequence: a controller that treats it as read-only never checks that its own value matches what it programmed, so a mismatch goes undetected until data is wrong. Replacement model: one number in two places that must agree. Debugging clue: a constant beat_error across every read.

“A CL of 22 is worse than a CL of 14.” Tempting because the numbers are directly comparable and lower is better within a grade. Why it is wrong: CL is in cycles, so the absolute latency depends on the clock. A CL of 22 at DDR4-3200 is 13.75 ns; a CL of 14 at DDR4-1600 is 17.5 ns. The larger CL is the faster part. Consequence: parts compared on the wrong axis, and a real regression when a “better” CL is chosen at a slower rate. Replacement model: a CL without a data rate is not a fact. Debugging clue: a latency comparison that omits the data rate.

“CL needs an ns-to-cycle conversion like tRCD.” Tempting because four of the module's parameters do and the habit generalises. Why it is wrong: §4 verified CL is published in cycles. There is no division, no ceiling, and nothing to resolve. Consequence: a design that resolves it anyway invents a frequency dependence CL does not have and gets a different CL at each speed grade. Replacement model: physical requirements resolve; configured depths do not. Debugging clue: the configured CL varying across grades in a way the table does not.

“One outstanding read at a time, so a counter suffices.” Tempting because every earlier chapter's obligation was one-at-a-time per resource. Why it is wrong: §6 — tCCD permits reads much closer together than CL, so several expectations are normally in flight; the depth is roughly ceil(CL / tCCD) plus the burst. Consequence: a single-expectation tracker that drops expectations under sustained reads, misaligning every classification after the first drop. Replacement model: a pipeline, not a counter. Debugging clue: correct at low read rates, wrong under bursts.

12. Interview Reasoning

“Why is CL not the same kind of parameter as tRCD?” Different class and different edge. tRCD is a minimum separation governing whether a READ may be issued — one-sided, so waiting longer is always legal. CL is a fixed programmed latency governing when data appears after an accepted read — two-sided, because the device drives at the configured offset whether or not anything is ready. The evidence is in the datasheet: Micron's DDR4 speed-bin table quotes tRCD in nanoseconds and CL as a bare cycle count, because one is a physical requirement and the other a configured pipeline depth.

“Does CL belong in a read's issue-legality conjunction?” No, and this is the cleanest test of whether someone has the class distinction. Issue legality is about permissions — tRCD, column spacing, bank state. CL is a prediction about the consequence of a permission already exercised. Putting it in the conjunction delays every read by CL for no reason: functionally correct, and throughput short by a constant per read with no error to point at.

“What happens if the capture path is ready one cycle late?” The beat is gone, and wrong data is captured. That is what two-sided means: there is no safe direction, so an off-by-one either way loses data rather than costing throughput. It is worth adding why this is harder to debug than a minimum-separation bug — it presents as intermittent corruption rather than as a stall, so it gets misattributed to signal integrity.

“Why can a CL of 22 be better than a CL of 14?” Because CL is in cycles and absolute latency depends on the clock. At DDR4-3200, tCK is 0.625 ns, so CL 22 is 13.75 ns; at DDR4-1600, tCK is 1.25 ns, so CL 14 is 17.5 ns. The larger number is the faster part. The general point is that a cycle-based parameter compared across data rates is meaningless — and that this is the opposite of the nanosecond parameters, whose cycle counts grow with frequency while their absolute requirement stays fixed.

“How deep must a read-expectation pipeline be?” Roughly ceil(CL / tCCD) plus the burst, because column spacing bounds how closely reads can be issued while CL bounds how long each takes to return. That is a nice connection between two chapters that look unrelated, and the failure mode if you undersize it is instructive: it does not stall, it drops expectations, and one dropped expectation misaligns every classification after it — so you get one real error followed by a cascade of artefacts.

“Your reads return wrong data with a constant one-cycle error. What is it?” A constant error across every read points at configuration rather than flow control: either the controller and the device hold different CL values, or the capture path has a fixed offset. Those two are indistinguishable from cycle numbers alone, so the discriminator is a mode-register readback — if the device's programmed CL matches the controller's, the offset is in the capture path. A varying error would point somewhere else entirely, at a dropped expectation or an undersized pipeline.

13. Engineering Exercises

1. Compute expected cycles. A controller issues reads at cycles 200, 204 and 206 with a programmed CL of 22. Give each read's expected first-beat cycle and the maximum number of expectations outstanding.

Worked: expected beats at 222, 226 and 228. All three reads are issued before the first beat returns, so three expectations are outstanding from cycle 206 to 221. That is the pipeline of §6 and the reason a counter cannot do this job.

2. Size the pipeline. With a CL of 22 and a tCCD of 4, how many reads can be outstanding? Add a burst of 8 transfers and give the required DEPTH.

Worked: reads can be issued every 4 cycles, and each takes 22 cycles to begin returning, so ceil(22 / 4) = 6 can be in flight before the first returns. A burst of 8 transfers occupies 4 cycles, so allow those too: DEPTH of at least 7, and rounding up to 8 is sensible. Undersizing does not stall — it drops expectations.

3. Compare across grades. A CL of 22 at DDR4-3200 and a CL of 16 at DDR4-2400. Which has lower absolute latency? Which would you choose and what else would you need to know?

Worked: at DDR4-3200, 22 × 0.625 = 13.75 ns. At DDR4-2400, 16 × 0.8333 = 13.33 nsthe DDR4-2400 part has marginally lower CAS latency in absolute terms. Which to choose depends on the workload: the DDR4-3200 part has higher bandwidth and slightly higher CAS latency, so a bandwidth-bound workload prefers it and a latency-bound one might not. You would also need the other parameters — tRCD and tRP at each grade — since CAS latency is one term in the total.

4. Classify the failures. For each observation, say whether it is a CL-class failure, a minimum-separation failure, or neither: (a) data arrives one cycle after the expected cycle; (b) a READ is refused for 22 cycles after an ACTIVATE; (c) read throughput is short by 22 cycles per read with correct data; (d) data arrives on the expected cycle but is wrong.

Worked: (a) CL-class — a two-sided violation, wrong data. (b) neither, and correct — that is tRCD doing its job. (c) a class confusion, not a timing violation: CL was put in the issue conjunction, per §2. (d) neither — the latency contract is satisfied, so look at the data path, mask or address. Recognising (d) as exoneration rather than as a CL problem is the point.

5. Find the one-sided bug. A design classifies a beat as acceptable when now >= expect_cycle. Construct the observation that passes wrongly, and say why the design may never notice.

Worked: a beat arriving at expect_cycle + 3 is accepted. The design never notices because it has no late detector at all — its check cannot distinguish on-time from late, so no test result ever differs. The bug is invisible until the data is wrong, and then the latency contract appears to be satisfied. §8's injection requirement exists for exactly this.

6. Explain the indistinguishability. §8 says this block cannot tell a wrong CL from a capture-path offset. Construct two scenarios producing identical outputs, then give the one measurement that separates them.

Worked: Scenario A — controller configured CL 22, device programmed CL 21: data arrives at read + 21, one cycle before expectation, beat_error of −1 on every read. Scenario B — both configured 22 correctly, but the capture path samples one cycle late relative to the bus: the observed beat appears at what the block computes as read + 23... and in fact whichever way the offset lies, the block sees a constant nonzero beat_error and cannot say why. The separating measurement is a mode-register readback: compare the device's programmed CL against the controller's. Agreement implicates the capture path.

7. Argue the classification outputs. §7 classifies rather than gates, and its header says the classification outputs are diagnostics rather than a functional path. Argue that a controller should gate on them, then rebut.

14. Summary

CL is the module's first fixed, programmed latencyChapter 13.3 §2's class B — and it breaks the pattern of the eight parameters before it in four connected ways.

It is two-sided. Data appears at read_cycle + CL, not at or after. Sampling early reads a beat that has not arrived; sampling late reads one that has gone. Both produce wrong data rather than a stall, so there is no conservative direction — which reshapes the RTL, the properties and the debugging.

It governs a different edge. tRCD constrains a command the controller chooses to issue; CL constrains data the device will produce. So CL does not belong in an issue-legality conjunction at all — it is not a permission but a prediction about the consequence of one already exercised. A controller that includes it delays every read for nothing.

It is published in cycles, and that is verified. Micron's DDR4 speed-bin tables head their columns Data Rate (MT/s) | CL | tRCD (ns) | tRP (ns) | tRC (ns) — CL with no unit, the others in nanoseconds. That heading, quoted since Chapter 14.1 §4, is the class distinction published: physical requirements in absolute time, configured pipeline depths in cycles. Three consequences follow — CL has no conversion and therefore no conversion bug; it is programmed, so the controller and device hold one number in two places that must agree; and a CL compared across data rates is meaningless, which is why a CL of 22 at DDR4-3200 is faster than a CL of 14 at DDR4-1600.

It is pipelined. Several reads are normally outstanding, because tCCD permits reads far closer together than CL takes to return. The depth is roughly ceil(CL / tCCD) plus the burst, and undersizing drops expectations rather than stalling — one drop misaligns every classification after it.

read_latency_contract tracks one expected cycle per outstanding read and classifies each first beat as on-time, early or late — the first block in the module to check both directions, because it is the first obligation with two of them. Its most important stated limitation is that it cannot distinguish a wrong CL from a capture-path offset, since both appear as a constant beat_error; the separating measurement is a mode-register readback, not a simulation.

And the debugging discriminator generalises: a constant error points at configuration, a varying error points at flow control.

15. What Comes Next

Chapter 14.10 closes the module with CWL, CL's write-direction counterpart — and it is not simply a mirror.

With a read, the device produces data and the controller receives it. With a write, the controller produces the data, which inverts the agency: the obligation is now on the controller to launch data at the configured offset, and the device is the recipient. Chapter 14.5 has already shown why that matters beyond symmetry — CWL is one of the terms in the projection that computes when write recovery begins, so an error in it moves a different parameter's deadline.

That closing chapter also has the module's synthesis work to do: the event-to-event matrix over all ten parameters, the cross-parameter consistency table, and the independent reference model that checks the whole set rather than one obligation at a time.

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.