DDR · Module 11
Burst Writes
A read's controller receives beats as they arrive. A write's controller must produce every beat on consecutive transfer opportunities, without interruption, because the device is sampling on a schedule and will not wait.
Chapter 11.2 scheduled a deadline. This chapter is the delivery, and it begins from a derivation that is already done.
One write command moves several beats for exactly the same architectural reason a read does. Chapter 10.4 §1 derived it: the array moves far more data per internal access than the interface carries per transfer, so the interface spends several transfer opportunities on what one access produced. That derivation is unchanged by direction and is not repeated here.
What changes is who has to sustain it:
How does a controller deliver a burst it cannot pause — and what does the last beat actually finish?
Both halves matter. A read's controller receives beats as the device produces them. A write's controller must produce every beat, on consecutive transfer opportunities, with no gaps — because the device is sampling on a schedule it derived independently and will not wait. And the answer to the second half is the one this module has been building toward: the last beat finishes less than you would think.
1. Same Reason, Different Burden
The beat count comes from the same place as a read's. Chapter 10.4 §2's table, unchanged:
| Generation | Prefetch | Interface used | Beats | Bytes per access |
|---|---|---|---|---|
| DDR3 | 8n | 64-bit module | 8 | 64 |
| DDR4 | 8n | 64-bit module | 8 | 64 |
| DDR5 | 16n | 32-bit sub-channel | 16 | 64 |
Which burst lengths exist, which are fixed and which are selectable is generation-specific, and Module 12 owns it. "DDR supports BL4, BL8 and BL16" remains a sentence not to write.
Now the difference. Set the two directions side by side for the burst itself:
| Read burst | Write burst | |
|---|---|---|
| Who produces beats | the device | the controller |
| Who must sustain the cadence | the device | the controller |
| A gap in the middle means | cannot occur — the device produces continuously | a hole in the data |
| If the producer stalls | n/a | the device samples whatever is on the bus |
| Consumer's failure mode | missed beats, detectable | n/a |
Read the third row carefully, because it is the burden. A write burst is not a sequence of independent transfers the controller may pace. The device is sampling on consecutive transfer opportunities from the moment its configured offset elapses, and it does not check whether anything meaningful is there. A controller that produces beat 0, stalls, and produces beat 1 two cycles later has not delivered a burst with a delay in it — it has delivered a burst in which beat 1 is garbage and its real beat 1 landed where beat 2 should have been.
2. Ownership for the Duration
Chapter 6.9 established the bidirectional bus and that exactly one side may drive it. Chapter 10.3 §1 applied that to a read, where the DRAM takes ownership for the return.
For a write the controller's side takes it, and holds it for the whole burst:
at the command: controller drives CA. Data bus unowned.
during the gap: data bus unowned. Chapter 11.2's deadline approaching.
at the launch: CONTROLLER/PHY drives DQ, and drives DQS with it.
for every beat: ownership is held continuously.
after the last: ownership released.Two consequences worth stating.
The strobe is sourced by the controller's side for the whole burst. Chapter 6.10 established that the strobe's ownership follows the data's direction exactly — and this is the direction where the controller is the source. Which is the reason write levelling exists, since the strobe must arrive at the device in a defined relationship to the data after a channel the controller cannot observe. Module 21 owns it.
Releasing ownership is as scheduled as taking it. Nothing signals the end either. The controller stops driving after the final beat because it counted, and the device stops sampling because it counted. A disagreement about the beat count is therefore a disagreement about when the bus is free, which is Chapter 6.9's contention hazard reached from a new direction.
3. The Mask Travels With the Beat
One fact from Chapter 6.11, stated because a write transaction cannot be understood without it, and then left there.
A data mask is part of the write data, not part of the command. It is presented per beat, per byte lane, alongside the beat it applies to — so a burst does not carry one mask, it carries one mask per beat.
The consequence for this chapter is structural: whatever produces beats must produce the mask with them, at the same cadence, with the same no-gap obligation. A design that streams data and computes masks on a different path has two things that must stay aligned across a burst that cannot be paused.
Everything else about masking is Chapter 6.11's — the inverted polarity, the per-byte semantics, the fact that the pin may not provide masking at all depending on its configured function, and the write_mask_apply block that resolves which bytes commit. §5's sequencer deliberately carries a mask alongside each beat and does not interpret it, for exactly that reason.
4. What the Last Beat Finishes
This section is short and it is the bridge the whole module turns on.
The final beat finishes the interface transfer. After it: the controller owes nothing further on any wire, the bus is released, the strobe stops, and the payload debt from Chapter 11.1 is discharged.
It does not finish the write.
The data has reached the device's input path. It has not yet been driven into the open row's storage in a way that is safe against the row being closed. Chapter 9.1 §2 established that an open row's values live in the sense amplifiers and that a precharge returns the bitlines toward a balanced condition — and a precharge that happens while the incoming write data is still being driven into that structure does not merely delay the write. It interferes with it.
5. RTL — Producing a Burst Without Gaps
The engineering problem
On a launch deadline, produce exactly the configured number of beats on consecutive cycles to an abstract PHY interface — and report when the payload source could not sustain it, because an underrun has no other symptom.
Why hardware needs it
Something must turn one launch event into N beats and hold the cadence. And because the device cannot be asked to wait, the inability to sustain must be a reported condition rather than a stall.
Classification
SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL, above the PHY boundary.
What it models
Beat production on a launch deadline; the payload handshake; the final interface beat; and underrun detection.
What it does NOT model
Capture, serialisation, the strobe, or the PHY (Modules 19 to 22). Masking semantics (Chapter 6.11). Bus ownership signals (Chapter 6.9) — the ownership is implied by driving and is that chapter's to model. Burst ordering, length selection or efficiency (Module 12). Recovery (Chapter 11.4) — burst_done means the bus is free, nothing more. Multiple outstanding writes.
Interface and parameter contract
// ─────────────────────────────────────────────────────────────────────────
// write_beat_sequencer
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL, above the PHY
// boundary.
//
// EVERYTHING BELOW THIS INTERFACE IS NOT MODELLED HERE: serialisation,
// strobe generation, output drivers, delay adjustment, write levelling and
// calibration are Modules 19-22's. This block presents ALREADY-PARALLEL
// DIGITAL BEATS in the controller's clock domain.
//
// MODELS: beat production on a launch deadline, the payload handshake, the
// final interface beat, and UNDERRUN -- the payload source failing to
// sustain a burst that cannot be paused.
//
// RELATION TO EXISTING BLOCKS: Chapter 4.1's sdr_burst_engine GENERATES an
// indexed sequence from a start pulse, with no payload and no handshake.
// Chapter 10.4's read_beat_collector is a SINK validating a sequence it
// did not produce. This is a SOURCE that must sustain one, and underrun is
// the failure neither of those can express.
//
// DOES NOT INTERPRET THE MASK. It travels with each beat to Chapter 6.11's
// write_mask_apply, which owns what it means.
//
// burst_done MEANS THE BUS IS FREE. It does NOT mean the write is
// finished -- Chapter 11.4 owns what is still owed.
//
// MODELS NO PHYSICAL OR ANALOG BEHAVIOUR.
// ─────────────────────────────────────────────────────────────────────────
module write_beat_sequencer #(
parameter int DATA_W = 32,
parameter int BYTES = DATA_W / 8,
parameter int TAG_W = 3,
// Beats per write. A CONFIGURED value from the generation and the
// mode-register settings in force -- not a property of "DDR".
parameter int BEATS_PER_WRITE = 8,
// DERIVED. Wide enough to hold BEATS_PER_WRITE itself, hence the +1: the
// counter must represent the completed count, not only the indices.
parameter int CNT_W = (BEATS_PER_WRITE <= 1) ? 1 : $clog2(BEATS_PER_WRITE + 1)
) (
input logic clk,
input logic rst_n,
// ── From Chapter 11.2's launch pipeline. The deadline.
input logic launch_due,
input logic [TAG_W-1:0] launch_tag,
// ── Payload source. NOTE: there is no backpressure TOWARD the device,
// so payload_valid going low mid-burst is not a stall -- it is an
// underrun, and the burst continues regardless.
input logic payload_valid,
input logic [DATA_W-1:0] payload_data,
// Per beat, per byte lane, ACTIVE LOW (Chapter 6.11). Carried, not
// interpreted.
input logic [BYTES-1:0] payload_dm_n,
output logic payload_take,
// ── To the abstract PHY.
output logic phy_wr_valid,
output logic [DATA_W-1:0] phy_wr_data,
output logic [BYTES-1:0] phy_wr_dm_n,
output logic phy_wr_first,
output logic phy_wr_last,
// ── Controller-side only. NO TAG CROSSES A DDR INTERFACE in either
// direction (Chapter 10.3 Section 6); this exists so the controller
// can attribute its own beats.
output logic [TAG_W-1:0] phy_wr_tag,
output logic burst_active,
output logic [CNT_W-1:0] beat_index,
// The configured beat count has been delivered. THE BUS IS FREE.
output logic burst_done,
// ── THE WRITE-SPECIFIC ERROR. A beat position arrived and the payload
// source had nothing. The burst does NOT stop: the device is sampling
// on a schedule and a paused burst would corrupt every remaining
// beat's position as well as this one.
output logic underrun,
// A launch while a burst is already active.
output logic err_restart
);
if (DATA_W < 8) begin : g_dw
initial $fatal(1, "write_beat_sequencer: DATA_W must be >= 8");
end
if (BYTES != (DATA_W / 8)) begin : g_by
initial $fatal(1, "write_beat_sequencer: BYTES must be DATA_W/8");
end
if (TAG_W < 1) begin : g_tw
initial $fatal(1, "write_beat_sequencer: TAG_W must be >= 1");
end
// A zero-beat write is not a short write -- it is a command that sends
// nothing, which is not a burst. Rejected structurally.
if (BEATS_PER_WRITE < 1) begin : g_bp
initial $fatal(1, "write_beat_sequencer: BEATS_PER_WRITE must be >= 1");
end
logic [CNT_W-1:0] cnt_q;
logic active_q;
logic [TAG_W-1:0] tag_q;
// ── Is this beat the final one? Compared against the count INCLUDING
// this beat, which is why CNT_W is sized with the +1.
logic [CNT_W-1:0] cnt_with_this;
logic is_last_beat;
always_comb begin
cnt_with_this = cnt_q + CNT_W'(1);
is_last_beat = (cnt_with_this == CNT_W'(BEATS_PER_WRITE));
end
// ── A beat is driven on the launch cycle and on every cycle the burst
// remains active. NOT gated on payload_valid: the device is sampling
// regardless, so suppressing the beat would shift every later beat
// into the wrong position and turn one bad beat into a bad burst.
logic driving;
assign driving = (launch_due && !active_q) || active_q;
assign phy_wr_valid = driving;
assign phy_wr_data = payload_data;
assign phy_wr_dm_n = payload_dm_n;
assign phy_wr_first = driving && !active_q;
assign phy_wr_last = driving && is_last_beat;
assign phy_wr_tag = active_q ? tag_q : launch_tag;
assign payload_take = driving && payload_valid;
// The source had nothing at a beat position. Reported; the beat is
// driven anyway with whatever the data lines carry.
assign underrun = driving && !payload_valid;
assign err_restart = launch_due && active_q;
assign burst_active = active_q;
assign beat_index = cnt_q;
assign burst_done = driving && is_last_beat;
always_ff @(posedge clk) begin
if (!rst_n) begin
cnt_q <= '0;
active_q <= 1'b0;
tag_q <= '0;
end else if (launch_due && !active_q) begin
// Start. The tag is captured and held for the whole burst.
tag_q <= launch_tag;
if (is_last_beat) begin
// Single-beat burst: BEATS_PER_WRITE == 1, first and last on the
// same beat, never entering the active state.
active_q <= 1'b0;
cnt_q <= '0;
end else begin
active_q <= 1'b1;
cnt_q <= CNT_W'(1);
end
end else if (active_q) begin
if (is_last_beat) begin
active_q <= 1'b0;
cnt_q <= '0;
end else begin
cnt_q <= cnt_with_this;
end
end
end
endmoduleState representation and transitions
A count, an active flag, and the burst's tag.
idle --launch_due--> driving(1)
driving(n) --n < N--> driving(n+1)
driving(N) --burst_done--> idleThere is no waiting state, and that absence is the design. A burst, once launched, advances every cycle regardless of the payload source — because the device advances regardless.
Combinational behaviour
The next-count arithmetic, the final-beat comparison, the driving term, the beat outputs, and two error terms. phy_wr_data is passed through ungated, so on an underrun the PHY receives whatever the source was carrying — which is exactly what happens on hardware and is the honest model.
Sequential behaviour and reset
Nonblocking only. Reset abandons a burst in flight, and that is a real hazard with no clean recovery: the device is mid-sample and will continue to the configured beat count. A controller reset during a write burst leaves the device committing whatever the bus carries for the remaining beats. §9's mechanism, and Chapter 11.1 §5's reset note applies with more force here.
Cycle-by-cycle trace
BEATS_PER_WRITE = 4, TAG_W = 3, tag 2, with an underrun on beat 3:
| Cycle | launch_due | payload_valid | phy_wr_valid | first | last | beat_index after | underrun |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | — | — | 0 | 0 |
| 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
| 2 | 0 | 1 | 1 | 0 | 0 | 2 | 0 |
| 3 | 0 | 0 | 1 | 0 | 0 | 3 | 1 |
| 4 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
Cycle 3 is the write-specific failure. The source had nothing; the beat was driven anyway, because suppressing it would move beat 4 into beat 3's position and corrupt the whole remainder rather than one beat. underrun reports it.
Cycle 4 still carries last and still asserts burst_done. The burst completed structurally with one bad beat in it — which is the honest outcome and the reason underrun must be reported rather than inferred from a missing completion.
How to simulate, and expected output
Drive the trace and check every output. Then:
Underrun on each beat position in turn, including the first and the last. The first-beat underrun is the worst case and should be directed: the burst starts with garbage and everything else is correctly positioned around it.
BEATS_PER_WRITE = 1 — first and last on one beat, never entering the active state. This exercises the single-beat path inside the start branch.
launch_due while active must assert err_restart, and note the current burst is not abandoned — abandoning on a suspicious input would corrupt a burst that may be fine.
Reset mid-burst must return to idle. Confirm the model does not pretend to recover: the device's remaining samples are not this block's to fix.
Payload never valid at all must produce a full burst of underruns and still complete.
Expected waveform
§6, which shows a clean burst and an underrun.
Synthesis implications
A CNT_W counter, a TAG_W register, one flag and a few comparators — under ten flops at eight beats. The sequencer is trivial; the payload buffer feeding it is not, and the underrun output exists to make the buffer's adequacy measurable rather than assumed.
Corner cases
BEATS_PER_WRITE == 1 is legal and handled in the start branch. BEATS_PER_WRITE == 0 does not elaborate — a command that sends nothing is not a short burst, and making it a parameter value would turn a bug into a configuration. CNT_W is sized +1 so the counter can represent the completed count, which is the standard off-by-one here and fails only on the last beat of every burst. DATA_W below 8 does not elaborate, and BYTES must be consistent with it.
Failure modes and debugging clues
underrun in a steady stream means the payload buffer cannot sustain the cadence — the buffer is too shallow, or its fill path is too slow for the configured launch depth. err_restart means launches are overlapping, which Chapter 11.1's single-outstanding admission should prevent. burst_done never asserting means BEATS_PER_WRITE exceeds what is being produced, which is a configuration mismatch rather than a sequencing fault.
Extension ideas
A payload FIFO with a fill threshold that gates launch_due upstream turns underrun from a reported failure into a prevented one — which is what real controllers do, and it is the natural next block. Per-tag payload sources are what multiple outstanding writes need.
Limitations
One burst at a time, one rank. It carries the mask without interpreting it. It does not model the bus ownership signals themselves. And it cannot tell a correct beat from an incorrect one — it delivers what the source gave it, and whether that was the right data is a scoreboard question.
6. A Burst, in Cycles
write_beat_sequencer — a burst that cannot pause
10 cyclesThe first burst is the normal case: four consecutive beats, first on one, last on the last, burst_done at the end.
The second burst is the lesson. At cycle 8 payload_valid is low. phy_wr_valid stays high. The beat is driven with whatever the data lines carry, underrun reports it, and the burst continues.
That is deliberate and it is not a bug. Suppressing the beat would leave the device sampling nothing at that position and would push the real remaining beat into the next position — turning one corrupted beat into a corrupted burst. The device is counting transfer opportunities, not valid ones. Staying in step and reporting the hole is strictly better than falling out of step.
And burst_done still asserts at cycle 9. The interface transfer completed. The bus is free. One beat of it is garbage, and the only evidence is the underrun output — which is why §5 makes it a first-class signal rather than an inference from something missing.
EDUCATIONAL — NOT TO SCALE, NOT JEDEC TIMING. One beat per cycle is Chapter 10.4 §6's readability convention; the real cadence is two transfers per clock period.
7. Four Assertions Worth Writing
// P1 -- exactly the configured number of beats, and last only on the
// final one. The structural contract: a burst that delivers the wrong
// count leaves the device sampling positions nobody filled, or filling
// positions the device has stopped sampling.
property p_last_only_on_final_beat;
@(posedge clk) disable iff (!rst_n)
phy_wr_last |-> phy_wr_valid
&& (beat_index == CNT_W'(BEATS_PER_WRITE - 1));
endproperty
assert property (p_last_only_on_final_beat);
// P2 -- the burst never pauses. Once active and not on the final beat,
// a beat must be driven on the very next cycle. THE chapter's property:
// it forbids the gap that corrupts every subsequent beat position, and
// it is what a naive payload-gated design breaks.
property p_burst_never_gaps;
@(posedge clk) disable iff (!rst_n)
(phy_wr_valid && !phy_wr_last) |=> phy_wr_valid;
endproperty
assert property (p_burst_never_gaps);
// P3 -- an underrun is reported exactly when the source was dry at a
// driven beat. Both directions: under-reporting hides silent corruption,
// over-reporting trains an engineer to ignore the output.
property p_underrun_reported_exactly;
@(posedge clk) disable iff (!rst_n)
underrun == (phy_wr_valid && !payload_valid);
endproperty
assert property (p_underrun_reported_exactly);
// P4 -- the tag is stable across a burst, and burst_done carries it. A
// tag changing mid-burst would split one write's beats across two
// records, and both halves would look like well-formed partial writes.
property p_tag_stable_within_burst;
@(posedge clk) disable iff (!rst_n)
(phy_wr_valid && !phy_wr_first)
|-> (phy_wr_tag == $past(phy_wr_tag));
endproperty
assert property (p_tag_stable_within_burst);What these prove. P1 pins the structure. P2 is the chapter's central property — it forbids the gap, and it is precisely what a design that gates phy_wr_valid on payload_valid violates, which is the most natural wrong implementation. P3 makes the underrun trustworthy in both directions. P4 protects attribution across the burst.
What these do not prove. Nothing here says the data was correct. The block delivers what the source gave it; if the source gave it the wrong values, every property passes. Nothing says the burst was launched at the right time — Chapter 11.2's deadline is an input here, and a burst delivered perfectly at entirely the wrong cycle satisfies all four. Nothing says the write is finished — burst_done means the bus is free, and Chapter 11.4 owns what is still owed. Nothing proves BEATS_PER_WRITE is right; a sequencer configured wrongly satisfies every property while delivering the wrong number of beats. And nothing proves anything below the boundary — no property here concerns a strobe, a driver, serialisation or a channel.
8. DV — When Should the Reference Memory Update?
This is the question a write environment must answer deliberately, and the wrong answers are all tempting.
Not at admission. Chapter 11.1 §8 showed the failure: the model diverges from the device for the whole launch interval, and a read of that location during the interval is reported as an error while the device is correct.
Not at the launch deadline. Better, and still early — the beats have not been delivered, and an underrun during the burst would leave the model holding data the device never received.
Not at recovery completion. Too late, and for a subtle reason: recovery is about when the row may be closed, not about when the data is readable. A read of the same open row after the burst is legal and should return the new data; a model that waits for recovery would report a stale value for a device that is behaving correctly.
At the final delivered beat — burst_done — is the defensible choice for a functional reference model, and the reasoning is worth stating rather than asserting:
after the last beat:
every beat has crossed the interface
the device has received the complete write
a subsequent read of the same open row should see it
the bus is free
what is NOT true:
the write is finished (Chapter 11.4)
the row may be closed (Chapter 11.4)Two further requirements:
Apply the mask when updating. A model that writes whole beats ignoring Chapter 6.11's per-byte mask will diverge on every partially masked write, and the divergence is silent until something reads a byte that should have been preserved.
Treat underrun as a data-correctness event, not a performance warning. A burst with an underrun delivered wrong data to a real location. The reference model cannot know what the device actually received, so the honest handling is to mark that location as unknown rather than to record either the intended or the observed value.
9. Debugging — First Beat Correct, Later Beats Shifted
Symptom. Reading back a written location shows the first beat's worth of data correct and subsequent portions wrong — often shifted by one position, or containing the previous write's tail.
Candidate mechanisms.
BEATS_PER_WRITEdoes not match the device's configured burst length, so the controller stops driving before the device stops sampling, or keeps driving after it stopped.- The burst was gated on
payload_valid— a gap was inserted, and every beat after it landed one position late. P2's failure, and the most likely cause of this exact symptom. - An underrun occurred and was not reported or not noticed, so one beat is garbage and the rest are correctly placed.
- The payload source is presenting beats in the wrong order — a segmentation or slicing fault above this block.
- The launch happened at the wrong cycle, so the whole burst is displaced. Chapter 11.2 §9 — and the tell is that the first beat would also be wrong, which this symptom excludes.
Evidence to collect. BEATS_PER_WRITE against the programmed mode-register burst configuration. underrun across the failing burst. Whether phy_wr_valid was continuous — this is the decisive one and is a single signal. The beat index sequence. And whether the first beat was genuinely correct, which distinguishes this from a launch-timing fault.
Discriminator.
- Was
phy_wr_validcontinuous across the burst? A gap is mechanism 2 and explains the symptom completely. One signal, and it should be checked first, because the fix is architectural rather than a constant. - Did
underrunassert? Mechanism 3, and the position of the assertion tells you which beat is garbage. - Compare
BEATS_PER_WRITEagainst the programmed burst length. A mismatch is mechanism 1, and its signature differs: the error is at the end of the burst rather than distributed from a gap. - Is the first beat correct? If no, this is not the right chapter — the whole burst is displaced and Chapter 11.2's launch timing is the subject.
- If the beats were continuous, the count was right, and no underrun occurred, mechanism 4: the data reaching this block was already in the wrong order, and the fault is in the payload source's segmentation.
Responsible layer. Mechanisms 1, 2, 3 and 4 are the controller. Mechanism 5 is also the controller but a different chapter. None is a PHY fault — and that is worth knowing, because "the data is shifted" instinctively suggests a capture problem, which is a read-side intuition that does not transfer.
Fix. For mechanism 2, never gate the burst on payload availability — buffer ahead and gate the launch, which is §5's extension idea and what real controllers do. For the others, per mechanism.
10. Common Misconceptions
"One WRITE command writes one beat."
Why it is tempting: one command, one operation, one result.
Concrete failure: a controller that drives one beat and stops. The device samples the configured number of positions, so every position after the first receives whatever the bus carried — and the write appears to partially succeed.
Correct model: the beat count follows from the prefetch relationship. §1.
Prevention: build the sequencer before the launch logic; a model that counts cannot express a single beat.
"A write burst can be paused if the data is not ready."
Why it is tempting: every other data interface an engineer has used has backpressure, and gating valid on ready is instinctive.
Concrete failure: the most damaging in this chapter. A gap moves every subsequent beat one position late, so instead of one bad beat the burst has one bad beat and all the rest in the wrong place — and the device, which counted transfer opportunities, commits all of it.
Correct model: there is no backpressure toward the device. Stay in step and report the hole. §1 and P2.
Prevention: P2, and buffering ahead so the launch is gated instead of the beats.
"Every DDR generation uses the same burst length."
Why it is tempting: BL8 is quoted so often it reads like a constant.
Concrete failure: a controller configured for 8 beats against a device expecting 16 stops driving halfway; the device samples the remaining positions from an undriven bus.
Correct model: generation- and configuration-specific. §1's table, and Module 12 for the full picture.
Prevention: take the number from the programmed mode registers, never from a default.
"The final beat means the bank can be precharged."
Why it is tempting: the data is gone, the bus is free, and nothing more is owed by the controller.
Concrete failure: Chapter 11.4's corruption case — a precharge issued into a device still driving the incoming data into the open row's storage.
Correct model: burst_done means the bus is free. §4, and the next chapter.
Prevention: the signal naming. A block whose output is called burst_done rather than write_done cannot be misread as finishing the write.
"A burst of 8 beats takes 8 clock cycles."
Why it is tempting: teaching waveforms — including §6's — draw one beat per cycle.
Concrete failure: a launch-to-recovery budget that double-counts the burst term, or a trace analysis looking for the final beat twice as far out.
Correct model: two transfers per clock period, so 8 beats occupy 4 periods. Chapter 10.4 §6.
Prevention: write the units — beats ÷ 2 = CK periods.
"The reference memory should update when the physical cells are written."
Why it is tempting: it sounds like the most accurate possible model.
Concrete failure: a model that waits for recovery reports stale values for reads of the same open row that the device answers correctly — and the "accuracy" is illusory anyway, since nothing observable marks the physical commitment.
Correct model: a functional memory model is an abstraction chosen for a purpose, and the purpose is answering read-back questions. burst_done is the defensible point. §8.
Prevention: keep the functional model and the recovery checker as separate components with separate questions.
11. Interview Reasoning
"Why does one write command deliver several beats?"
For the same reason a read returns several: the array moves far more data per internal access than the interface carries per transfer, so the interface spends several transfer opportunities on what one access produced. The ratio is the prefetch depth, and it is the same number in both directions. What differs is who must sustain the cadence — the device produces a read burst and the controller produces a write burst, and only one of those two can fail to keep up.
"Can a write burst be paused?"
No, and the instinct to pause it is the most damaging mistake in this area. There is no backpressure toward the device — no not-ready signal, no retry — so the device samples consecutive transfer opportunities from the moment its configured offset elapses. A controller that inserts a gap does not delay the burst; it moves every subsequent beat one position late, so one unavailable beat becomes a corrupted burst. The correct behaviour is to stay in step, drive the position anyway, and report the hole — which is why an underrun output is a first-class signal rather than something inferred from a missing completion.
"Who drives DQS during a write, and why does it matter here?"
The controller's side, for the whole burst — the strobe's ownership follows the data's direction. It matters because the controller is now responsible for a timing relationship it cannot observe: the strobe must arrive at the device in a defined relationship to the data after traversing a channel, and that relationship is what write levelling calibrates. It also means releasing the bus is as scheduled as taking it — the controller stops driving because it counted and the device stops sampling because it counted, so a disagreement about the beat count is a disagreement about when the bus is free.
"When should a write scoreboard update its reference memory?"
At the final delivered beat. Not at admission — the model would diverge for the whole launch interval and report errors for a correct device. Not at recovery completion — recovery is about when the row may be closed, not when the data is readable, so a read of the same open row after the burst should already see the new value. At burst_done every beat has crossed the interface and a read-back should observe it. And the masking must be applied on update, or every partially masked write diverges silently. Crucially, the functional model and the recovery checker should stay separate components: merging them produces something wrong about both.
"Data written to a location reads back with the first part correct and the rest shifted. Where do you look?"
At whether the beat valid was continuous across the burst — one signal, and a gap explains the symptom completely. That is the payload-gating mistake, and it is the most likely cause because gating valid on ready is what every other interface trains you to do. If the beats were continuous, check for an underrun and its position; then compare the configured beat count against the programmed burst length, whose signature differs because the error clusters at the end rather than from a gap. And note the first beat being correct rules out a launch-timing fault, which would displace the whole burst.
12. Engineering Exercise
BEATS_PER_WRITE = 8, DATA_W = 32, TAG_W = 3.
1. How many clock periods of transfer opportunity does one burst occupy?
2. How wide must beat_index be, and why is $clog2(8) wrong?
3. A designer gates phy_wr_valid on payload_valid. The source is dry for one cycle during beat 3. Describe exactly what the device receives.
4. The same source is dry and the design does not gate. Describe what the device receives now, and say which is better.
5. BEATS_PER_WRITE is 8 and the device is configured for 16. What happens to positions 9 through 16?
6. Give the defensible reference-memory update point and two update points that are wrong, with the failure each produces.
13. Summary
One write command delivers several beats for the same architectural reason a read returns several — the array moves more per access than the interface carries per transfer. The derivation is Chapter 10.4's and the beat counts are generation-specific.
What changes is who sustains the cadence. A read's controller receives; a write's controller must produce every beat on consecutive transfer opportunities. There is no backpressure toward the device and no retry.
So a write burst cannot be paused. Inserting a gap moves every subsequent beat one position late, turning one unavailable beat into a corrupted burst. Stay in step, drive the position anyway, and report the hole — which is why underrun is a first-class output rather than an inference from something missing.
The controller's side owns DQ and DQS for the whole burst, and releases them on a count rather than a signal — so a disagreement about the beat count is a disagreement about when the bus is free.
The mask travels with the beat, not the command, so whatever produces beats must produce masks at the same cadence under the same no-gap obligation.
And the last beat finishes the interface transfer and not the write. The bus is free, the payload debt is discharged, and the bank still cannot be closed — which is the next chapter and the reason the signal is called burst_done.
14. What Comes Next
Every beat has been delivered. The bus is free. The controller owes nothing on any wire.
Chapter 11.4 — Write Recovery (tWR) is about why the write is still not finished.
The data has reached the device's input path; it has not yet been driven into the open row's storage in a way that survives the row being closed. A precharge issued now does not delay the write — it interferes with it, because Chapter 9.1 §4 established what a precharge does to the bitlines that the incoming data is still being driven onto.
That chapter derives the mechanism before naming the parameter: what the interval protects, which event starts the obligation, what operation is blocked, and how a controller represents an obligation that belongs to the device rather than to itself. It is the largest structural difference between a read transaction and a write one, and it is the reason a write's record outlives its data.
Return to Burst Reads for the prefetch derivation and the beat counts, DM for what the mask means and how it is applied, The DQ Bus for ownership and contention, DQS for the strobe the controller sources here, Write Latency (CWL) for the deadline that starts this burst, and SDR SDRAM for the beat engine that generates without sustaining.
Continue learning
Related tutorials
- Related topic
Burst Reads
One read command returns several transfers because the array moves more data per access than the interface is wide. That makes beat counting a correctness obligation, not bookkeeping.
- Related topic
SDR SDRAM
Making DRAM synchronous replaced an analog timing negotiation with a clocked contract, which is what made pipelining and counted bursts possible. It also fixes the vocabulary the rest of the curriculum depends on: clock frequency, transfer rate, data rate and bandwidth are four different quantities.
- Related topic
Ranks
A rank is the set of devices that together supply the controller's data width. Because ranks share one physical data bus, only one may drive it at a time — which makes a rank a question of resource ownership, and means adding ranks adds capacity without adding bandwidth.
- Related topic
DQ — The Data Bus
Saying DQ is bidirectional says almost nothing. What matters is who drives these wires right now, how ownership changes, and what guarantees both sides never drive at once — which is a state machine, not a property.
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.
