DDR · Module 14
tWR — Write Recovery
Every other parameter triggers on a command you can watch arrive. tWR triggers on the last write data beat — a cycle no command bus carries, which must be projected from the write command plus CWL plus the burst.
Chapter 11.4 already owns write recovery. It established the mechanism, named the five events, built write_recovery_guard, handled auto-precharge, and demolished the misconception that tWR is measured from the WRITE command.
This chapter does not re-teach any of that. It exists because Chapter 14.4 §5's trace leaned on tWR entirely and revealed something the earlier chapter had no reason to emphasise:
tWR is the only parameter in this module whose triggering event's cycle must be computed rather than observed.
Every other obligation here arms on an accepted command. A command arrives on the command bus, you see it, you start counting. tWR arms on the last write data beat — a moment no command bus carries, which has to be projected forward from the WRITE command through CWL and the burst length. That projection is a small arithmetic expression, it depends on two other parameters, and it is where tWR goes wrong.
1. The Obligation, in Five Parts
PARAMETER tWR
1 triggering event the LAST WRITE DATA BEAT into bank B
— NOT the WRITE command. See §3.
2 constrained event PRECHARGE addressed to bank B
3 resource bank B BANK-LOCAL
4 magnitude in nanoseconds and/or cycles — see §5
5 sense MINIMUM SEPARATION
─────────────────────────────────────────────────────────────────
consequence PRECHARGE of bank B not before
last_write_data_cycle + resolved(tWR)Compare part 1 against every other parameter in this module:
| Parameter | Triggering event | Observable on the command bus? |
|---|---|---|
| tRCD | ACTIVATE accepted | yes |
| tRP | PRECHARGE accepted | yes |
| tRAS | ACTIVATE accepted | yes |
| tRC | ACTIVATE accepted | yes |
| tCCD | column command accepted | yes |
| tRRD | ACTIVATE accepted | yes |
| tWR | last write data beat | no — must be projected |
tWR is the odd one out, and that single structural fact generates most of this chapter.
The follow-up questions:
What re-arms it? Another write to the same bank. Since writes to one bank are serialised by column spacing, the obligation effectively tracks the most recent write's data completion.
What can dominate it? tRAS, from the ACTIVATE. Chapter 14.4 §5 showed tWR dominating tRAS on a short access — deadline 13 against 8 — but the ordering is not fixed. A write issued long after the activate has a tRAS that expired ages ago; a write issued immediately after tRCD may have tRAS still outstanding. Neither obligation reliably dominates, which is exactly why the composition matters.
Cycle or absolute-time component? See §5, where I am careful about what is verified.
Does generation matter? For the structure, no. For the start-point details, possibly — §5 says what I could and could not confirm.
2. Why This Chapter Is Not Chapter 11.4
Worth being explicit, because the overlap is real and the distinction is the chapter's justification.
Chapter 11.4 asked: what is write recovery and why does it exist? It answered from the write's point of view — a write is not finished when its data lands, the cells must be driven, and a precharge in the middle destroys that. Its RTL takes data_done as an input, because from inside a write sequencer the end of the data is a thing you know about, having just produced it.
This chapter asks: how does tWR compose with everything else, and where does data_done come from?
That is a genuinely different engineering problem, and it has a genuinely different failure mode: the obligation can be wrong while the block enforcing it is right.
3. The Trigger, Stated Once
Chapter 11.4 §3 established this and argued it at length. Stated here in one paragraph because §4 depends on it, and not re-argued:
The interval protects data being driven from the sensing circuitry into the cells. That driving cannot begin before the data has arrived, so the interval must be measured from the end of the write data, not from the WRITE command. The command is merely the event that schedules the data; the data is the event that starts the obligation.
Chapter 11.4 §11 covers why measuring from the command is wrong and what it costs. This chapter's contribution is the arithmetic that connects the two events.
4. Projecting the Trigger
Here is the projection, term by term, with each term's owner named.
write_cmd_cycle the WRITE command's accepted cycle.
OBSERVED on the command bus.
+ CWL CAS write latency: cycles from the accepted
WRITE to the FIRST write data beat.
Chapter 14.10. A FIXED, PROGRAMMED latency.
+ burst_cycles clock cycles the burst occupies on the data
bus. Chapter 12.1. For a burst of 8 transfers
this is 4 clock cycles, because DDR moves two
transfers per cycle.
− 1 because the FIRST beat lands ON the cycle
write_cmd + CWL, so the LAST of N beat-cycles
lands on write_cmd + CWL + N − 1.
────────────────────────────────────────────────────────────────────
= last_write_data_cycleThen the deadline:
tWR deadline = last_write_data_cycle + resolved(tWR)Work it with Chapter 14.4 §5's educational magnitudes — CWL of 2, burst occupying 2 cycles, tWR of 5, WRITE at cycle 5:
first data = 5 + 2 = cycle 7
last data = 5 + 2 + 2 − 1 = cycle 8
tWR deadline = 8 + 5 = cycle 13Which is exactly the deadline that trace used, and the reason its candidate-3 precharge at cycle 9 was illegal.
And a third error, which is the dangerous direction:
Project from write_cmd_cycle alone — treating the command as the trigger — and the deadline is CWL + burst_cycles − 1 cycles early: four cycles with these magnitudes, more with a realistic CWL. That is a genuine violation, and Chapter 11.4 §10 covers what it destroys.
5. Units — What Is Verified and What Is Not
Here I have to be careful, and being careful is more useful than being complete.
6. The Exact Legal Boundary
last write data beat on (computed) cycle N
resolved requirement D cycles
PRECHARGE at M is LEGAL ⟺ M - N >= D
earliest legal M = N + DSame convention as every other parameter. The novelty is not the boundary — it is that N is a computed quantity, so an error in the projection shifts the whole boundary without anything about the counting being wrong.
7. RTL — Projecting a Deadline for the Scoreboard
Collision check. Chapter 11.4's write_recovery_guard counts down from a data_done input and blocks precharge. That block is correct and this chapter reuses it — building a second countdown would be precisely the ten-timers architecture Chapter 14.4 §6 argued against. Chapter 13.3's deadline_scoreboard holds obligations as timestamps and maximises them. Chapter 6.5's cas_to_data_window models a column command's data window.
The gap is the arithmetic between the command bus and data_done. Nothing in the corpus computes when a write's data will end. write_recovery_guard is told; cas_to_data_window tracks a window it was told about. This block predicts.
The engineering problem. A timing scoreboard watching the command bus must arm the tWR obligation, but the arming event happens on the data bus later. Project it, publish the resulting deadline, and — critically — publish the intermediate terms so the projection can be checked without re-deriving it.
Classification: controller-side arithmetic plus a small pipeline. No physical modelling; no PHY modelling. The block never touches a data pin and has no notion of electrical timing. It computes cycle numbers.
// ─────────────────────────────────────────────────────────────────────
// twr_deadline_projector
//
// CLASSIFICATION
// Controller-side cycle arithmetic with a short pipeline. Counts
// cycles and adds small integers.
//
// WHAT IT MODELS
// §4's projection, and nothing else:
// last_write_data = write_cmd + CWL + burst_cycles - 1
// twr_deadline = last_write_data + twr_cycles
// then arms a per-bank obligation at the PROJECTED cycle, which is
// what Chapter 11.4's write_recovery_guard would be TOLD by a write
// sequencer and what a command-bus-side scoreboard must work out.
//
// WHAT IT DOES NOT MODEL
// Any physical process. Any PHY or electrical behaviour. It does
// not drive, sample, or observe a single data pin -- Modules 19-21
// own that. It has no DQS, no strobe, no eye, no skew. It computes
// cycle NUMBERS from a command it saw on the command bus.
// It also does NOT decide precharge legality on its own: tRAS
// (Chapter 14.3) is the other applicable obligation, which is why
// the output is a DEADLINE for a scoreboard rather than a verdict.
//
// WHY PROJECT INSTEAD OF OBSERVING
// A scoreboard watching the command bus has no data-completion
// event available. Waiting for one would mean either a data-path
// input this block has no business having, or arming the obligation
// late -- and arming a MINIMUM-SEPARATION obligation late makes it
// expire late, which is safe but wrong, and hides real violations
// that occurred before the arming.
//
// CONVENTION (Chapter 13.2 §6)
// Trigger on computed cycle N with magnitude D forbids PRECHARGE on
// N .. N+D-1, permits from N+D.
// Unlike every other block in this module, the arming cycle N is
// IN THE FUTURE when the WRITE is observed, so the obligation is
// held as (delay-until-arm, then countdown) -- see pend_cnt.
//
// SIMULTANEITY
// a new WRITE while a projection is pending -> the new one wins and
// its deadline replaces the old. Legal here because column
// spacing (Chapter 14.6) serialises writes to one bank, so a
// genuine overlap would already be a tCCD violation. REPORTED
// on projection_overrun so that assumption is not silent.
// arm-cycle and new WRITE coincide -> the new WRITE wins.
// reset -> all projections and obligations abandoned.
// ─────────────────────────────────────────────────────────────────────
module twr_deadline_projector #(
parameter int BANKS = 16,
// Largest value any of the three summed terms can reach, in cycles.
parameter int MAX_CYCLES = 64,
parameter int BK_W = (BANKS <= 1) ? 1 : $clog2(BANKS),
// Sized as a COUNT. The sum CWL + burst + tWR is the largest value
// held, so this must cover it -- not just the largest single term.
parameter int CNT_W = (MAX_CYCLES <= 1) ? 1 : $clog2(MAX_CYCLES + 1)
) (
input logic clk,
input logic rst_n,
// ── The three terms of §4's projection, each owned by a different
// chapter and each supplied INDEPENDENTLY. Deriving any of them
// from another is how the projection goes wrong.
input logic [CNT_W-1:0] cwl_cycles, // Chapter 14.10
input logic [CNT_W-1:0] burst_cycles, // Chapter 12.1 -- CYCLES,
// not transfers. §4.
input logic [CNT_W-1:0] twr_cycles, // resolved tWR
// ── Observed on the command bus.
input logic write_accepted,
input logic [BK_W-1:0] write_bank,
// ── Candidate PRECHARGE under evaluation.
input logic [BK_W-1:0] cand_bank,
// ── The tWR verdict for that bank. One member of the applicable
// set; Chapter 14.3's tRAS is the other, and Chapter 14.4's
// composite takes the maximum.
output logic twr_ok,
output logic [CNT_W-1:0] twr_remaining,
// ── The intermediate terms, published so §10's checker can verify
// the projection WITHOUT recomputing it the same way. Publishing
// these is the difference between a debuggable block and one that
// is wrong by four cycles for a week.
output logic [CNT_W-1:0] projected_data_delay, // CWL + burst - 1
output logic projection_pending,
// A WRITE arrived while a projection for that bank was still
// pending. Should be impossible given column spacing -- reported
// rather than assumed.
output logic projection_overrun
);
if (BANKS < 1) begin : g_banks
initial $fatal(1, "twr_deadline_projector: BANKS must be >= 1");
end
if (MAX_CYCLES < 1) begin : g_max
initial $fatal(1, "twr_deadline_projector: MAX_CYCLES must be >= 1");
end
// ── Two phases per bank. pend_cnt counts down to the projected
// arming cycle; twr_cnt then counts the obligation itself. Two
// counters rather than one because the two intervals are
// conceptually different: one is a PREDICTION of when the trigger
// will occur, the other is the OBLIGATION the trigger creates.
logic [CNT_W-1:0] pend_cnt [BANKS];
logic [CNT_W-1:0] twr_cnt [BANKS];
logic [BANKS-1:0] pend;
// ── §4's projection. Guarded so a degenerate configuration in which
// CWL + burst is zero cannot underflow. burst_cycles of zero is
// not physically meaningful and is refused at elaboration time by
// neither guard, so it is handled here instead.
logic [CNT_W-1:0] data_delay;
always_comb begin
if ((cwl_cycles + burst_cycles) >= CNT_W'(1)) begin
data_delay = cwl_cycles + burst_cycles - CNT_W'(1);
end else begin
data_delay = '0;
end
projected_data_delay = data_delay;
end
always_comb begin
projection_pending = (pend != '0);
twr_remaining = twr_cnt[cand_bank];
// Pending counts as blocked: the obligation has not been armed
// yet, but it WILL be, and permitting a precharge in the interim
// would violate it retroactively. This is the subtle correctness
// point of the whole block -- see the interface contract.
twr_ok = (twr_cnt[cand_bank] == '0)
&& (pend_cnt[cand_bank] == '0);
projection_overrun = write_accepted && pend[write_bank];
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pend <= '0;
for (int unsigned b = 0; b < BANKS; b++) begin
pend_cnt[b] <= '0;
twr_cnt[b] <= '0;
end
end else begin
for (int unsigned b = 0; b < BANKS; b++) begin
// Phase 1: count down to the projected arming cycle.
if (pend_cnt[b] != '0) begin
pend_cnt[b] <= pend_cnt[b] - 1'b1;
// On reaching the arming cycle, hand over to phase 2.
if (pend_cnt[b] == CNT_W'(1)) begin
pend[b] <= 1'b0;
twr_cnt[b] <= twr_cycles;
end
end else if (twr_cnt[b] != '0) begin
// Phase 2: the obligation itself.
twr_cnt[b] <= twr_cnt[b] - 1'b1;
end
end
if (write_accepted) begin
// Arm the PROJECTION, not the obligation. data_delay may be
// zero for a degenerate CWL and burst, in which case the
// obligation arms immediately.
if (data_delay != '0) begin
pend_cnt[write_bank] <= data_delay;
pend[write_bank] <= 1'b1;
end else begin
pend_cnt[write_bank] <= '0;
pend[write_bank] <= 1'b0;
twr_cnt[write_bank] <= twr_cycles;
end
end
end
end
endmoduleInterface contract, and the subtle part. twr_ok is low while the projection is pending, not only while the obligation is counting. That is the block's one non-obvious correctness decision and it is worth stating plainly: between the WRITE command and the projected last-data cycle, the obligation has not been armed yet — but it will be, with a deadline later than now. Permitting a precharge during that interval would violate an obligation that is merely not yet armed. A block that gated only on twr_cnt would permit precharges in exactly the window the write is being performed in, which is the worst possible place.
Parameter contract. CNT_W must cover cwl_cycles + burst_cycles + twr_cycles, not the largest single term — sizing it for one term is an easy mistake that truncates the projection. burst_cycles is documented as cycles, not transfers, because §4 showed that conflation costs four cycles on a burst of 8.
Internal state. Two counters per bank, and the split is deliberate. pend_cnt is a prediction; twr_cnt is an obligation. Collapsing them into one counter loaded with data_delay + twr_cycles would compute the same final deadline and would lose the ability to report which phase a bank is in — and §11's debugging table turns entirely on that distinction.
Corner cases. data_delay == 0: arms the obligation immediately, which is the degenerate case of a zero CWL and single-cycle burst; handled explicitly rather than allowed to underflow. twr_cycles == 0: obligation expires immediately on arming. A WRITE while pending: projection_overrun fires and the new projection replaces the old — safe because Chapter 14.6's column spacing should have prevented it, and reported because “should have” is not a guarantee. BANKS == 1: degenerates correctly.
Synthesis implications. 2 × BANKS counters plus one small adder shared combinationally. The adder is the only new logic shape in the chapter.
Failure modes. Using burst_transfers for burst_cycles: deadline four cycles late for a burst of 8 — safe, slow, silent. Omitting the − 1: one cycle late, same character. Arming twr_cnt directly from write_accepted: deadline data_delay cycles early — a genuine violation, and Chapter 11.4 §10 covers the damage. Gating twr_ok on twr_cnt alone: precharges permitted during the write's own data burst. Sizing CNT_W for one term: projection truncates, deadline wildly wrong.
8. The Projection, in Cycles
twr_deadline_projector — a trigger that has not happened yet
10 cyclesCycles 2 through 4 are what makes this block different from every other guard in the module. The obligation has not been armed. twr_cnt is zero. And twr_ok is low, because the trigger is in the future and the deadline it will create is later than now.
The precharge offered at cycle 3 lands in the middle of the write's own data burst. A block gating only on twr_cnt would have permitted it. Notice that the data beats D0 and D1 appear at cycles 3 and 4 — the precharge would have arrived between them.
Cycle 4 is the projected trigger, and nothing on the command bus marks it. It is computed. That is the chapter.
9. Four Assertions Worth Writing
// ── P1. The projection matches §4's arithmetic exactly. THE property
// of this chapter, because the projection is where tWR goes wrong
// while the block enforcing it stays correct.
// Catches the transfers-for-cycles conflation and the missing -1,
// both of which are conservative and therefore invisible to every
// functional test.
property p_projection_is_cwl_plus_burst_minus_one;
@(posedge clk) disable iff (!rst_n)
((cwl_cycles + burst_cycles) >= CNT_W'(1)) |->
(projected_data_delay == (cwl_cycles + burst_cycles - CNT_W'(1)));
endproperty
a_projection_is_cwl_plus_burst_minus_one:
assert property (p_projection_is_cwl_plus_burst_minus_one);
// ── P2. THE subtle correctness point of §7: a precharge is refused
// during the PENDING phase as well as during the obligation, because
// the trigger is in the future and the deadline it will create is
// later than now. A block gating only on twr_cnt permits precharges
// inside the write's own data burst.
property p_pending_counts_as_blocked;
@(posedge clk) disable iff (!rst_n)
(pend_cnt[cand_bank] != '0) |-> !twr_ok;
endproperty
a_pending_counts_as_blocked: assert property (p_pending_counts_as_blocked);
// ── P3. The obligation is armed with the full magnitude exactly when
// the projection completes -- not at the WRITE command. Catches
// arming on the command, which is the one genuinely UNSAFE error of
// the three in §4 and shortens the deadline by the whole projection.
generate
for (genvar gb = 0; gb < BANKS; gb++) begin : g_handoff
property p_armed_at_projected_cycle;
@(posedge clk) disable iff (!rst_n)
( ($past(pend_cnt[gb], 1) == CNT_W'(1)) && !$past(write_accepted, 1) )
|-> (twr_cnt[gb] == $past(twr_cycles, 1));
endproperty
a_armed_at_projected_cycle: assert property (p_armed_at_projected_cycle);
end
endgenerate
// ── P4. A write arriving while a projection is pending is REPORTED,
// never silently absorbed. Column spacing (Chapter 14.6) should
// make it impossible, and "should" is not a guarantee.
property p_overrun_is_reported;
@(posedge clk) disable iff (!rst_n)
(write_accepted && pend[write_bank]) |-> projection_overrun;
endproperty
a_overrun_is_reported: assert property (p_overrun_is_reported);
// ── C1. The pending phase is REACHED. A run where this never covers
// has a degenerate projection (zero data delay) and P2 is vacuous.
c_pending_phase_reached: cover property (@(posedge clk) disable iff (!rst_n)
projection_pending);What these prove. That the projection is §4's expression; that the pending window blocks; that the handoff from projection to obligation happens at the projected cycle with the full magnitude; and that an overrun is reported.
What these do not prove. That cwl_cycles, burst_cycles and twr_cycles are right — three inputs from three different chapters, and P1 checks only that they are combined correctly. In particular P1 passes perfectly when burst_cycles is fed a transfer count, because the arithmetic is still a + b − 1. That error is only catchable by §9's measurement against observed data beats or by an independently sourced term. And nothing about tRAS, the other precharge obligation.
Vacuity. P3's antecedent requires a pending count of exactly 1 with no coincident write; C1 requires a non-degenerate projection. Cover both.
10. DV — Checking a Computed Trigger
Chapter 11.4 §9 covered checking an interval with no observable endpoint. This section covers the part that chapter did not need: checking the projection itself.
Invert the representation. The design uses two countdowns; the checker records write_cmd_cycle[bank] as a timestamp and computes the deadline by addition when a precharge appears.
Compute the projection differently. The design computes CWL + burst_cycles − 1 up front, once. A checker should instead observe the actual data beats if the testbench has visibility of them, and compare the observed last-data cycle against the design's projection. That is the strongest possible check because it is not a recomputation at all — it is a measurement against a prediction.
If data visibility is unavailable, compute the projection from separately sourced terms: CWL from the mode-register configuration rather than from the design's cwl_cycles port, and burst cycles from the burst-length configuration rather than from burst_cycles. The whole point is that a wrong term should produce a disagreement.
Check the pending window explicitly. A checker that only validates the obligation phase will not notice a design that permits precharges during the pending phase — which §8 showed is the worst window. Assert that no precharge is accepted between the WRITE command and the projected last-data cycle.
TIMING VIOLATION
command : PRECHARGE, bank 0
issued at : cycle 9
resource : bank 0
applicable : tWR -> legal from 13 CONTROLLING
tRAS -> legal from 8 satisfied
violated : tWR
trigger : last write data, cycle 8 (PROJECTED)
projection : WRITE @ 5 + CWL 2 + burst 2 − 1 = 8
observed : last data beat seen at cycle 8 ✓ matches
requirement : 5 cycles
short by : 4 cyclesThe projection and observed lines are the point of this report. They separate two completely different bugs that produce identical-looking violations: a wrong projection (the lines disagree) versus a correct projection that was ignored (they agree and the precharge still came early). Without those two lines, both present as “tWR violation, short by 4” and the investigation starts by guessing.
11. Debugging
Symptom. Precharge violates tWR, or throughput after writes is below the model.
| Candidate mechanism | Evidence | Discriminator |
|---|---|---|
Armed on the WRITE command | Shortfall ≈ CWL + burst_cycles − 1 | Compare shortfall against that sum. If it matches, the trigger is the command. This is a real violation. |
burst_transfers used for burst_cycles | No violation; deadline late by burst_transfers − burst_cycles | For a burst of 8, deadline 4 cycles late. Safe and slow. Compare projected_data_delay against the hand-computed value. |
− 1 omitted | No violation; deadline exactly 1 cycle late | Same check, one cycle. |
| Precharge permitted during the pending phase | Violation lands between write data beats | Correlate the precharge cycle with the data burst. §8's cycle-3 case. |
| Only tWR consulted, tRAS ignored | Violations on precharge after a short access with no write | tRAS. The applicable set was too small in the other direction. |
CNT_W sized for one term | Deadline wildly wrong, not off by a small amount | Check whether projected_data_delay is plausible at all. |
| Stale CWL after a mode-register change | Violations begin after a configuration change | Whether the projection re-ran. |
The discriminator that splits this table is whether there is a violation at all. A genuine violation with a shortfall matching CWL + burst_cycles − 1 is the command-as-trigger bug. No violation, but measurable slowness, points at the projection being conservative — and then the size of the excess names which term is wrong: one cycle is the missing − 1, four cycles on a burst of 8 is transfers-for-cycles.
That is an unusually clean diagnostic, and it exists only because §7's block publishes projected_data_delay. A block that computed the projection internally without exposing it would force the same diagnosis to be done by reverse-engineering a waveform.
12. Common Misconceptions
“tWR begins wherever the controller finds convenient.”
Tempting because the WRITE command is the observable event and using it is simpler. Why it is wrong: Chapter 11.4 §3 — the interval protects data being driven into cells, which cannot begin before the data arrives. Consequence: a violation of CWL + burst_cycles − 1 cycles on every write followed by a precharge. Replacement model: the trigger is the last data beat, projected from the command. Debugging clue: the shortfall equals that sum exactly.
“The trigger is observable, like every other parameter's.” Tempting because the other six parameters in this module do trigger on commands you can watch arrive. Why it is wrong: §1's table — tWR is the exception, and the last data beat appears on no command bus. Consequence: a scoreboard designed on the assumption that all obligations arm on observed commands has nowhere to hook tWR, and the usual improvisation is to use the command. Replacement model: one obligation in the set has a computed trigger. Debugging clue: a timing scoreboard that handles six parameters cleanly and tWR awkwardly.
“Burst length in transfers is the number to add.” Tempting because burst length is quoted in transfers — a burst of 8 is 8 transfers. Why it is wrong: DDR moves two transfers per clock cycle, so 8 transfers occupy 4 cycles, and the projection is in cycles. Consequence: deadline 4 cycles late on every write — safe, slow, and entirely invisible to any assertion. Replacement model: transfers and cycles differ by the double-data-rate factor (Chapter 12.1). Debugging clue: post-write throughput below the model by a consistent margin.
“The obligation is not armed yet, so a precharge is fine.” Tempting because the counter really is zero during the pending phase. Why it is wrong: the trigger is in the future and will create a deadline later than now, so a precharge issued during the pending window violates an obligation retroactively — and it lands during the write's own data burst. Consequence: the worst-placed possible precharge. Replacement model: pending counts as blocked. Debugging clue: the violating precharge sits between data beats.
“tWR always dominates tRAS after a write.” Tempting because Chapter 14.4 §5's trace showed exactly that — deadline 13 against 8. Why it is wrong: it depended on the write being issued early. A write issued long after the activate has a tRAS that expired much earlier; the ordering is a function of history. Consequence: a controller that checks only tWR after a write violates tRAS when the write came late in the row's life. Replacement model: take the maximum; do not assume an ordering. Debugging clue: violations only on writes issued shortly after an activate, or only on those issued long after — either pattern indicates one obligation is being skipped.
“A conservative projection is harmless.”
Tempting because both projection errors are in the safe direction, and neither fails anything. Why it is wrong: it is safe and it is not free, and worse, it is undetectable by testing — so it persists. A four-cycle excess on every write-then-precharge is a real throughput cost on write-heavy, miss-heavy workloads. Replacement model: conservatism in a projection is a permanent, silent tax. Debugging clue: publish projected_data_delay and check it by hand once; it takes a minute and no test will ever do it for you.
13. Interview Reasoning
“Why is write recovery measured from the write data rather than from the WRITE command?”
Because the interval protects data being driven from the sensing circuitry into the cells, and that cannot start before the data has arrived. The command only schedules the data. The consequence worth adding is the size of the error: measuring from the command makes the deadline CWL + burst_cycles − 1 cycles early, which is a genuine violation rather than a small inaccuracy.
“How does a timing scoreboard know when the write data ended?”
It does not observe it — it projects it, as write_cmd_cycle + CWL + burst_cycles − 1. That makes tWR the only parameter in the set whose trigger is computed rather than seen, and it makes the obligation vulnerable to errors in two other parameters. The two classic slips are using burst length in transfers instead of cycles, which is four cycles on a burst of 8, and dropping the − 1.
“Is a precharge legal between the WRITE command and the first data beat?”
No, and the reasoning is the interesting part: the tWR obligation has not been armed yet, so a naive counter reads zero — but the trigger is in the future and the deadline it will create is later than now, so permitting the precharge violates the obligation retroactively. It also places the precharge in the middle of the write's own data burst. A guard must treat the pending window as blocked.
“After a write, which obligation gates the precharge — tWR or tRAS?” Whichever expires later, and that depends on history rather than on configuration. A write issued shortly after the activate leaves tRAS outstanding and tWR usually dominating; a write issued late in the row's life has tRAS long expired. The answer is the maximum over both, and assuming an ordering is how one of them gets skipped.
“Your precharges are legal but write throughput is 5 percent below model. Where do you look?”
At the projection, because both of its likely errors are conservative and therefore invisible to every test. Compare the design's computed data delay against CWL + burst_cycles − 1 worked by hand. An excess of exactly one cycle is a missing − 1; an excess matching the difference between burst transfers and burst cycles is the transfers-for-cycles conflation. The general lesson is that a safe-direction arithmetic error in a projection is a permanent tax that no assertion will ever report.
“How would you verify the projection itself?” Best case, do not recompute it — measure it. If the testbench can see the data bus, compare the observed last-data cycle against the design's projected one; a prediction checked against a measurement cannot share a bug with what it is checking. If data visibility is unavailable, compute the terms from independently sourced configuration — CWL from the mode register, burst cycles from the burst-length setting — rather than from the design's own ports, so that a wrong term produces a disagreement instead of agreement.
14. Engineering Exercises
1. Project a deadline. A WRITE to bank 3 is accepted at cycle 400. CWL resolves to 14 cycles, the burst is 8 transfers, and tWR resolves to 15 cycles. Give the first data cycle, the last data cycle, and the tWR deadline.
Worked: 8 transfers occupy 4 clock cycles. First data at 400 + 14 = 414. Last data at 400 + 14 + 4 − 1 = 417. Deadline 417 + 15 = cycle 432.
2. Size the three errors. Using exercise 1's figures, compute the deadline produced by (a) arming on the command, (b) using 8 for burst cycles, (c) omitting the − 1. For each, say whether it is a violation or a slowdown and by how much.
Worked: (a) 400 + 15 = 415 — 17 cycles early, a violation, and 17 equals CWL + burst_cycles − 1 = 14 + 4 − 1. (b) 400 + 14 + 8 − 1 + 15 = 436 — 4 cycles late, a slowdown. (c) 400 + 14 + 4 + 15 = 433 — 1 cycle late, a slowdown. Only (a) fails anything, which is exactly why (b) and (c) survive.
3. Decide the pending-window question. In exercise 1, is a precharge to bank 3 legal at cycle 410? At 416? At 432? Justify each from the obligation's state rather than from the counter's value.
Worked: 410 — no. The obligation is pending; the trigger has not occurred but will, at 417, creating a deadline of 432. 416 — no, and this one lands mid-burst, between the first data at 414 and the last at 417. 432 — yes, 432 − 417 = 15 >= 15. The instructive part is that a counter-based test would read zero at both 410 and 416.
4. Find the missing obligation. A controller checks tWR correctly and ignores tRAS. Construct a command sequence in which it violates tRAS, and one in which the omission is invisible.
Worked: let tRAS be 8 and the projection above give a tWR deadline. Visible: ACTIVATE at 0, WRITE at 1 — tWR's deadline comes out around 1 + CWL + burst − 1 + tWR, and with small educational magnitudes that can land before tRAS's deadline of 8, so a precharge permitted by tWR violates tRAS. Invisible: ACTIVATE at 0, WRITE at 50 — tRAS expired at 8, long before any tWR deadline, so tWR dominates and the omission never shows. The bug is a function of when the write occurred, which is why the discriminator in §11 is a traffic pattern rather than a signal.
5. Design the check you cannot do by recomputation. Write the strongest available check on the projection, assuming the testbench can observe data beats. Then write the best available check assuming it cannot, and say what the second one fails to catch.
Worked: with data visibility, compare the observed last-data cycle against projected_data_delay + write_cmd_cycle — a measurement against a prediction, which cannot share a bug. Without it, recompute from independently sourced CWL and burst configuration. The second fails to catch a case where the device or PHY delivers data at an offset different from the configured CWL, since both design and checker would then be consistently wrong about reality — which is a Modules 19–21 concern and outside this block's contract.
6. Argue about the two counters. §7 uses pend_cnt and twr_cnt rather than one counter loaded with data_delay + twr_cycles. The single counter computes the same deadline. Make the case for collapsing them, then rebut it using §11.
7. Size the counter. With CWL of 14, burst of 4 cycles and tWR of 15, what is the minimum MAX_CYCLES that keeps every term and the sum representable? What happens at MAX_CYCLES of 16?
Worked: the largest value held is twr_cycles at 15 and the largest computed is data_delay at 17, so MAX_CYCLES must be at least 17 and CNT_W at least $clog2(18) = 5 bits. At MAX_CYCLES of 16, CNT_W is $clog2(17) = 5 bits, which happens to still hold 17 — so this particular case survives, which is a good illustration of why sizing must be argued from the sum rather than checked empirically on one configuration.
15. Summary
tWR constrains a PRECHARGE of a bank from the last write data beat into that bank — bank-local, a minimum separation. Chapter 11.4 owns why the trigger is the data rather than the command, and this chapter reuses its write_recovery_guard rather than rebuilding it.
What this chapter adds is the structural observation that Chapter 14.4 §5's trace made unavoidable: tWR is the only parameter in the module whose triggering cycle must be computed. The other six arm on commands visible on the command bus. The last write data beat appears on no command bus, so a timing scoreboard must project it:
last_write_data_cycle = write_cmd_cycle + CWL + burst_cycles − 1Three terms, three owning chapters, and each of the three ways to get it wrong has a distinct signature. Arming on the command makes the deadline CWL + burst_cycles − 1 cycles early — a genuine violation whose shortfall equals that sum exactly. Using burst transfers where burst cycles belong makes it four cycles late for a burst of 8. Omitting the − 1 makes it one cycle late. The last two are conservative, so no test will ever report them, and they persist as a silent throughput tax.
The non-obvious correctness point is the pending window. Between the WRITE command and the projected last-data cycle, the obligation is not yet armed and a counter reads zero — but the trigger is in the future and its deadline is later than now, so a precharge there violates the obligation retroactively and lands in the middle of the write's own burst. Pending must count as blocked.
twr_deadline_projector keeps the prediction and the obligation in two separate counters so that a design can report which phase a bank is in, and publishes projected_data_delay so the projection can be checked by hand in a minute rather than reverse-engineered from a waveform. §5 is explicit that this chapter publishes no real tWR value or unit form, because I could not verify them from primary documentation — and the chapter's content does not depend on them.
Finally: tWR does not reliably dominate tRAS. Which controls depends on when the write was issued relative to the activate, so the answer is the maximum over both, and assuming an ordering is how one gets skipped.
16. What Comes Next
Every obligation in the module so far has been bank-local — held per bank, and saying nothing about any other bank.
Chapter 14.6 breaks that. Column-to-column spacing is constrained by a resource that every bank shares, and DDR4 splits it into two cases depending on whether the two accesses fall in the same bank group. That is the first genuinely non-bank-local parameter in the module, it is where _S and _L enter — with the generation labelling Chapter 13.3 §4 insisted on — and it is where a per-bank state machine stops being sufficient.
Continue learning
Related tutorials
- Related topic
Write Recovery (tWR)
The bus is free, the controller owes nothing, and the bank still cannot be closed. A precharge issued too early does not delay the write — it interferes with data still being driven into cells.
- 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.
- Related topic
Timing Constraints Catalogue
Four classes of timing constraint, the resource that owns each one, and the equation the whole subject reduces to: a command becomes legal at the maximum of every applicable deadline — never the minimum, and never the most recent.
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.
