Skip to content
VLSI Mentor

DDR · Module 6

CAS# — Column Address Strobe

A column command is where the data pipeline begins, so the interval from it to data arriving became the number everyone quotes. CAS latency is named after a signal that is no longer a dedicated pin.

Chapter 6.4 covered the trio's row half and the strobe-to-level transition. This chapter covers the column half, and it has by far the larger legacy.

Almost every engineer who has bought a memory module can quote a CAS latency. It is the number on the package, the first figure in any specification comparison, and the one DDR timing parameter that escaped into general usage.

It is named after this signal, and that is worth examining, because it raises a question the name alone does not answer:

Why did the interval from a column command to data become the definitive measure of memory latency — rather than the interval from a row command, which is the expensive one?

The answer is about asymmetry, and it explains more about memory-system behaviour than the number itself ever does.

1. The Asymmetry

Module 3 established the physical facts and Chapter 5.2 the structural ones. Put them together from the signalling side and the asymmetry is stark.

A row access is expensive, destructive and infrequent. Activating a row drives a wordline, dumps charge onto bitlines, and waits for sense amplifiers to resolve — Chapter 3.5's regenerative process, which takes as long as it takes. It is destructive, so it obliges a restore. And it produces a whole row held in the sense amplifiers.

A column access is cheap, non-destructive and repeatable. The row is already sitting in the sense amplifiers. A column access selects which bits of it move to the interface. Nothing is disturbed, nothing must be restored, and it can be repeated against the same open row indefinitely.

A row command is expensive, destructive and infrequent: it activates a wordline and waits for sense amplifiers to resolve, leaving a whole row held. A column command is cheap, non-destructive and repeatable: it selects which bits of the already-held row move to the interface, and it is the command after which data appears. One row command therefore serves many column commands, and the interval that defines memory latency is measured from the column command.Row commandexpensive, destructiveRow heldin the sense ampsInfrequentonce per rowColumn commandcheap, repeatableData pipeline startslatency measured hereMany per rowthe common caseproducesstartsenables12
Figure 1 — one row command serves many column commands; the second is where data begins.

One row command enables many column commands, and that ratio is the whole reason the row buffer matters. Chapter 5.2 §1 made the same point structurally; here it is visible as a relationship between two signals.

2. Why Latency Attached to the Column Command

Now the question the chapter exists for.

A row command produces no data. It opens a row. Nothing crosses the data bus as a result, and nothing can — the controller has not yet said which bits it wants.

A column command is the first moment at which data is determined. It names the column, so from that instant the device knows what to deliver. Everything after it is pipeline: internal routing, the prefetch organisation Chapter 4.2 §2 described, serialisation, and the interface itself.

So the interval from the column command to the first data beat is the interval that is genuinely fixed. It is a pipeline depth. It does not depend on what the array was doing, because the array's work is already complete.

3. What the Interval Is Made Of

Without consuming Module 10's read operation or Modules 13 and 14' parameters, the composition of the interval is worth naming, because it explains why the number has grown across generations while memory has got faster.

Internal routing. From the sense amplifiers through the column path — Chapter 5.3 §1's shared resource — toward the interface.

Prefetch and serialisation. Chapter 4.3 §1 established that prefetch depth is the frequency ratio between the interface and the core. A deeper prefetch means a wider internal fetch being serialised to a narrower, faster interface — and serialisation takes time proportional to depth.

Interface launch. Driving the data and its strobe with correct timing, which is PHY work.

Now the counter-intuitive part. CAS latency measured in cycles has grown substantially across generations. Measured in time, it has not grown nearly as much, and has often improved — because the cycles themselves got much shorter.

Why it grows in cycles at all: Chapter 4.1 §12 worked this out directly. The array's access time is set by physics that does not scale with the interface clock, so as the clock speeds up, the same physical duration occupies more cycles. A latency quoted in cycles is a ratio between a roughly fixed physical interval and a shrinking clock period.

Which makes cycle-count latency comparisons across generations meaningless without the clock period, and is the second-most-common misuse of this number after §2's.

4. RTL — The Column-Command-to-Data Window

Engineering problem

A column command commits the controller to a data transfer that will begin a fixed number of events later and occupy a defined number of beats. The controller must schedule around that window — and two column commands issued too close together produce overlapping data windows on a bus that can only carry one transfer at a time.

Model the commitment and detect the collision.

Classification

