DDR · Module 14
CWL — CAS Write Latency
CWL inverts CL's agency: the controller produces the data, so the obligation is on the controller to launch it at the configured offset — and it is the term that sets when write recovery begins.
Chapter 14.9 established CL as a fixed, programmed latency — two-sided, in cycles, governing data the device produces. CWL is its write-direction counterpart, and it is not simply a mirror:
With a read, the device produces the data and the controller receives it. With a write, the controller produces the data and the device receives it.
That inversion of agency changes who owes the obligation, which changes what can go wrong and who can detect it. And CWL carries a second job no other parameter in this module has: Chapter 14.5 showed it is a term in the projection that computes when write recovery begins, so an error in CWL moves a different parameter's deadline.
This is also the module's last chapter, so it closes with the synthesis work: 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.
1. The Obligation, in Five Parts
PARAMETER CWL (CAS write latency)
1 triggering event a WRITE command accepted
2 constrained event the FIRST write data beat — launched BY THE
CONTROLLER
3 resource the data interface and the device's write path
4 magnitude in CK CYCLES — programmed, not physical
5 sense FIXED LATENCY — two-sided
─────────────────────────────────────────────────────────────────
consequence controller must launch first write data AT
write_cycle + CWL — not at or afterStructurally identical to CL's five parts, with part 2's agency reversed. Everything interesting follows from that one word: launched.
The follow-up questions:
What re-arms it? Nothing in the minimum-separation sense; each accepted WRITE creates its own launch obligation, and several can be outstanding — a pipeline, as Chapter 14.9 §6 established for reads.
What can dominate it? Nothing. Like CL, it is not a lower bound and does not participate in Chapter 13.3 §3's maximum. It does not constrain whether the WRITE may issue — that is tRCD and tCCD.
Cycle or absolute-time component? Cycles, programmed, like CL.
Does generation matter? Not for the structure.
2. The Agency Inversion
Put the two latencies side by side, because the difference is entirely in who acts.
READ accepted ──── CL ────▶ DEVICE drives data
controller must be READY to capture
WRITE accepted ──── CWL ────▶ CONTROLLER drives data
device will be ready to captureWith CL, the controller is the recipient. The device commits; the controller must be ready. If the controller is not ready, it loses data that was correctly driven.
With CWL, the controller is the producer. The controller commits; the device will be ready at the configured offset. If the controller launches on the wrong cycle, the device captures the wrong thing — or captures nothing and leaves the intended location holding stale data.
Verified evidence for the class, the same as Chapter 14.9 §4's:
3. The PHY Boundary — Stated Sharply
This is the most easily crossed boundary in the module, and crossing it produces RTL that teaches a fiction.
What a controller owes. To present write data to its own interface such that it is launched at the configured offset from the accepted WRITE, measured in cycles.
What a controller does not own. How that data is physically driven onto the bus — the strobe, its preamble, its phase relationship to the data, the skew between lanes, the training that established any of it. Modules 19–21 own every part of that.
4. CWL Inside Another Parameter's Deadline
Chapter 14.5 built this and this chapter completes the loop from the other end.
last_write_data_cycle = write_cmd_cycle + CWL + burst_cycles − 1
tWR deadline = last_write_data_cycle + resolved(tWR)CWL appears in a deadline for a parameter that is not CWL. So consider a controller whose CWL is one cycle too large:
the controller launches write data one cycle LATE
→ the device captures on the wrong cycle (a CWL failure)
AND the projected last-data cycle is one cycle late
→ the tWR deadline is one cycle late
→ precharge is held one cycle longer than needed (a tWR slowdown)One wrong number, two symptoms, in two different parameters' behaviour. And the two have opposite severities: the launch error corrupts data, while the deadline error merely costs a cycle. An engineer who finds the tWR slowdown first will be investigating the wrong parameter.
5. RTL — A Controller-Side Launch Obligation
Collision check. Chapter 14.9's read_latency_contract classifies observed read beats against expectations — the recipient side. Chapter 11.x's write_beat_sequencer and write_launch_pipeline own write-data sequencing. Chapter 14.5's twr_deadline_projector projects the recovery trigger. Chapter 6.5's cas_to_data_window models a column command's window.
What none of them does is track the controller's own launch obligation and report a mislaunch. read_latency_contract checks something the device owed; this checks something the design under test owes, which §2 argued is a different verification problem because there is no external authority to compare against.
The engineering problem. For each accepted WRITE, compute the cycle at which this controller must launch its first data beat, assert the launch on exactly that cycle, and report when a launch happens on any other cycle — in either direction.
Classification: controller-side sequential launch scheduling, with self-checking outputs. Counts cycles, compares cycle numbers.
What it does not model: any electrical behaviour. No strobe, no preamble, no phase, no skew, no training. §3.
// ─────────────────────────────────────────────────────────────────────
// write_launch_contract
//
// CLASSIFICATION
// Controller-side sequential launch scheduling with self-checking
// outputs. Counts cycles and compares cycle numbers.
//
// WHAT IT MODELS
// §1's obligation, with the agency of §2:
// trigger = WRITE accepted on cycle N
// constrained = the controller's OWN first write data beat,
// AT cycle N + CWL
// sense = FIXED LATENCY (class B) -- two-sided
// pipelined, because several writes can be outstanding.
// Also publishes the projected LAST data cycle, which is the input
// Chapter 14.5's tWR projection needs -- §4.
//
// WHAT IT DOES NOT MODEL
// Any electrical or PHY behaviour. There is deliberately NO dqs,
// NO preamble, NO phase, NO skew and NO training anywhere in this
// block, because a controller-side block naming those would be
// claiming to model what it cannot see (§3). It emits a launch
// enable on a cycle. How the data physically reaches the device is
// the PHY's contract, owned by Modules 19-21.
//
// WHY SELF-CHECKING
// §2: with CL the device is the authority and a controller compares
// against it. With CWL the CONTROLLER is the authority, so there is
// nothing external to compare against -- and a mislaunch corrupts
// data the DEVICE stores, which the controller cannot observe. The
// obligation must therefore be checked against an independently
// computed expectation, which is what launch_early/launch_late are.
//
// CONVENTION (Chapter 13.2 §6, as an equality per Chapter 14.9 §5)
// WRITE accepted on cycle N with programmed CWL_CYCLES: the first
// data beat must be launched ON cycle N + CWL_CYCLES, and the
// remaining BURST_CYCLES-1 beats on the following cycles.
//
// SIMULTANEITY
// a WRITE accepted on the cycle a previous write's data launches
// -> both handled; the pipeline holds several obligations.
// two obligations due on the SAME cycle -> impossible if tCCD
// (Chapter 14.6) is honoured, since two writes cannot be closer
// than tCCD and share one CWL. REPORTED on due_collision.
// reset -> all obligations abandoned. In-flight data belongs to
// commands the controller no longer knows about.
// ─────────────────────────────────────────────────────────────────────
module write_launch_contract #(
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.
parameter int OCC_W = (DEPTH <= 1) ? 1 : $clog2(DEPTH + 1)
) (
input logic clk,
input logic rst_n,
// ── The PROGRAMMED latency, in cycles. No conversion: §2 places CWL
// in CL's class, so there is no absolute-time term and nothing to
// resolve. A resolver here would invent a frequency dependence.
input logic [TS_W-1:0] cwl_cycles,
input logic write_accepted,
// ── The controller's own data path reporting a first-beat launch.
input logic launch_observed,
output logic [TS_W-1:0] now,
// ── The obligation the controller owes next.
output logic launch_due_pending,
output logic [TS_W-1:0] launch_due_cycle,
output logic [OCC_W-1:0] outstanding,
// High on exactly the cycle a launch is required. A data path may
// use this directly as its launch enable, which is the point: the
// obligation and the action come from one source.
output logic launch_now,
// ── Two-sided self-check. Exactly one asserts when a launch is
// observed with an obligation pending.
output logic launch_on_time,
output logic launch_early,
output logic launch_late,
output logic signed [TS_W-1:0] launch_error,
// The due cycle passed with no launch. Distinct from late: a launch
// that never happens leaves the device holding stale data, which is
// a different failure from one on the wrong cycle.
output logic launch_missed,
// ── §4: the projected LAST data cycle, which Chapter 14.5's tWR
// projection consumes. Published here because this block already
// holds CWL and the burst, so computing it elsewhere would
// duplicate the arithmetic and invite the two copies to drift.
output logic [TS_W-1:0] projected_last_data,
output logic projected_valid,
output logic due_collision,
output logic overflow
);
if (DEPTH < 1) begin : g_depth
initial $fatal(1, "write_launch_contract: DEPTH must be >= 1");
end
if (TS_W < 2) begin : g_ts
initial $fatal(1, "write_launch_contract: TS_W must be >= 2");
end
if (BURST_CYCLES < 1) begin : g_burst
initial $fatal(1, "write_launch_contract: BURST_CYCLES must be >= 1");
end
logic [TS_W-1:0] due_ts [DEPTH];
logic [PTR_W-1:0] head, tail;
logic [OCC_W-1:0] occ;
logic signed [TS_W-1:0] err;
logic have_due;
logic retire;
always_comb begin
have_due = (occ != '0);
launch_due_pending = have_due;
launch_due_cycle = have_due ? due_ts[tail] : '0;
outstanding = occ;
// Wrap-tolerant signed error, per Chapter 13.3 §7.
err = have_due ? $signed(now - due_ts[tail]) : '0;
launch_error = err;
launch_now = have_due && (err == '0);
launch_on_time = launch_observed && have_due && (err == '0);
launch_early = launch_observed && have_due && ($signed(err) < 0);
launch_late = launch_observed && have_due && ($signed(err) > 0);
// Due cycle passed with nothing launched.
launch_missed = have_due && ($signed(err) > 0) && !launch_observed;
// §4's projection, from the obligation this block already holds.
// BURST_CYCLES is CYCLES, not transfers -- Chapter 14.5 §4 sizes
// that confusion at four cycles for a burst of 8.
projected_valid = have_due;
projected_last_data = have_due
? (due_ts[tail] + TS_W'(BURST_CYCLES) - TS_W'(1))
: '0;
overflow = write_accepted && (occ == OCC_W'(DEPTH));
due_collision = (occ >= OCC_W'(2))
&& (due_ts[tail] == due_ts[(tail + PTR_W'(1))]);
// Retire once the obligation is discharged or demonstrably missed.
// Retiring on a miss keeps one lost launch from misaligning every
// later obligation -- Chapter 14.9 §7's argument, unchanged.
retire = have_due && (launch_observed || ($signed(err) > 0));
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
due_ts[i] <= '0;
end
end else begin
now <= now + 1'b1;
if (write_accepted && (occ != OCC_W'(DEPTH))) begin
due_ts[head] <= now + cwl_cycles;
head <= head + 1'b1;
end
if (retire) begin
tail <= tail + 1'b1;
end
// Single occupancy update over all four combinations, so the
// push and retire paths cannot disagree about occ.
case ({ (write_accepted && (occ != OCC_W'(DEPTH))), retire })
2'b10: occ <= occ + OCC_W'(1);
2'b01: occ <= occ - OCC_W'(1);
default: occ <= occ;
endcase
end
end
endmoduleInterface contract. launch_now is intended to drive the data path's launch enable, so the obligation and the action share one source and cannot disagree — which is the structural answer to §2's “no external authority” problem. launch_observed then closes the loop from whatever the data path actually did. projected_last_data feeds Chapter 14.5's tWR projection from the block that already holds both terms, so the arithmetic exists once.
Parameter contract. DEPTH sized as Chapter 14.9 §7 argued — roughly ceil(CWL / tCCD) plus the burst. OCC_W is $clog2(DEPTH + 1) and PTR_W is $clog2(DEPTH), for the count-versus-index reason now stated in four chapters. BURST_CYCLES is documented as cycles, not transfers.
Corner cases. cwl_cycles == 0: the launch is due on the write cycle itself, arithmetically handled and not physically meaningful. DEPTH == 1: a second write while one is outstanding reports overflow. launch_observed with no obligation pending: no classification asserts, because all three require have_due — a spurious launch is not called early. Reset with obligations outstanding: abandoned.
Synthesis implications. DEPTH timestamp registers, two pointers, an occupancy counter, one subtractor, and an adder for the projection. launch_now is a zero-comparison on the subtraction result.
Failure modes. Gating launch_now on >= instead of == — converts a two-sided obligation into a one-sided one, launching continuously from the due cycle onward. Not driving the data path from launch_now but from a separate counter: the two drift and the self-check compares against the wrong thing. Using BURST_CYCLES as transfers: projected_last_data is late by the double-data-rate factor, moving tWR's deadline per §4. A resolver on cwl_cycles: invents a conversion.
6. The Launch Obligation, in Cycles
write_launch_contract — the controller owes the launch
10 cycleslaunch_now and launch_obs are identical here, which is what correct operation looks like when the data path is driven from the obligation. The value of the self-check is entirely in the cases where they differ — and §5's interface note explains why driving the data path from launch_now makes them agree by construction, leaving launch_observed to catch a data path that ignored it.
proj last data is the output that leaves this chapter. At cycle 5 it reads 8, and that number is what Chapter 14.5's projector needs to arm the tWR obligation. One parameter's output is another parameter's trigger — the only such coupling in the module.
7. Four Assertions Worth Writing
// ── P1. The launch obligation is an EQUALITY, not a lower bound.
// launch_now must assert on exactly the due cycle and nowhere else.
// Catches gating on >=, which converts a two-sided obligation into
// a one-sided one and launches continuously from the due cycle --
// the §5 headline failure and the class error of Chapter 14.9 §3.
property p_launch_now_is_exact;
@(posedge clk) disable iff (!rst_n)
launch_due_pending |-> (launch_now == (now == launch_due_cycle));
endproperty
a_launch_now_is_exact: assert property (p_launch_now_is_exact);
// ── P2. The due cycle is write + CWL, with NO conversion. §2 places
// CWL in CL's class -- a configured cycle count -- so a ceiling or
// a resolver on this path would invent a frequency dependence.
property p_due_is_write_plus_cwl;
@(posedge clk) disable iff (!rst_n)
( $past(write_accepted, 1) && ($past(outstanding, 1) == '0) )
|-> (launch_due_cycle == ($past(now, 1) + $past(cwl_cycles, 1)));
endproperty
a_due_is_write_plus_cwl: assert property (p_due_is_write_plus_cwl);
// ── P3. The two-sided classification is well formed: exactly one of
// on-time, early and late asserts on a classified launch. 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)
(launch_observed && launch_due_pending)
|-> ($countones({launch_on_time, launch_early, launch_late}) == 1);
endproperty
a_classification_is_exclusive: assert property (p_classification_is_exclusive);
// ── P4. The projected last-data cycle -- the value Chapter 14.5's tWR
// projection consumes -- is due + burst_cycles - 1. §4 showed a
// wrong CWL moves tWR's deadline; this property makes sure the
// block at least propagates it correctly, so a tWR shift can be
// attributed to CWL rather than to this arithmetic.
property p_projection_feeds_twr_correctly;
@(posedge clk) disable iff (!rst_n)
projected_valid |->
(projected_last_data == (launch_due_cycle + TS_W'(BURST_CYCLES)
- TS_W'(1)));
endproperty
a_projection_feeds_twr_correctly:
assert property (p_projection_feeds_twr_correctly);
// ── C1. A launch is observed ON TIME at least once. A classifier never
// shown to fire is not known to work, and Chapter 14.9 §8 makes the
// stronger point: an EARLY and a LATE launch should be injected
// deliberately, because a design with no late detector passes every
// test without ever testing the late direction.
c_launch_on_time: cover property (@(posedge clk) disable iff (!rst_n)
launch_on_time);What these prove. That the launch obligation is enforced as an equality — the structural signature of a class-B obligation; that the due cycle is write + CWL unconverted; that the classification is well formed; and that the value feeding tWR's projection is propagated correctly.
What these do not prove, and the first is the limitation §2 predicted. They cannot tell whether the programmed CWL matches the device's. The block launches on the cycle its own cwl_cycles dictates, and every property passes whether or not the device agrees — which is precisely the failure mode a controller-side authority creates. Only a mode-register readback settles it, which is a configuration check and not a timing one; §9 makes it a rule for the whole-set model.
They also do not prove the PHY delivers what was launched (§3 — a different contract entirely), nor that BURST_CYCLES is a cycle count rather than a transfer count, since P4's arithmetic is correct either way.
Vacuity. P2 requires an empty pipeline at the write; C1 requires an on-time launch. Cover both, and inject a deliberate early and late launch per Chapter 14.9 §8.
8. Cross-Parameter Consistency
Ten parameters, and a module with inconsistent definitions would be worse than no module. Here is the full set with the classification each chapter used, so any disagreement is visible in one place.
| Parameter | Trigger | Constrained event | Scope | Class | Unit form | Generation |
|---|---|---|---|---|---|---|
| tRCD | ACT accepted | column cmd, same bank | bank-local | min separation | ns (verified) | neutral |
| tRP | PRE accepted | ACT, same bank | bank-local | min separation | ns (verified) | neutral |
| tRAS | ACT accepted | PRE, same bank | bank-local | min separation | ns (inferred §15.3 §4) | neutral |
| tRC | ACT accepted | next ACT, same bank | bank-local | min separation, derived | ns (verified) | neutral |
| tWR | last write data | PRE, same bank | bank-local | min separation | not verified | neutral |
| tCCD | column cmd accepted | next column cmd | shared / bank group | min separation | nCK | DDR4 _S/_L |
| tRRD | ACT accepted | next ACT, diff bank | shared / bank group | min separation | max(nCK, ns) (verified) | DDR4 _S/_L |
| tFAW | accumulation of ACTs | next ACT | shared, rank | rolling window | not verified | count of 4 verified |
| CL | RD accepted | first data beat (device drives) | data path | fixed latency | nCK (verified) | neutral |
| CWL | WR accepted | first data beat (controller drives) | data path | fixed latency | nCK | neutral |
Three patterns are worth reading off that table.
Eight of ten are minimum separations. The module is overwhelmingly about one-sided obligations, which is why Chapter 14.9's two-sided arrival needed a whole section of re-orientation.
Only two triggers are not an accepted command. tWR's is a data event, requiring Chapter 14.5's projection, and tFAW's is an accumulation, requiring Chapter 14.8's history queue. Those two exceptions account for the module's two most distinctive RTL blocks.
Scope splits five bank-local, three shared, two data-path. And every shared one is where Chapter 13.3 §4's Bug 2 lives — fast and wrong, passing single-bank tests.
9. The Event-to-Event Matrix
The module's synthesis artefact: every populated edge, with its scope and class.
───────────── CONSTRAINED EVENT ─────────────
PRIOR EVENT ACTIVATE column cmd PRECHARGE data
───────────────────────────────────────────────────────────────────
ACTIVATE tRC (B,m) tRCD (B,m) tRAS (B,m) ·
same bank
ACTIVATE tRRD (G,m) · · ·
diff bank tFAW (R,w)
───────────────────────────────────────────────────────────────────
column cmd · tCCD (G,m) · CL (D,f)
CWL (D,f)
───────────────────────────────────────────────────────────────────
PRECHARGE tRP (B,m) · · ·
───────────────────────────────────────────────────────────────────
last write data · · tWR (B,m) ·
SCOPE: B bank-local G bank group / shared R rank-wide
D data path
CLASS: m minimum separation f fixed latency w rolling window
· no direct edge in this setFour structural facts fall out of the matrix that no individual chapter could show.
One ACTIVATE populates three cells in its own row. Chapter 14.4 §1 said one accepted activate arms three obligations; here you can see them as three edges from one event.
The ACTIVATE column has four entries across two scopes. A candidate ACTIVATE is subject to tRC or tRP bank-locally, and tRRD and tFAW at coarser scopes. That is the most heavily constrained command in DDR, which is why Chapter 14.8 noted tFAW is often controlling for activate-heavy traffic.
The data column is the only one with fixed latencies, and it is the only column where the controller is not choosing to act.
The matrix is not symmetric and has empty cells. A PRECHARGE does not directly constrain a column command — it constrains an ACTIVATE, which then constrains the column command. Some relationships are compositions of edges rather than edges, which Chapter 13.3 §5 predicted and Chapter 14.4 worked through.
10. DV — A Whole-Set Reference Model
Every chapter has built a per-parameter checker. A real verification environment needs one model over all of them, and Chapter 13.4 §6's issue_timing_checker is its skeleton. Four design rules, each earned somewhere in the module.
Invert every representation. The designs in this module use per-bank countdowns (14.1, 14.2, 14.3), a shared countdown (14.6), a circular timestamp buffer (14.8) and expectation FIFOs (14.9, this chapter). The model should hold timestamps for everything and recompute from scratch, so it shares no bookkeeping with any of them.
Resolve every magnitude independently, from its own table entry. Not from the design's registers. Chapter 14.1 §10 made the general case; the module then produced three specific reasons it matters — tRP and tRCD coinciding at some grades so a shared register passes; tRAS obtained by subtraction so a wrong tRC propagates; and tRRD's page-size dependence so the wrong table row is silently plausible.
Derive data events rather than observing design signals. tWR's trigger and both latencies' data events must come from the model's own CWL + burst − 1 arithmetic (Chapter 14.5 §4), computed from independently sourced terms. A model reading the design's projection inherits its errors.
Report the applicable set, not just the violation. Every chapter's report converged on this shape, and the module's best examples are worth collecting: Chapter 14.4 §8 marks satisfied obligations so a reader sees nothing was overlooked; Chapter 14.6 §10 and 14.7 §11 add the counterfactual — this would have been legal in a different bank group; Chapter 14.8 §10 enumerates the window's contents, because a density violation has four source events and the oldest is the one that matters.
TIMING VIOLATION
command : PRECHARGE, bank 2
issued at : cycle 4181
applicable : tWR -> legal from 4186 CONTROLLING
tRAS -> legal from 4150 satisfied
violated : tWR
trigger : last write data, cycle 4171 (DERIVED)
derivation : WRITE @ 4154 + CWL 14 + burst 4 − 1 = 4171
CWL source : device mode register readback, not design register
short by : 5 cycles
note : tRAS was satisfied 31 cycles earlier; a design
checking only tRAS would have issued at 4150The CWL source line is this chapter's contribution to the report format. §2 established that a wrong CWL moves tWR's deadline, so a model that took CWL from the design's register would compute the same wrong trigger and agree. Stating where the number came from makes the independence auditable rather than merely claimed.
11. Debugging
Symptom. Written data is wrong when read back later, or precharge timing after writes is off.
| Candidate mechanism | Evidence | Discriminator |
|---|---|---|
| CWL wrong — controller and device disagree | Write data corrupt and tWR deadline shifted, both by the same k | §4's discriminator. Both symptoms shifted by the same amount implicates CWL as one root cause. Mode-register readback confirms. |
| tWR projection arithmetic wrong | tWR deadline shifted; write data correct | §4. Data fine plus deadline off means the projection, not CWL — Chapter 14.5 §4's − 1 or transfers-for-cycles. |
Launch gated on >= not == | Data launched continuously from the due cycle | §5's headline failure. Check whether launch_late can ever fire. |
Data path not driven from launch_now | launch_now and launch_observed differ | §5's interface note. Two sources drifting. |
| PHY untrained or mis-trained | CWL verified correct; data still corrupt | §3. This block exonerates CWL and points at Modules 19–21 — a useful negative result. |
BURST_CYCLES as transfers | projected_last_data late by the DDR factor | Compare against due + burst_cycles − 1 by hand. |
DEPTH undersized | Failures only under sustained write bursts | overflow. One dropped obligation misaligns all later ones. |
The discriminator that defines this chapter is whether two symptoms moved together. CWL is the only parameter in the module that appears in another parameter's deadline, so a CWL error shows up twice — once as corruption and once as a tWR shift, both by the same k. Seeing both together identifies a single root cause where an engineer would otherwise chase two bugs.
And the second: whether CWL is provably correct. If it is, and data is still wrong, the honest conclusion is that the problem is not in this chapter's scope at all — it is the PHY, and §3 drew that boundary precisely so this conclusion can be reached cleanly rather than by exhaustion.
12. Common Misconceptions
“CWL tells the controller when a WRITE may issue.”
Tempting because it is the write-side latency number, and the read-side symmetry with tRCD invites it. Why it is wrong: like CL, CWL governs data, not issue. Issue legality is tRCD, tCCD and bank state. Consequence: writes delayed by CWL for no reason — correct data, throughput short per write. Replacement model: issue obligations and latency obligations are different edges. Debugging clue: no data errors and write throughput short by a constant.
“CWL is CL for writes, so the same reasoning applies.” Tempting because the five-part forms are structurally identical. Why it is wrong: §2's agency inversion. With CL the controller receives and the device is the authority; with CWL the controller produces and is the authority. So a CWL failure corrupts data the device stores, which the controller cannot observe — it surfaces on a later read of a different transaction. Consequence: a verification environment that checks CL properly and assumes CWL is symmetric has no independent check on the write direction. Replacement model: same class, opposite agency, different detectability. Debugging clue: write corruption discovered far from its cause.
“A wrong CWL only affects write data.” Tempting because CWL is the write-data latency and that is where it acts. Why it is wrong: §4 — CWL is a term in tWR's projected trigger, so a wrong CWL also moves the write-recovery deadline. One wrong number, two parameters' behaviour. Consequence: an engineer investigating a tWR timing shift looks at tWR and finds nothing wrong with it. Replacement model: CWL is the module's only parameter that feeds another's deadline. Debugging clue: a tWR shift and a data corruption of the same magnitude.
“The controller's RTL should model the strobe to get CWL right.” Tempting because write data really does need a strobe with a real phase relationship. Why it is wrong: §3 — CWL is a cycle count; the electrical relationship is the PHY's contract, established by training and owned by Modules 19–21. Consequence: controller RTL naming signals it cannot observe, which misleads every later reader and makes the two independent requirements harder to debug separately. Replacement model: two requirements, one digital and one electrical, both necessary. Debugging clue: a proposed fix that requires the controller to know about skew.
“CWL needs an ns-to-cycle conversion.” Tempting because four parameters in the module do. Why it is wrong: §2 places CWL in CL's class — a configured pipeline depth in cycles, verified for CL from the datasheet's own headings. Consequence: a design resolving it invents a frequency dependence and gets a different CWL per speed grade. Replacement model: configured depths do not resolve. Debugging clue: the configured CWL varying across grades when the table does not.
“A checker that copies the RTL's timing counters is independent verification.” Tempting because it agrees with the design, which looks like passing. Why it is wrong: agreement is the problem — a checker sharing the design's representation, magnitudes, or derived events shares its bugs and reports success on a broken design. §10's four rules are the module's accumulated answer. Consequence: a verification asset that provides confidence without checking, which is worse than no checker because the team stops looking. Replacement model: invert the representation, resolve magnitudes from the table, derive data events independently. Debugging clue: a checker that has never disagreed with its design over a long programme.
13. Interview Reasoning
“What does CWL constrain, and how does it differ from CL?” Both are fixed programmed latencies in cycles — two-sided, so being late fails as surely as being early. The difference is agency: with CL the device drives data and the controller must be ready to capture; with CWL the controller drives data and the device will be ready. That inversion matters for detectability — a CL failure corrupts data the controller reads and its own checkers can see it, while a CWL failure corrupts what the device stores and only surfaces on a later read of a different transaction.
“Does CWL tell the controller when a WRITE may issue?”
No — issue legality is tRCD, column spacing and bank state. CWL governs when the data must be launched after the write has already been accepted. Putting it in the issue conjunction delays every write by CWL for no reason: correct data, throughput short by a constant, and no error to point at.
“Why is write recovery measured from the write data rather than the WRITE command, and what does CWL have to do with it?”
Because the interval protects data being driven into the cells, which cannot start before the data arrives. CWL is how you find that cycle: the last write data is write_cmd + CWL + burst_cycles − 1, so CWL is a term in tWR's deadline. That gives CWL a property no other parameter in the set has — a wrong CWL produces both a data corruption and a tWR timing shift, by the same amount, which is a useful single-root-cause signature.
“Your write data is corrupt and precharges after writes are one cycle late. One bug or two?”
Almost certainly one: a CWL that is one cycle too large. It mislaunches the data and, because CWL feeds tWR's projection, it also pushes the recovery deadline out by one. The check is whether both shifts are the same magnitude — if they are, fix CWL and both resolve. If the data were correct and only the deadline were off, the projection arithmetic would be the suspect instead, typically the − 1 or using burst transfers where burst cycles belong.
“Should controller RTL model the write strobe to implement CWL?” No. CWL is a cycle count, and the electrical relationship between data and strobe is the PHY's contract, established by training. A controller-side block naming a strobe is claiming to model what it cannot observe. The practical argument is that these are two independent requirements — correct CWL with an untrained PHY still fails, and a perfect PHY cannot rescue a wrong CWL — so keeping them separate is what makes either debuggable.
“How would you verify all ten of these parameters together rather than one at a time?” One model over the whole set, following four rules the module accumulated: hold timestamps for everything and recompute from scratch, since the designs use countdowns, a shared counter, a circular buffer and FIFOs; resolve every magnitude from its own table entry rather than from the design's registers, because tRP and tRCD coincide at some grades, tRAS is often obtained by subtraction, and tRRD depends on page size; derive data events from independently sourced CWL and burst rather than reading the design's projection; and report the whole applicable set with the controlling parameter marked, plus a counterfactual where scope selection was involved.
“Give me the most heavily constrained command in DDR and why.”
ACTIVATE. It is subject to tRC or tRP bank-locally, tRRD at bank-group scope, and tFAW rank-wide as a rolling window — four obligations across three scopes and two constraint classes. That is why activate-heavy traffic so often finds tFAW controlling, and why the activate column of the event-to-event matrix is the fullest.
14. Engineering Exercises
1. Compute launch cycles and the tWR trigger. A WRITE is accepted at cycle 500 with CWL of 14 and a burst of 8 transfers. Give the first data cycle, the last data cycle, and the projected tWR trigger.
Worked: 8 transfers occupy 4 clock cycles. First data launch at 500 + 14 = 514. Last data at 500 + 14 + 4 − 1 = 517. That last figure is the tWR trigger, and Chapter 14.5 adds the resolved tWR to it.
2. Propagate a CWL error. Repeat exercise 1 with CWL mistakenly set to 15. Give the launch cycle, the projected trigger, and both resulting symptoms.
Worked: launch at 515 — one cycle late, so the device captures the wrong cycle, giving data corruption. Projected trigger at 518 instead of 517, so the tWR deadline is one cycle late and precharge is held one cycle longer than needed — a slowdown. One wrong number, two symptoms, both shifted by 1.
3. Distinguish two root causes. Design A has CWL one too large. Design B has the correct CWL but omits the − 1 in the projection. Both show a tWR deadline one cycle late. Give the one observation that separates them.
Worked: whether the write data is correct. Design A also mislaunches, so data is corrupt. Design B launches correctly and only the deadline moves. Data correct plus deadline late isolates the projection; both wrong isolates CWL.
4. Classify all ten. Without looking at §8, write each of the module's ten parameters' trigger, scope and class. Then check. Which did you get wrong, and is the error in the trigger, the scope or the class?
Worked guidance: the two most commonly missed triggers are tWR's — a data event, not the command — and tFAW's, which is an accumulation rather than any single event. The most commonly missed scopes are tCCD's and tRRD's, which are shared rather than bank-local. And the most commonly missed classes are CL's and CWL's, which are fixed latencies rather than minimum separations. Those six are the module's six hardest facts, and they are hard for a reason — each is the exception to a pattern the other parameters establish.
5. Read the matrix. Using §9's matrix, list every obligation applicable to a candidate ACTIVATE to bank 5 given that bank 5 was precharged recently and other banks have been active. Then say which you would expect to control for activate-heavy traffic.
Worked: tRP from bank 5's precharge (bank-local); tRC from bank 5's previous activate (bank-local); tRRD from the most recent activate to any bank, with the magnitude selected by bank-group relationship; and tFAW from the rolling window. Four obligations, three scopes. For activate-heavy traffic tFAW is the most likely to control, because it is the only one that tightens as the rate of activates rises rather than depending on a single prior event.
6. Find the dependent-checker bug. A verification model reads CWL from the design's configuration register to derive the tWR trigger. Construct the scenario in which the model and the design are both wrong and agree, and give the fix.
Worked: the controller is programmed with CWL 15 while the device holds 14. The design launches at write + 15 and projects the trigger from 15; the model reads 15 and projects the same. They agree, and both are wrong by one — the device captured at write + 14. Every check passes and the data is corrupt. The fix is §10's rule: the model takes CWL from a mode-register readback of the device, not from the design's register, so the disagreement surfaces.
7. Justify the boundary. §3 forbids naming electrical signals in this block. Argue that a controller-side CWL model should include a preamble parameter, then rebut using the two-independent-requirements argument.
15. Summary
CWL is CL's write-direction counterpart — a fixed, programmed latency in cycles, two-sided, with the same five-part shape and one word changed: the controller launches the data rather than receiving it.
That agency inversion has three consequences. A CWL failure corrupts data the device stores, which the controller cannot observe, so it surfaces on a later read of a different transaction. The obligation is owed by the design under test, so there is no external authority to compare against and a verification environment must reconstruct the expected launch cycle independently. And CWL is a term in tWR's projected trigger, so a wrong CWL moves a different parameter's deadline — the only such coupling in the module, and it yields a single-root-cause signature: corruption and a tWR shift of the same magnitude.
The PHY boundary is the sharpest in the module. CWL is a cycle count; the strobe, its preamble, its phase and the training that established them are the PHY's contract, owned by Modules 19–21. write_launch_contract has no strobe, no preamble and no skew anywhere in it — and the practical payoff is that a verified-correct CWL exonerates this layer and points cleanly at the PHY, rather than leaving the two tangled.
The module's synthesis: eight of ten parameters are minimum separations; only two triggers are not an accepted command — tWR's data event and tFAW's accumulation, which account for the module's two most distinctive RTL blocks; and scope splits five bank-local, three shared, two data-path, with every shared one being where Chapter 13.3 §4's fast-and-wrong bug lives.
The event-to-event matrix shows what no single chapter could: one ACTIVATE populating three cells in its own row; the ACTIVATE column carrying four obligations across three scopes, making it the most heavily constrained command in DDR; the data column as the only one with fixed latencies and the only one where the controller is not choosing to act; and empty cells that are compositions rather than edges — a PRECHARGE constrains an ACTIVATE, which then constrains the column command.
And the whole-set reference model rests on four rules the module earned chapter by chapter: invert every representation; resolve every magnitude from its own table entry, because tRP and tRCD coincide at some grades, tRAS is often obtained by subtraction, and tRRD depends on page size; derive data events independently; and report the applicable set with the controlling parameter marked and a counterfactual where scope was selected.
16. Module 14 in One Chain
Module 13 built a timing engine and left it empty. This module filled it.
Every parameter is an edge with five parts — a triggering event, a constrained event, a resource, a magnitude and a sense — and the recurring grammar was the point rather than the parameters. By this chapter, a parameter you have never met should be a four-question classification: what triggers it, what does it constrain, what resource owns it, and is it a minimum, a latency, an occupancy or a window?
The units carry the classification. Micron's verified speed-bin heading — Data Rate (MT/s) | CL | tRCD (ns) | tRP (ns) | tRC (ns) — says it in one line: physical requirements in absolute time, configured pipeline depths in cycles. Physical requirements cost more cycles at a faster clock; configured depths cost the same cycles and less time. That single distinction predicted why tRRD carries a dual-term specification, why tCCD does not resolve, and why a CL of 22 can be faster than a CL of 14.
Scope determines applicability, and getting it wrong has two opposite failures. Bank-local held globally is safe and slow and invisible; shared held per bank is fast and wrong and fails more the better the design does its job.
Composition is a maximum, never a sum. Chapter 14.4 §5's trace showed tRAS satisfied while a precharge was illegal, and tRC satisfied while an activate was illegal — and changing one WRITE to a READ moved two verdicts by four cycles. Memorising definitions would have answered none of those six candidates.
And the honest gaps are part of the result. Five DDR5 questions this curriculum could not close from public material — interleaved burst, activate-to-column separation, column spacing, activate spacing and the activate window — plus tWR's and tFAW's unit forms and CWL's values. DDR4 is publicly documented and DDR5 largely is not, so a DDR4 timing table carried into DDR5 deserves more scepticism than it usually gets. _S and _L are DDR4-era names for a DDR4 mechanism, and this module labelled them as such every time.
Module 15 takes up refresh — the obligation that is neither a separation nor a latency but a deadline the controller owes the device, and the first in this curriculum where failing to act, rather than acting too soon, is the violation.
Continue learning
Related tutorials
- Related topic
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.
- Related topic
Write Latency (CWL)
A read latency is a promise the device makes and a controller can mispredict. A write latency is a requirement the controller must meet — and a promise can be mispredicted while a deadline can only be missed.
- Related topic
Why Timing Parameters Exist
A DDR command can be perfectly meaningful and target a bank in exactly the right state and still be illegal right now. That third refusal is what timing parameters are, and it is a different question from the first two.
- Related topic
Device Physics Behind Timing
The array's processes take absolute time and know nothing about any clock. A controller counts cycles. Converting between them is where a physical duration becomes a digital obligation — and where the same device needs more cycles the faster you run it.
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.