SYNTHESIZABLE RTL — an educational scheduling model.

What it represents: that a column command creates a future obligation on the data bus, that the obligation has a fixed offset and duration, and that overlapping obligations are a scheduling error the controller must prevent.

What it explicitly does not represent — and this is the important part: LATENCY_EVENTS is not CAS latency. It is not CL, not any JEDEC parameter, and not derived from any device. It is an educational cycle count chosen so the shape of the commitment is simulable. Real read latency is a per-device, per-speed-grade value composed of several specified parameters, and Modules 13 and 14 own it. Nothing here should be used to size anything.

Also absent: the array, the column path, prefetch, serialisation, the data itself, DQS, and any notion of read-versus-write turnaround — Chapter 6.9 owns bus ownership and this block deliberately does not duplicate it.

Interface contract

col_cmd with col_is_write issues a column command. cmd_accepted and collision report whether it was schedulable. data_window_active with beat_index marks the committed transfer window. pending exposes a command awaiting its window.

State

A countdown to the window's start, a beat counter within it, and the direction of the committed transfer.

Combinational behaviour

Collision detection — whether a new command's future window would overlap one already committed.

Sequential behaviour

The countdown and the beat counter.

How to simulate

vlog cas_to_data_window.sv tb_cas_to_data_window.sv then vsim -c tb_cas_to_data_window -do "run -all".

Expected result: an accepted command produces a data window exactly LATENCY_EVENTS later lasting BURST_BEATS beats; a command issued too soon after another is rejected with collision and creates no window.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// CAS TO DATA WINDOW.
// Classification: SYNTHESIZABLE RTL -- an educational SCHEDULING model.
//
// A column command commits the controller to a data transfer beginning a
// fixed number of events later and lasting a defined number of beats. The
// commitment is the point: the controller must schedule around a window
// that does not exist yet.
//
// LATENCY_EVENTS IS NOT CAS LATENCY. It is not CL, not any JEDEC
// parameter, and not derived from any device. It is an educational cycle
// count chosen so the SHAPE of the commitment is simulable. Real read
// latency is a per-device, per-speed-grade value composed of several
// specified parameters -- Modules 13 and 14 own it. Do not size anything
// from this.
//
// ALSO NOT MODELLED: the array, the column path, prefetch, serialisation,
// the data itself, DQS, and read/write bus turnaround. Chapter 6.9 owns
// data-bus ownership and this block does not duplicate it.
// ─────────────────────────────────────────────────────────────────────────
module cas_to_data_window #(
  // Events from column command to first data beat. EDUCATIONAL.
  parameter int LATENCY_EVENTS = 3,
  parameter int BURST_BEATS    = 4,
  parameter int LAT_W  = (LATENCY_EVENTS <= 1) ? 1 : $clog2(LATENCY_EVENTS + 1),
  parameter int BEAT_W = (BURST_BEATS    <= 1) ? 1 : $clog2(BURST_BEATS)
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              col_cmd,
  input  logic              col_is_write,

  output logic              cmd_accepted,
  // A new column command whose data window would overlap one already
  // committed. REPORTED, NEVER QUEUED -- queueing is the scheduler's job
  // (Module 17), and silently absorbing it would hide a scheduling bug.
  output logic              collision,

  output logic              pending,
  output logic              data_window_active,
  output logic [BEAT_W-1:0] beat_index,
  output logic              window_is_write
);

  // ── COMPILE-TIME legality.
  if (LATENCY_EVENTS < 1) begin : g_lat
    initial $fatal(1, "cas_to_data_window: LATENCY_EVENTS must be >= 1");
  end
  if (BURST_BEATS < 1) begin : g_beats
    initial $fatal(1, "cas_to_data_window: BURST_BEATS must be >= 1");
  end

  logic [LAT_W-1:0]  wait_q;
  logic [BEAT_W-1:0] beat_q;
  logic              window_q;
  logic              dir_q;

  assign pending            = (wait_q != '0);
  assign data_window_active = window_q;
  assign beat_index         = beat_q;
  assign window_is_write    = dir_q;

  // ── Collision detection.
  //
  //    A new command's window would start LATENCY_EVENTS from now and last
  //    BURST_BEATS. A command is schedulable only if nothing is already
  //    committed -- neither a pending countdown nor an active window.
  //
  //    This is deliberately CONSERVATIVE: a real scheduler can pipeline
  //    column commands so their windows abut exactly, which is how a
  //    memory interface achieves high data-bus utilisation at all. Modelling
  //    that properly needs the full timing rules of Modules 13/14, so this
  //    block refuses any overlap and says so rather than implementing a
  //    half-correct approximation.
  logic busy;
  assign busy = pending || window_q;

  assign cmd_accepted = col_cmd && !busy;
  assign collision    = col_cmd &&  busy;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wait_q   <= '0;
      beat_q   <= '0;
      window_q <= 1'b0;
      dir_q    <= 1'b0;
    end else begin
      if (cmd_accepted) begin
        // Commit. Direction is captured with the command because the
        // window's direction is decided at command time, not at data time
        // -- which is exactly why a controller must know, LATENCY_EVENTS
        // in advance, which way the data bus will be pointing.
        wait_q <= LAT_W'(LATENCY_EVENTS);
        dir_q  <= col_is_write;
      end else if (wait_q != '0) begin
        wait_q <= wait_q - LAT_W'(1);
        if (wait_q == LAT_W'(1)) begin
          // Countdown expiring opens the window on the next event.
          window_q <= 1'b1;
          beat_q   <= '0;
        end
      end else if (window_q) begin
        if (beat_q == BEAT_W'(BURST_BEATS - 1)) begin
          window_q <= 1'b0;
          beat_q   <= '0;
        end else begin
          beat_q <= beat_q + BEAT_W'(1);
        end
      end
    end
  end

endmodule

Cycle-by-cycle example

LATENCY_EVENTS = 3, BURST_BEATS = 4:

Cyclecol_cmdResultpendingdata_window_activebeat_index
01 (read)accepted00
11collision10
2010
30010
40011
50012
61collision013
71accepted00

Cycle 1 is the commitment made visible. The first command's data has not appeared and will not for two more events — and the bus is already spoken for. A controller that schedules against observed bus activity rather than against committed obligations will issue this command and produce a genuine conflict three cycles later.

Cycle 6 is the same lesson from the other side: the window is active, so the bus is visibly busy.

The direction is captured at cycle 0, not at cycle 3. A controller must know which way the data bus will be pointing LATENCY_EVENTS before it points that way — which is what makes Chapter 6.9's ownership problem a scheduling problem rather than a reactive one.

Waveform expectation

§5. Watch pending and data_window_active never overlap, and collision assert only while one of them is high.

Synthesis implication

Two small counters, a direction bit and a few gates. Trivial. A real controller's version is considerably richer — it tracks multiple outstanding column commands whose windows abut, which is how data-bus utilisation gets anywhere near the interface's capability. This block refuses overlap entirely and says so, because modelling the pipelined case correctly requires the timing rules Modules 13 and 14 own.

Corner cases

LATENCY_EVENTS == 1 opens the window on the event after the command — the minimum meaningful commitment. BURST_BEATS == 1 gives a single-beat window and BEAT_W == 1 through the guard. A command arriving on the window's final beat is rejected, which is conservative: a real interface can start the next window immediately there, and the comment says why this block does not. Reset clears the commitment, which correctly models that a reset invalidates outstanding transfers rather than completing them.

Debugging clues

If windows overlap, the collision test is checking window_q alone rather than pending || window_q — the pending case is the one that matters, because that is the obligation not yet visible on the bus. If the window opens one event early or late, check the wait_q == 1 comparison against the load value; an off-by-one here shifts every transfer and looks exactly like a latency misconfiguration. If the direction is wrong for a transfer, check that dir_q is captured at cmd_accepted and not sampled when the window opens — by then the command's direction input has long since changed.

Limitations

No real timingLATENCY_EVENTS is educational, as the header insists. No pipelining of column commands, which is the single largest simplification and is stated in the code. No array, no prefetch, no serialisation, no data. No DQS and no bus turnaround cost — two transfers in opposite directions are treated identically here, and Chapter 6.9 shows why that is not true.

5. Commitment in Cycles

cas_to_data_window — the committed window, and two collisions

10 cycles
Ten cycles. A column command is accepted and begins a countdown; during the countdown the data bus shows nothing but is already committed, so a second column command is rejected as a collision. The data window then opens for four beats. A command issued during the window is also rejected. Once the window closes a further command is accepted.committed but invisiblecommitted butinvisiblefour-beat data windowfour-beat data windowbus committed, not busybus committed, not busywindow openswindow openswindow visibly busywindow visibly busyCKcol_cmdcol_is_writecmd_acceptedcollisionpendingdata_windowbeat_index------0123------t0t1t2t3t4t5t6t7t8t9
Figure 2 — a column command commits the data bus before anything appears on it.

Cycles 1 and 2 are the interesting region. data_window is low, beat_index shows nothing, and the data bus looks completely idle. It is not — it is committed, and the collision at cycle 1 proves it.

That gap between committed and busy is the whole scheduling problem of a memory controller. A scheduler that reasons about what the bus is doing now is reasoning about the wrong thing; it must reason about what the bus has already been promised to do. Chapter 5.7 §3 traced a request through the hierarchy; this is where the "and then it waits" part becomes a resource reservation.

Cycle 6's collision is the easy case — the window is active and visibly so. Cycle 1's is the one that catches people.

Representative educational cycles. The three-event latency and four-beat burst are chosen for legibility. Real values are per-device and per-speed-grade.

6. Three Assertions Worth Writing

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

// P1 -- an accepted command produces its window exactly LATENCY_EVENTS
// later. The fixed offset is the architectural claim: the interval is a
// pipeline depth and does not depend on history.
property p_window_at_fixed_offset;
  @(posedge clk) disable iff (!rst_n)
    cmd_accepted |-> ##LATENCY_EVENTS data_window_active;
endproperty
assert property (p_window_at_fixed_offset);

// P2 -- THE COMMITMENT PROPERTY. No command is accepted while an earlier
// one's window is still owed -- including while it is merely PENDING and
// the bus looks idle. A design that checked only the active window would
// pass every test where commands are far apart and fail exactly where
// scheduling matters.
property p_no_accept_while_committed;
  @(posedge clk) disable iff (!rst_n)
    (pending || data_window_active) |-> !cmd_accepted;
endproperty
assert property (p_no_accept_while_committed);

// P3 -- windows never overlap, stated on the output rather than on the
// acceptance logic, so a bug in the countdown cannot hide behind the same
// condition that caused it.
property p_pending_and_window_exclusive;
  @(posedge clk) disable iff (!rst_n)
    !(pending && data_window_active);
endproperty
assert property (p_pending_and_window_exclusive);

// P4 -- the window lasts exactly BURST_BEATS and the index walks. Catches
// the off-by-one that shifts every transfer and presents as a latency
// misconfiguration.
property p_beats_walk;
  @(posedge clk) disable iff (!rst_n)
    (data_window_active && (beat_index != BEAT_W'(BURST_BEATS-1)))
      |=> (data_window_active
           && (beat_index == ($past(beat_index) + BEAT_W'(1))));
endproperty
assert property (p_beats_walk);

P2 is the property worth internalising, and the reason is in its antecedent: it includes pending. A checker written against data_window_active alone is satisfied by a design that permits a second command during the countdown — and that design works perfectly in any test where commands are issued far apart, which is most directed tests. The bug lives precisely where the scheduler is being aggressive, which is where the performance is.

P3 states the same invariant on the outputs rather than on the decision, which matters because a countdown bug would corrupt both the decision and the outputs consistently. A property that shares its expression with the logic it checks proves only that the logic equals itself.

What none of them prove. Nothing about real latencyLATENCY_EVENTS is an educational parameter and no property here validates it against anything. Nothing about data correctness, since there is no data. Nothing about DQS or bus turnaround, which is Chapter 6.9's. And nothing about whether the array could actually supply the data — Chapter 5.2 §4 established that a column command serves whatever row is open, and this block has no idea whether that is the right one.

7. Where CAS# Went

In DDR4, the pin is CAS_n/A15, multiplexed exactly as Chapter 6.4 §7 described for RAS#, with ACT_n selecting: low means the pin carries row-address bit A15, high means it carries CAS_n.

In DDR5 the command/address interface is encoded differently again, and CAS# does not appear.

The term "CAS latency" survived both changes, and continues to be the standard way to describe read latency. That is not sloppiness — it is a name for an interval that remains real and central, attached to a signal that no longer bounds it. Exactly the pattern Chapter 6.4 §3 identified: interfaces inherit vocabulary faster than they inherit mechanisms.

The practical caution is narrow. Using "CAS latency" to mean read latency is standard and fine. Reasoning from it to the existence or behaviour of a CAS# pin on a DDR4 or DDR5 device is not, and it is the error that produces monitors decoding phantom commands — Chapter 6.4 §9's first mechanism.

8. Common Misconceptions

"CAS latency is how long a memory access takes." Wrong model: the quoted number describes the time from a request to its data. Why it is tempting: it is the only latency number most parts advertise, it is genuinely well-defined, and it is measured in a unit that sounds like access time. Consequence: performance predictions wrong by large and variable factors. The number excludes everything before the column command — and Chapter 5.2 established that a row conflict costs a precharge and an activate before any column command can be issued. Two parts with identical CAS latency can perform very differently because the difference lives entirely in what the number omits. Correct model: it is the interval from a column command to the first data beat — the last and cheapest stage. The expensive, state-dependent part is the row access, and it is not in the number. There is no single number for "how long does a memory access take", which is Chapter 1.8's thesis. Prevention: ask what must have already happened for the column command to be issuable. If a row had to be closed and another opened, none of that is counted.

"A higher CAS latency part is slower." Wrong model: the cycle count is directly comparable across parts and generations. Why it is tempting: lower numbers looking better is a reasonable default, and within one speed grade it is roughly true. Consequence: comparing cycle counts across generations or speed grades and reaching the opposite of the correct conclusion. A latency in cycles is a ratio between a roughly fixed physical interval and a shrinking clock period — so as clocks speed up, the same physical duration occupies more cycles. Correct model: convert to time before comparing. Cycle counts across different clock periods are not comparable, and the growth in quoted CAS latency across generations largely reflects shorter cycles rather than slower memory. Prevention: multiply by the clock period. If the two parts' periods differ, the raw cycle counts say nothing.

"CAS# is what fetches the data." Wrong model: the column strobe causes data to be retrieved from the array. Why it is tempting: data does appear after it, and "strobe" suggests causation. Consequence: misunderstanding why the interval is fixed rather than variable, and expecting it to depend on the array's state. It does not — the array's work was already done by the row command. Correct model: the row is already held in the sense amplifiers. A column command selects which bits of it move, and the interval afterwards is internal routing, serialisation and interface launch — a pipeline depth, not an array access. That is exactly why it is deterministic and therefore quotable. Prevention: ask what the array is doing during the interval. Nothing — it finished before the column command was issued.

"DDR4 still has a CAS# pin, since we still talk about CAS latency." Wrong model: the term implies the signal. Why it is tempting: the vocabulary is universal and current, so the signal seems like it must be. Consequence: the phantom-command decode of Chapter 6.4 §9 — a monitor reading CAS_n/A15 as a command bit while it is carrying an address bit during every activate. Correct model: the interval is real and current; the dedicated pin is not. DDR4 multiplexes it as CAS_n/A15 under ACT_n's control, and DDR5 encodes commands differently again. A term outliving its signal is normal and harmless until you reason from the term back to hardware. Prevention: separate "is this term current" from "is this pin present". The answers differ here.

9. Debugging — Read Data Arrives at the Wrong Time

Symptom. A memory interface returns data, but the controller captures it at the wrong moment — shifted by a consistent number of events. Data values are intact; their alignment is wrong.

Consistent shift with intact values is a strong signature: it is an offset fault, not a margin fault, and Chapter 5.6 §9 established that the distinction is available from the symptom alone. A constant error everywhere is a number, and numbers come from configuration.

Mechanism 1 — the configured read latency does not match the device. Inspect: the controller's latency configuration against the device's actual mode-register setting. Expected evidence: a mismatch equal to the observed shift. Discriminator: does the shift equal a configuration difference? This is first because it is a register comparison costing nothing, and because it is overwhelmingly the most common cause.

Mechanism 2 — an additional interval was not accounted for. Inspect: whether a module buffering layer is present and whether its latency is in the controller's model. Expected evidence: a shift equal to the buffer depth on registered or load-reduced modules. Discriminator: is the shift equal to a buffer's contribution? Chapter 5.6 §9's first mechanism, appearing here from the data side — and the two paths are configured separately, so accounting for the command path and not the data path produces exactly this.

Mechanism 3 — the round-trip was never trained. Inspect: whether read training ran and what margin it reported. Expected evidence: a shift that varies between ranks or between systems with identical configuration. Discriminator: does the shift differ per rank? Chapter 4.4 §3 established that the round-trip delay depends on the assembled board and must be measured, not computed — so a per-rank difference points at training rather than at configuration.

Mechanism 4 — the controller scheduled against observed rather than committed bus state. Inspect: whether two column commands were issued whose windows overlap. Expected evidence: corruption rather than shift, concentrated where commands are close together. Discriminator: is the data shifted or is it wrong? §4's pending case: a scheduler that reasons about a bus that looks idle will issue into a committed window, and the result is two transfers colliding — not a clean offset. This mechanism is distinguishable from the others by the symptom shape alone.

Mechanism 5 — not latency: the wrong data was fetched. Inspect: whether the values are correct-but-misaligned or simply wrong. Expected evidence: plausible data from the wrong address. Discriminator: are the values right? Chapter 5.2 §4's silent row mismatch produces correct-looking data from the wrong row, with correct timing — the opposite signature, and a completely different investigation.

Discrimination, cheapest first. Ask whether the data is shifted or wrong — one question separating mechanisms 4 and 5 from the rest. Then compare the controller's configured latency against the device's mode register. Then check whether the shift equals a buffering layer's contribution. Then compare the shift across ranks.

The reasoning lesson. The composition of a latency is a sum of contributions from different layers, and a shift equal to any one of them points at that layer. Device latency, module buffering and board round-trip are all additive and all configured or measured separately — so measuring the size of the shift is itself the diagnostic, before any hypothesis about cause. Engineers who treat "data arrives at the wrong time" as a single problem search all three layers at once; engineers who first measure how wrong usually only need to search one.

10. Interview Reasoning

"Why is memory latency named after the column command rather than the row command?" Because the column command is the first moment at which the data is determined, and the interval after it is fixed. A row command produces no data at all — it opens a row — and how long a request takes overall depends entirely on what was already open, so there is no single number for it. Once a column command is issued, the array's work is already done and everything remaining is pipeline: internal routing, serialisation from the prefetch width to the interface width, and interface launch. That interval is deterministic and history-independent, which is what makes it quotable and comparable. The cost is that the number describes the last and cheapest stage of an access, and gets read as though it described the whole thing.

"Why has CAS latency grown across generations if memory has got faster?" Because it is quoted in cycles, and a cycle count is a ratio between a roughly fixed physical interval and a shrinking clock period. The internal work — routing out of the sense amplifiers, serialising a wide internal fetch to a narrow fast interface — is set by physics that does not scale with the interface clock. So as the clock speeds up, the same physical duration occupies more cycles. Measured in time rather than cycles, the interval has not grown nearly as much and has often improved. The practical consequence is that comparing cycle counts across generations or speed grades without converting to time gives the wrong answer.

"What is actually happening during the interval after a column command?" Not an array access — that already happened. The row is sitting in the sense amplifiers, held there by the row command. What happens during the interval is internal routing from the sense amplifiers through the column data path, serialisation from the prefetch width down to the interface width, and the PHY launching the data with its strobe. That composition is why the interval is fixed: none of it depends on what the array was doing, because the array is finished.

"Does a DDR4 device have a CAS# pin?" Not a dedicated one. The ball is CAS_n/A15, multi-function, with ACT_n selecting: low means it carries row-address bit A15, high means it carries CAS_n. DDR5 encodes commands differently again and has no such signal. The term "CAS latency" survived both changes because it names an interval that is still real and central — which is fine, until someone reasons from the term back to the hardware and writes a monitor that decodes those pins as command bits during activates, when they are carrying address.

"Read data is consistently arriving a few cycles off. How do you narrow it down?" First by asking whether the data is shifted or actually wrong, because those are different faults. Intact values with wrong alignment is an offset problem, and a constant offset is a number — which means it comes from configuration rather than from margin, and the investigation is a register comparison rather than a measurement. So I would compare the controller's configured read latency against the device's mode register, then check whether the shift happens to equal a module buffering layer's contribution, since registered and load-reduced modules add a fixed stage and the command and data paths are configured separately. If the shift differs between ranks with identical configuration, that points at read training instead, because the board round-trip has to be measured rather than computed. And if the data is wrong rather than shifted, it is a different problem entirely — most likely a column access served by the wrong open row, which produces plausible data at correct timing.

11. Engineering Exercise

Educational values throughout; no specification figures are implied.

1. A workload's accesses are all row hits. Which interval dominates their latency, and is it the quoted number? The column-command-to-data interval — and yes, for this workload the quoted number is close to the truth, because no row work is needed. This is the best case and the only case the number describes well.

2. The same workload now alternates between two rows in one bank. What changed? Every access is a row conflict: a precharge and an activate must complete before a column command can even be issued. The quoted latency is unchanged and the actual latency is much larger. Two systems with identical CAS latency, differing only in access pattern, now perform very differently — which is why §8's first misconception is expensive.

3. §4's model rejects a command at cycle 1 when the bus shows nothing. Justify the rejection. The bus is committed, not idle. The first command's data window opens at cycle 3 and a second command issued at cycle 1 would open its window at cycle 4 — overlapping. A scheduler reasoning about observed bus activity would issue it; one reasoning about committed obligations would not. The distinction between committed and busy is the whole of a memory scheduler's job.

4. Why does §4 capture col_is_write at command time rather than when the window opens? Because by the time the window opens, the command's direction input has long since changed — it belongs to whatever command is being presented now. More fundamentally, the controller must know which way the data bus will point LATENCY_EVENTS before it points that way, which makes bus direction a scheduled property rather than a reactive one. Chapter 6.9 builds on exactly this.

5. Part A: latency 16 cycles at a 0.5 ns clock period. Part B: latency 22 cycles at a 0.3 ns period. Which has lower read latency? A: 16 × 0.5 = 8.0 ns. B: 22 × 0.3 = 6.6 ns. Part B, despite the larger cycle count. Cycle counts across different clock periods are not comparable, and the instinct that a bigger number is worse gets this backwards.

6. A colleague proposes reducing memory latency by choosing parts with lower CAS latency. What would you suggest measuring first? The row hit rate. If most accesses are row conflicts, the dominant cost is the precharge-and-activate sequence that the CAS latency figure entirely excludes, and improving the quoted number moves a small component of a large total. Chapter 5.7 §3's question applies: which level is blocking, and how often — and address mapping (Module 18) is usually the cheaper lever than part selection.

12. Summary

A column command is where the data pipeline begins, and that is why memory latency is named after it.

The asymmetry is the reason. A row access is expensive, destructive, infrequent, and leaves a whole row held in the sense amplifiers. A column access is cheap, non-destructive, and repeatable against that held row — so one row command serves many column commands.

A row command produces no data, because the controller has not yet said which bits it wants. The column command is the first moment the data is determined, and everything after it is pipeline: internal routing, serialisation from the prefetch width to the interface width, and interface launch.

So the interval is a pipeline depth, not an array access — which makes it deterministic and history-independent, and therefore quotable and comparable. The interval before it is state-dependent and is not in the number: a row conflict costs a precharge and an activate before any column command can issue, and none of that appears. A figure that is precise and narrow invites being read as broad, and this is the canonical example.

Cycle counts are not comparable across clock periods. Quoted CAS latency has grown across generations largely because the same roughly fixed physical interval occupies more of a shrinking cycle.

And a column command commits the data bus before anything appears on it. §4's model makes that visible: a bus that looks idle can already be spoken for, and a scheduler reasoning about observed activity rather than committed obligations will collide. The direction is also committed at command time, which makes bus ownership a scheduled property — the foundation Chapter 6.9 builds on.

In DDR4 the pin is CAS_n/A15, multiplexed under ACT_n; in DDR5 the encoding changed again. The term outlived the signal by two generations, which is harmless until someone reasons from the term back to the hardware.

13. What Comes Next

Chapter 6.6 closes the trio, and it is where the three chapters' real argument arrives.

WE# selects direction — write or read — which is the smallest of the three jobs and the easiest to describe. But three signals give eight encodings, and that is the whole command space the legacy scheme had. As arrays grew, row addresses needed more bits; as features accumulated, commands needed more encodings. Both pressures point at the same scarce resource.

DDR4's answer — reusing the command pins as address bits under ACT_n's control — is the clearest demonstration in this module of 6.1's thesis, and 6.6 works it out properly.

Return to RAS# for the trio's row half and the strobe-to-level transition, Banks for the row state a column command depends on, or DDR2 for the prefetch organisation that fills part of this interval. The full path is on the DDR tutorials index.

Continue learning

Standards & specifications

Governing standard
JEDEC JESD79 (DDR SDRAM)(opens JEDEC Solid State Technology Association in a new tab)

Defines the DDR SDRAM device itself — signals, command encoding, mode registers, timing parameters and the initialisation sequence — one document per generation. Memory-controller microarchitecture, address-mapping policy, PHY training algorithms and board-level design are not specified by it.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the DDR curriculum.