DDR · Module 20
Read Capture
A read strobe arrives aligned to the data's transitions, so it cannot be used as it stands. Capture means shifting it, ordering two edge streams into one beat sequence, and absorbing a burst that cannot be told to wait.
Chapter 19.1 §4 drew a box labelled CAPTURE and said Module 20 would open it. 19.4 delivered a gated strobe to its edge and stopped. This chapter opens it.
Three things happen inside, and they are three genuinely different problems:
The strobe must be shifted, because 20.2 §4 established it arrives aligned to the data's transitions rather than its centre. The two edge polarities must be turned back into one ordered beat sequence. And the burst must be absorbed, because nothing downstream can tell the device to wait.
Only the second and third are expressible in portable RTL. The first is the one that decides whether the interface works, and this chapter is explicit about that asymmetry rather than quietly building a model that appears to cover all three.
1. What the Box Contains
Open it and there are three stages, not one.
gated strobe + DQ <- 19.4 delivered this
|
v
[A] PHASE SHIFT technology-specific. Not RTL.
| move the strobe from the data's edge toward its centre
v
[B] SAMPLE technology-specific. Not RTL.
| two events per strobe cycle, one per polarity
v
captured rise/fall samples <- THE CAPTURE CONTRACT (§3)
|
v
[C] NORMALISE + ABSORB digital. This chapter's RTL.
| order the edges, buffer the burst
v
ordered beat stream -> 19.2's assembler takes itStages A and B are where the interface succeeds or fails, and neither is portable RTL. A phase shift of a fraction of a bit interval is a delay element; a sample against a data-rate strobe is a dedicated capture cell. Chapter 19.1 §5 listed both as things RTL cannot express, and nothing about being in Module 20 changes that.
Stage C is genuinely digital, genuinely necessary, and genuinely capable of being wrong. It is where this chapter's two blocks live, and §13's failure trace shows the corruption it produces when it is.
2. Gate Correct Is Not Capture Correct
The distinction that separates this module from the last, stated before anything is built on it.
The gate asks: should the receiver be listening now? Chapter 19.4 answered it with a predicted window, and its failure mode is displacement — beats lost from the start of a burst, or floating-line noise admitted before it.
Capture asks: at what point relative to this strobe should DQ be sampled? Its failure mode is corruption — the right number of beats, carrying wrong values.
| Gate (19.4) | Capture (this chapter) | |
|---|---|---|
| Question | listen when? | sample where within? |
| Granularity | whole cycles | a fraction of a bit interval |
| Failure signature | beats missing or extra | beats present, values wrong |
| Determinism | deterministic displacement | often rate- and temperature-sensitive |
| Detectable digitally? | yes — beat count is wrong | no — the count is right |
The last row is the practical consequence and it drives §17's debugging order. A beat-count error is visible to a digital checker. A sampling-phase error is not, because every digital observable is correct: the gate opened, the right number of edges arrived, the right number of beats emerged, the framing was right. Only the data is wrong, and nothing in the receive path knows what the data should have been.
So the two are diagnosed by opposite evidence: count the beats first. If the count is wrong it is 19.4's problem and this chapter is a distraction. If the count is right and the data is wrong, the gate is exonerated and this chapter's §12 applies.
3. The Capture Contract
What a captured sample is, defined explicitly because everything downstream depends on it.
one capture event =
a DQ value, sampled once, by the capture circuitry,
against one edge of the shifted strobe,
belonging to one lane,
with that edge's polarity recorded.
produced by : technology-specific capture circuitry (§1 stage B)
consumed by : this chapter's normaliser (§5)
stallable : NO — see §7Four properties, each load-bearing:
A value. LANE_W bits — one lane's worth (20.2 §2).
A polarity. Which strobe edge produced it. This is not decoration: §6's ordering depends on it, and losing it produces §13's failure.
A lane identity. Chapter 20.2 §5's lane_strobe_binding checks that this identity is honest.
Burst framing. Which sample begins a burst. Chapter 19.2 §6 established why framing must come from the capture side rather than a local counter: a counter resynchronises wrongly after any disturbance and shifts every subsequent word.
4. The Read Capture Flow
Two rows are the chapter's boundaries. “shift strobe by trained phase” is where the interface succeeds or fails, and it is inside Capture HW — not in any RTL here. “order edges, buffer burst” is this chapter's contribution, and it is entirely in PHY digital.
5. Two Streams, One Sequence
The capture circuitry produces samples on both strobe polarities. Chapter 20.2 §3 established the correspondence: eight beats on eight edges, alternating.
But the two polarities arrive as two separate event streams, because they are taken by different capture elements. Turning them back into one ordered sequence is a digital problem with a specific, deterministic bug:
what capture produces what the assembler needs
rise stream: D0, D2, D4, D6 D0, D1, D2, D3, D4, D5, D6, D7
fall stream: D1, D3, D5, D7
get the interleave backwards and you get:
D1, D0, D3, D2, D5, D4, D7, D6
every pair swapped. Deterministic. The beat COUNT is correct.That is a permutation, not corruption — and the distinction matters for diagnosis. Corrupt data varies; a swapped interleave produces the same wrong answer every time, which §17 uses as the discriminator.
6. The Edge Normaliser
// ─────────────────────────────────────────────────────────────────────
// capture_event_normalizer
//
// CLASSIFICATION
// Synthesizable educational RTL. Sequential. One responsibility:
// take ALREADY-CAPTURED rise and fall samples and emit them as one
// ordered beat sequence with correct burst framing.
//
// ITS INPUTS ARE SAMPLES, NOT SIGNALS. It does not sample anything.
//
// WHAT IT DOES NOT MODEL
// - NO SAMPLING. rise_sample and fall_sample arrive already taken by
// technology-specific capture circuitry (§1 stage B). There is no
// flop clocked by a strobe anywhere in this module.
// - NO PHASE SHIFT. The strobe was shifted before these samples
// existed; the amount is Module 21's to find and Chapter 19.5's
// to hold. This block has no representation of it (§1 stage A).
// - NO SAMPLE QUALITY. A sample taken at a transition edge is
// indistinguishable here from one taken dead centre (§3). Nothing
// this block outputs carries any information about margin.
// - No gate. Chapter 19.4 decides whether to listen.
// - No word assembly. Chapter 19.2's phy_read_assembler consumes
// this block's output; a second assembler is not built here.
// - No backpressure toward capture. §7 explains why there cannot be.
// ─────────────────────────────────────────────────────────────────────
module capture_event_normalizer #(
parameter int LANE_W = 8,
// 0 = rising edge carries the EVEN beats (0,2,4,...)
// 1 = falling edge carries the even beats
// Present as a parameter because the correspondence is an
// architectural convention, not a logic truth -- and getting it
// backwards is §13's failure. A parameter makes the convention a
// stated decision rather than a buried assumption.
parameter bit RISE_IS_EVEN = 1'b1
) (
input logic clk,
input logic rst_n,
// ── Already-captured samples, one per strobe polarity. Both may
// present in the same internal cycle: the strobe runs faster than
// this clock, so one internal cycle spans both polarities.
input logic rise_valid,
input logic [LANE_W-1:0] rise_sample,
input logic fall_valid,
input logic [LANE_W-1:0] fall_sample,
// ── Burst framing from the capture side. Chapter 19.2 §6 established
// why this is an input rather than a local counter: a counter
// resynchronises wrongly and shifts every later word.
// burst_last is needed as well as burst_first: without it the
// block cannot tell when a burst has ENDED, so it cannot detect a
// sample arriving outside one (see err_sample_without_frame).
input logic burst_first,
input logic burst_last,
// ── The ordered beat stream. Two beats can become available in one
// cycle, so the consumer may take one per cycle and the second
// waits -- which is what makes §7's buffering necessary.
output logic beat_valid,
output logic [LANE_W-1:0] beat_data,
output logic beat_is_even,
output logic beat_first,
input logic beat_ready,
output logic err_both_pending_dropped,
output logic err_sample_without_frame
);
if (LANE_W < 1) $fatal(1, "capture_event_normalizer: LANE_W must be >= 1");
// ── The second sample of a cycle is held while the first is offered.
// One slot is sufficient and no more: at most two samples arrive
// per cycle and at least one leaves whenever the consumer is
// ready, so the backlog cannot exceed one WITHIN this block.
// A sustained consumer stall is a different problem and is §8's.
logic held;
logic [LANE_W-1:0] held_data;
logic held_even;
logic held_first;
// Declared here, with the other state, because it is read by a
// continuous assignment below -- a variable must be declared before
// the assignment that reads it.
logic in_burst;
// ── Which polarity goes first in a cycle carrying both. This is the
// ordering convention, expressed once.
logic first_is_rise;
assign first_is_rise = RISE_IS_EVEN;
logic beat_fire;
assign beat_fire = beat_valid && beat_ready;
// ── Present the held sample if there is one, otherwise the leading
// polarity of this cycle.
always_comb begin
if (held) begin
beat_valid = 1'b1;
beat_data = held_data;
beat_is_even = held_even;
beat_first = held_first;
end else if (first_is_rise && rise_valid) begin
beat_valid = 1'b1;
beat_data = rise_sample;
beat_is_even = 1'b1;
beat_first = burst_first;
end else if (!first_is_rise && fall_valid) begin
beat_valid = 1'b1;
beat_data = fall_sample;
beat_is_even = 1'b1;
beat_first = burst_first;
end else if (first_is_rise && fall_valid) begin
beat_valid = 1'b1;
beat_data = fall_sample;
beat_is_even = 1'b0;
beat_first = 1'b0;
end else if (!first_is_rise && rise_valid) begin
beat_valid = 1'b1;
beat_data = rise_sample;
beat_is_even = 1'b0;
beat_first = 1'b0;
end else begin
beat_valid = 1'b0;
beat_data = '0;
beat_is_even = 1'b0;
beat_first = 1'b0;
end
end
// ── Both samples present and the consumer did not take one: the
// trailing sample would be lost. Reported, because a dropped
// captured beat cannot be recovered -- the device will not resend.
assign err_both_pending_dropped =
rise_valid && fall_valid && held && !beat_fire;
// ── A sample outside any framed burst. Distinguished from the above
// because the diagnosis differs: this points at Chapter 19.4's
// gate admitting something it should not have.
assign err_sample_without_frame = (rise_valid || fall_valid)
&& !burst_first && !held && !in_burst;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
held <= 1'b0;
held_data <= '0;
held_even <= 1'b0;
held_first <= 1'b0;
in_burst <= 1'b0;
end else begin
// in_burst must be CLEARED as well as set. Setting it on
// burst_first alone latches it high after the first burst, and
// err_sample_without_frame then never fires again -- the check
// would silently stop working after one burst.
if (burst_first) in_burst <= 1'b1;
else if (burst_last) in_burst <= 1'b0;
// ── Release first, then capture the trailing sample. A leading
// sample taken this cycle frees the slot for the trailing one
// in the same cycle, which is the whole point of the slot.
if (held && beat_fire) held <= 1'b0;
// Hold the TRAILING polarity when both arrive and the leading
// one is being taken. If the leading one is NOT taken, the slot
// is already occupied and err_both_pending_dropped fires above.
if (rise_valid && fall_valid && (!held || beat_fire)) begin
held <= 1'b1;
held_data <= first_is_rise ? fall_sample : rise_sample;
held_even <= 1'b0;
held_first <= 1'b0;
end
end
end
endmoduleSimulating it. Drive rise_valid and fall_valid together with burst_first on the first cycle and beat_ready high: the block emits the rising sample with beat_is_even high and beat_first high, then the falling sample with both low, one per cycle. Hold beat_ready low with both samples arriving and err_both_pending_dropped asserts — which is §7's problem arriving one level down.
Synthesis. One LANE_W-wide holding register plus three flags and a small mux tree. Instantiated per lane, because 20.2 §1 established the strobe is the unit of timing authority and therefore of capture.
What is unrealistic about it. Both samples appearing in one internal cycle is a modelling convenience that stands in for a real rate relationship (19.2 §2's ratio). Real capture hardware presents samples in an arrangement set by the PHY's clocking, and a real normaliser is shaped by that arrangement. The ordering lesson transfers; the cycle structure is illustrative.
7. The Burst Cannot Be Told to Wait
The architectural fact of the chapter, and the one most often designed wrong.
beat_ready in §6 points downstream, not upstream. There is no signal anywhere in a DDR interface by which a receiver tells a device to pause mid-burst. Chapter 19.2 §4 established this for the read path in general; here is its sharpest consequence.
the asymmetry, stated plainly
DEVICE SIDE the burst arrives because the device was
told to send it, CL cycles ago. It is
executing a schedule. There is no
mechanism to interrupt it.
DIGITAL SIDE the consumer may stall for its own reasons
-- a full queue, a busy assembler, a
controller not accepting words.
BETWEEN THEM something must absorb the difference, or
captured data is lost. Lost, not delayed.8. How Much Buffering Is Enough
The rule is to derive the depth rather than choose it, because a chosen depth carries an unstated assumption.
EDUCATIONAL MODEL — the rates below are this model's, chosen so the
derivation is traceable. A real PHY's arrival and service rates
follow from its clocking ratio (19.2 §2).
arrival 2 captured samples per internal cycle (one per polarity)
service 1 beat per internal cycle, when the consumer accepts
burst B beats, arriving over B/2 internal cycles
stall S internal cycles inside the burst with no acceptance
over the burst:
arrived = B
served = B/2 - S
------------------------------------
peak occupancy = B - (B/2 - S) = B/2 + SBurst B | S = 0 | S = 1 | S = 2 | S = 4 |
|---|---|---|---|---|
| 4 | 2 | 3 | 4 | 6 |
| 8 | 4 | 5 | 6 | 8 |
| 16 | 8 | 9 | 10 | 12 |
So the depth requirement is B/2 + S_max, and the important half of that expression is the second term. B/2 is fixed by the burst and the rate ratio; S_max is an assumption about the downstream that the buffer is built on.
A depth chosen without stating S_max is a depth with a hidden contract. It will work until the consumer stalls one cycle longer than anybody wrote down, and then it will lose data — under load, intermittently, in a way that looks nothing like a buffer problem.
Worked for an eight-beat burst tolerating a two-cycle stall: 8/2 + 2 = 6. §9's block defaults to that and asserts the bound rather than assuming it.
9. The Receive Buffer
// ─────────────────────────────────────────────────────────────────────
// capture_rx_buffer
//
// CLASSIFICATION
// Synthesizable educational RTL. Sequential. One responsibility:
// absorb the difference between a NON-STALLABLE arrival side and a
// stallable consumer, and report when it cannot.
//
// WHAT IT DOES NOT MODEL
// - NO BACKPRESSURE TOWARD CAPTURE, BY DESIGN. There is no ready
// output on the write side, because no such signal exists in a
// DDR interface (§7). Overflow is therefore DATA LOSS, not a
// wait, and err_overflow says so.
// - No sampling, no phase, no sample quality (§1, §3).
// - No word assembly. Chapter 19.2's phy_read_assembler consumes
// this buffer's output.
// - No burst-length policy. Chapter 12.1 owns burst length; DEPTH
// here is derived from it plus a stated stall bound (§8), and the
// derivation is the caller's to justify.
// ─────────────────────────────────────────────────────────────────────
module capture_rx_buffer #(
parameter int LANE_W = 8,
// Derived, not chosen: B/2 + S_max for the model of §8.
// Default is an 8-beat burst tolerating a 2-cycle consumer stall.
parameter int DEPTH = 6,
// The stall bound the DEPTH was derived under. Carried as a
// parameter so the ASSUMPTION travels with the module rather than
// living in a comment somebody deletes (§8).
parameter int STALL_BOUND = 2,
parameter int PTR_W = (DEPTH <= 1) ? 1 : $clog2(DEPTH),
parameter int CNT_W = $clog2(DEPTH + 1)
) (
input logic clk,
input logic rst_n,
// ── Arrival side. NO READY. A beat offered here is taken or lost.
input logic in_valid,
input logic [LANE_W-1:0] in_data,
input logic in_is_even,
input logic in_first,
// ── Consumer side. THIS side may stall.
output logic out_valid,
output logic [LANE_W-1:0] out_data,
output logic out_is_even,
output logic out_first,
input logic out_ready,
output logic [CNT_W-1:0] occupancy,
output logic full,
output logic empty,
// Sticky: a lost captured beat is unrecoverable and must never be
// a transient nobody noticed.
output logic err_overflow,
// The §8 assumption, checked at run time: the consumer stalled for
// longer than the depth was derived to absorb. Fires BEFORE
// overflow does, which makes it the useful warning.
output logic err_stall_bound_exceeded
);
if (LANE_W < 1) $fatal(1, "capture_rx_buffer: LANE_W must be >= 1");
if (DEPTH < 1) $fatal(1, "capture_rx_buffer: DEPTH must be >= 1");
if (STALL_BOUND < 0) $fatal(1, "capture_rx_buffer: STALL_BOUND must be >= 0");
logic [DEPTH-1:0][LANE_W-1:0] mem;
logic [DEPTH-1:0] even_f;
logic [DEPTH-1:0] first_f;
logic [PTR_W-1:0] wptr, rptr;
logic [CNT_W-1:0] stall_run;
logic do_write, do_read;
assign full = (occupancy == CNT_W'(DEPTH));
assign empty = (occupancy == '0);
assign out_valid = !empty;
assign do_read = out_valid && out_ready;
// A beat is written whenever one is offered and there is room. When
// there is NO room it is lost -- which is why do_write and in_valid
// are different signals, and why the difference is an error.
assign do_write = in_valid && (!full || do_read);
assign out_data = mem[rptr];
assign out_is_even = even_f[rptr];
assign out_first = first_f[rptr];
assign err_stall_bound_exceeded = stall_run > CNT_W'(STALL_BOUND);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wptr <= '0;
rptr <= '0;
occupancy <= '0;
err_overflow <= 1'b0;
stall_run <= '0;
end else begin
if (in_valid && !do_write) err_overflow <= 1'b1;
if (do_write) begin
mem[wptr] <= in_data;
even_f[wptr] <= in_is_even;
first_f[wptr] <= in_first;
wptr <= (int'(wptr) == DEPTH-1) ? '0 : wptr + PTR_W'(1);
end
if (do_read)
rptr <= (int'(rptr) == DEPTH-1) ? '0 : rptr + PTR_W'(1);
// ── Occupancy as a NET update. Simultaneous write and read is
// zero change, not two independent adjustments -- the same
// discipline Chapter 17.2 §8 applied to its entry pool.
case ({do_write, do_read})
2'b10: occupancy <= occupancy + CNT_W'(1);
2'b01: occupancy <= occupancy - CNT_W'(1);
default: occupancy <= occupancy;
endcase
// ── Consecutive cycles in which a beat was available and the
// consumer did not take it. Saturating, so a long stall
// cannot wrap back under the bound and look acceptable.
if (out_valid && !out_ready) begin
if (!(&stall_run)) stall_run <= stall_run + CNT_W'(1);
end else begin
stall_run <= '0;
end
end
end
endmoduleSimulating it. Offer beats every cycle with out_ready high: occupancy oscillates near zero and nothing errors. Drop out_ready for three cycles with STALL_BOUND = 2 and err_stall_bound_exceeded asserts on the third — before any data is lost, which is the point of carrying the assumption as a parameter. Keep it low until occupancy reaches DEPTH and the next arriving beat sets err_overflow.
Synthesis. DEPTH × (LANE_W + 2) flops plus two pointers and a counter — for the defaults, 60 bits of storage. Per lane. The occupancy counter is sized $clog2(DEPTH+1) because it counts to DEPTH, not to DEPTH−1; sizing it as a pointer width truncates a full buffer to empty, which is the same count-versus-index trap Chapter 17.2 §8 documented.
10. A Read Burst, Captured
EDUCATIONAL — cycle numbers show ordering only. No pin timing, no sub-interval phase and no device parameter is implied; §1 stage A is not modelled.
Eight beats, DEPTH = 6, consumer accepting one per cycle throughout.
A read burst through the digital receive path — samples in, ordered beats out
10 cycles cyc rise fall burst_ norm norm buf buf out out
vld vld first valid beat wr occ valid data
─── ──── ──── ────── ───── ───── ──── ─── ───── ─────
0 1 1 1 1 D0 1 1 0 --
1 1 1 0 1 D1* 1 1 1 D0
2 1 1 0 1 D2 1 1 1 D1
3 1 1 0 1 D3* 1 1 1 D2
4 0 0 0 1 D4 1 1 1 D3
5 0 0 0 1 D5* 1 1 1 D4
6 0 0 0 1 D6 1 1 1 D5
7 0 0 0 1 D7* 1 1 1 D6
8 0 0 0 0 -- 0 0 1 D7
9 0 0 0 0 -- 0 0 0 --
* = the trailing polarity of a cycle, emitted from the held slotRead the * rows. Cycles 0 to 3 each deliver two samples and emit one beat, with the second held; cycles 4 to 7 emit the backlog with no new arrivals. The normaliser's single held slot is sufficient because the consumer keeps up — and §11 shows what happens when it does not.
Note burst_first on cycle 0 only. Every later beat carries beat_first low, and 19.2 §6's assembler uses that one bit to anchor the word. A framing bit that arrived a cycle late would shift the entire word, which §17 lists as a distinct symptom from everything else in this chapter.
11. Failure — The Buffer Overflows
Same burst, DEPTH = 6, STALL_BOUND = 2, and the consumer stops accepting from cycle 1.
cyc in_vld out_ready do_write occ stall_run bound_err overflow
─── ────── ───────── ──────── ─── ───────── ───────── ────────
0 1 1 1 1 0 0 0
1 1 0 1 2 1 0 0
2 1 0 1 3 2 0 0
3 1 0 1 4 3 1 <-- 0
4 1 0 1 5 4 1 0
5 1 0 1 6 5 1 0
6 1 0 0 6 6 1 1 <--
7 1 0 0 6 7 1 1Two markers, and the gap between them is the design margin.
Cycle 3 — the stated assumption is violated. err_stall_bound_exceeded asserts because the consumer has stalled for three cycles and the depth was derived for two. No data has been lost yet. This is the warning §9 exists to give, and it names the cause rather than the symptom.
Cycle 6 — data is lost. The buffer is full, a captured beat arrives, and do_write is low. That beat is gone: the device will not resend it, and no downstream mechanism can ask.
12. Failure — Gate Correct, Capture Wrong
The trace that separates this module from Module 19, and the one with no digital signature at all.
CONCEPTUAL — NOT TO SCALE. Sub-interval positions are sketched; a
cycle grid cannot represent them and §1 stage A is not modelled.
the gate: opened before the strobe, closed after. CORRECT.
the count: 8 edges admitted, 8 samples taken, 8 beats emitted. CORRECT.
the phase: sampled near the data's transition rather than its centre.
|<-------- one beat -------->|
DQ ---+============================+---
^ ^
| |
sampled here should be here
(transition) (centre)
result:
beats : 8 of 8 <- every digital observable is RIGHT
framing : correct
values : some bits wrong, varying run to run
err_* : none asserted anywhere in this chapter's RTLEvery signal this chapter's blocks produce is correct. beat_valid fired eight times. beat_first was right. Occupancy behaved. No error output asserted. The assembler formed a full word.
And the data is wrong. Because 20.2 §4 established that a read strobe arrives at the data's transitions, and if the shift of §1 stage A put the sampling instant near a transition rather than near the centre, the capture circuitry sampled while the data was changing.
13. Failure — Capture Correct, Assembly Wrong
The mirror image, and the one digital verification does catch.
Set RISE_IS_EVEN to the wrong value for the architecture — the samples are perfect, the phase is ideal, the gate is right, and the interleave is backwards.
capture produces normaliser emits assembler forms
rise: D0 D2 D4 D6 D1 D0 D3 D2 D5 D4 D7 D6
fall: D1 D3 D5 D7 |
v
every ADJACENT PAIR swapped
observables:
beat count : 8 of 8 correct
framing : correct
values : wrong, and IDENTICALLY wrong every burst
err_* : none assertedDeterministic pairwise permutation. That signature is diagnostic: it is not corruption, it is a reordering, and the same input produces the same wrong output every time.
The discriminator against §12 is repeatability. A phase problem produces varying errors, because whether a marginal sample resolves correctly depends on noise and conditions. A swapped interleave produces the same error, always. One observation — run the same burst twice and compare — separates a Module 20 digital bug from a Module 20 physical one, and it costs nothing.
And unlike §12, this one is catchable in simulation: §15's DV compares against an independently-ordered expected sequence, and a swapped interleave fails on beat 0.
14. What the Assertions Prove
// ═══ NORMALISER ══════════════════════════════════════════════════════
// ── N1. A beat is emitted only for a sample that arrived. Catches a
// normaliser that manufactures a beat from an empty cycle, which
// would lengthen every burst.
property p_beat_requires_sample;
@(posedge clk) disable iff (!rst_n)
beat_valid |-> (held || rise_valid || fall_valid);
endproperty
a_beat_requires_sample: assert property (p_beat_requires_sample);
// ── N2. The held slot is released only by a transfer. The ownership
// property: a sample must not be discarded because a new one
// arrived, because a discarded captured beat is unrecoverable.
property p_held_released_on_transfer_only;
@(posedge clk) disable iff (!rst_n)
($fell(held) && !$past(err_both_pending_dropped, 1))
|-> $past(beat_valid && beat_ready, 1);
endproperty
a_held_released_on_transfer_only: assert property (p_held_released_on_transfer_only);
// ── N3. Burst framing appears exactly once per burst, on the first
// emitted beat. Catches a beat_first that leaks onto later beats,
// which makes 19.2's assembler restart mid-word.
property p_first_only_on_first;
@(posedge clk) disable iff (!rst_n)
(beat_valid && beat_first) |-> (burst_first || held_first);
endproperty
a_first_only_on_first: assert property (p_first_only_on_first);
// ── N4. Polarity alternates within a cycle carrying both samples: the
// leading beat is even and the trailing one is not. This is §13's
// failure expressed as a contract -- it holds for EITHER value of
// RISE_IS_EVEN, which is exactly why the parameter is safe and a
// hardcoded interleave is not.
property p_polarity_alternates;
@(posedge clk) disable iff (!rst_n)
(rise_valid && fall_valid && !held && beat_valid)
|-> beat_is_even;
endproperty
a_polarity_alternates: assert property (p_polarity_alternates);
// ═══ BUFFER ══════════════════════════════════════════════════════════
// ── B1. Occupancy never exceeds DEPTH and never underflows. The
// count-versus-index property: CNT_W is $clog2(DEPTH+1) precisely
// so that a full buffer is representable, and this fails loudly if
// it is ever sized as a pointer instead.
property p_occupancy_bounded;
@(posedge clk) disable iff (!rst_n)
occupancy <= CNT_W'(DEPTH);
endproperty
a_occupancy_bounded: assert property (p_occupancy_bounded);
// ── B2. A beat offered with room available is ALWAYS written. There
// is no backpressure toward capture (§7), so a beat that is not
// written when it could have been is silently lost.
property p_no_silent_drop;
@(posedge clk) disable iff (!rst_n)
(in_valid && (!full || do_read)) |-> do_write;
endproperty
a_no_silent_drop: assert property (p_no_silent_drop);
// ── B3. Overflow is sticky. A lost captured beat is unrecoverable, so
// the indication must survive until reset (§11).
property p_overflow_sticky;
@(posedge clk) disable iff (!rst_n)
err_overflow |=> err_overflow;
endproperty
a_overflow_sticky: assert property (p_overflow_sticky);
// ── B4. Under the STATED stall bound, overflow never occurs. This is
// §8's derivation as a property, and it is the one that makes the
// depth defensible rather than arbitrary. Its antecedent is the
// assumption, written down where it can be checked.
property p_no_overflow_within_bound;
@(posedge clk) disable iff (!rst_n)
(!err_stall_bound_exceeded) |-> !$rose(err_overflow);
endproperty
a_no_overflow_within_bound: assert property (p_no_overflow_within_bound);
// ── B5. Output is stable while the consumer stalls.
property p_output_stable_while_stalled;
@(posedge clk) disable iff (!rst_n)
(out_valid && !out_ready)
|=> (out_valid && $stable(out_data) && $stable(out_is_even)
&& $stable(out_first));
endproperty
a_output_stable_while_stalled: assert property (p_output_stable_while_stalled);
// ═══ COVERS ══════════════════════════════════════════════════════════
c_both_samples: cover property (@(posedge clk) disable iff (!rst_n)
rise_valid && fall_valid);
c_held_emitted: cover property (@(posedge clk) disable iff (!rst_n)
held && beat_valid && beat_ready);
c_buffer_full: cover property (@(posedge clk) disable iff (!rst_n) full);
c_simultaneous_rw: cover property (@(posedge clk) disable iff (!rst_n)
do_write && do_read);
c_bound_exceeded: cover property (@(posedge clk) disable iff (!rst_n)
$rose(err_stall_bound_exceeded));
c_overflow_reached: cover property (@(posedge clk) disable iff (!rst_n)
$rose(err_overflow));What they prove. That the digital receive path preserves every captured beat it is given room for, orders the two polarities consistently, frames bursts once, bounds its occupancy, and never loses data while the stated stall assumption holds.
What they cannot prove — and this is the chapter's conclusion, not a caveat. Nothing above is sensitive to whether a sample was taken at a good instant. §12's failure satisfies every one of these properties, because the values are not part of any contract they express. There is no variable in this RTL for margin, phase, aperture or skew, so no property over this RTL can constrain them.
Concretely: B1 through B5 establish that the buffer is a correct buffer. They say nothing about whether the bits inside it are the bits the device sent. A digital assertion cannot prove an eye is open, and the reason is not that nobody has written the right property — it is that the quantity is absent from the model by construction (§1).
Vacuity. N2's antecedent needs a falling held with no drop, which a test that never fills the slot never produces — c_held_emitted rules that out. B4's antecedent is the absence of a bound violation, so it passes trivially on a test that always violates the bound and equally on one that never stalls; c_bound_exceeded and c_overflow_reached together establish that both regimes were visited. Without those two covers, B4 is the most reassuring and least informative property in the chapter.
15. DV — Independent Ordering and an Independent Count
Two checkers, because the chapter has two failure classes it can catch.
Ordering. Build the expected beat sequence from the stimulus, not from the DUT. The testbench knows which samples it injected on which polarity; interleave them in an independent function — a queue built by appending from two source lists — and compare against the observed beat_data stream. §13's swapped interleave fails on the first beat.
Accounting. Count captured beats offered and beats emerged, independently of occupancy. Every beat offered with room must appear exactly once, in order. At end of test the counts must reconcile, and any difference must be explained by an err_overflow assertion — an unexplained difference is silent data loss, which is the worst outcome in the chapter.
CAPTURE ORDERING MISMATCH
lane : 1
RISE_IS_EVEN : 1 (as configured)
injected rise stream : D0 D2 D4 D6
injected fall stream : D1 D3 D5 D7
expected beat order : D0 D1 D2 D3 D4 D5 D6 D7
observed beat order : D1 D0 D3 D2 D5 D4 D7 D6
first mismatch : beat 0 (expected D0, observed D1)
pattern : every adjacent pair transposed
beat count : 8 of 8 -- CORRECT
framing : correct
err_* asserted : none
diagnosis : the interleave convention is inverted. Either
RISE_IS_EVEN does not match the architecture, or the
capture side is labelling polarity the other way.
discriminator : run the identical burst twice. A transposition
reproduces EXACTLY; a sampling-phase problem (§12)
does not. One repeat separates a digital bug from a
physical one.
NOT indicated : anything about sample quality. Both streams could
be sampled at the worst possible instant and this
report would be unchanged.Directed cases worth running: rise only; fall only; both; neither; both with the consumer stalled, which must set err_both_pending_dropped; DEPTH = 1; a stall exactly at STALL_BOUND and one cycle beyond; back-to-back bursts with burst_first on the first beat of each; a reset mid-burst; and simultaneous write and read at both full and empty.
16. Corner Cases
Normaliser.
| Situation | Correct behaviour | Failure if mishandled |
|---|---|---|
| rise only | one beat, marked even | a fabricated second beat |
| fall only | one beat, marked odd | polarity mislabelled |
| both, consumer ready | leading emitted, trailing held | the trailing sample lost |
| both, consumer stalled | err_both_pending_dropped | silent loss of a captured beat |
| neither | beat_valid low | a beat emitted from nothing (N1) |
burst_first with both samples | framing on the leading beat only | the assembler restarts mid-word |
LANE_W = 1 | legal | zero-width sample |
| reset mid-burst | slot cleared, framing lost | a stale sample joining the next burst |
| sample outside a framed burst | err_sample_without_frame | a gate fault (19.4) misread as a capture fault |
Buffer.
| Situation | Correct behaviour | Failure if mishandled |
|---|---|---|
DEPTH = 1 | PTR_W guarded to 1; single slot works | zero-width pointer |
occupancy reaches DEPTH | full; representable | CNT_W as a pointer width truncates full to empty |
| full, write and read same cycle | write succeeds, net zero | a beat dropped at exactly the boundary |
| empty, read asserted | nothing happens | underflow to DEPTH |
| full, write offered, no read | data lost, err_overflow | silent loss |
stall exactly at STALL_BOUND | no bound error | an off-by-one warning |
| stall one beyond | err_stall_bound_exceeded, no loss yet | the warning arrives with the damage |
stall_run saturates | holds at maximum | wraps below the bound and looks healthy |
| reset while full | pointers and occupancy cleared | stale beats delivered after reset |
The “full, write and read same cycle” row is the one most often coded wrong: do_write deliberately includes || do_read, so a beat arriving into a full buffer that is being drained this cycle is accepted. Omitting that term loses one beat per burst at exactly the worst moment, and B2 is the property that catches it.
17. Debugging
Symptom: beats are missing from the start of every burst. Not this chapter. Chapter 19.4 §12 — a late gate. The discriminator is that the count is short and the displacement is identical every time. Check the count before anything else, because a short count sends you to Module 19 and a full count sends you here.
Symptom: the count is right and bits are wrong, varying run to run. §12. Sampling phase. Nothing in this chapter's RTL will show it, and no assertion will fire. Go to Module 21 for whether the setting was established and 20.5 for how much margin the setting had.
Symptom: the count is right and bits are wrong, identically every time. §13. A digital permutation — interleave or lane mapping. Run the burst twice and compare; reproducibility is the discriminator. Then check RISE_IS_EVEN against the architecture and 20.2 §5's binding.
Symptom: corruption only when the controller is busy. §11. Check err_overflow and err_stall_bound_exceeded. The load correlation is the tell — a phase problem does not care how busy the consumer is. This one is almost always misdiagnosed as a capture-phase problem first.
Symptom: one lane wrong, others fine. Per-lane state or per-lane binding. Chapter 20.2 §9 owns that procedure, and it starts with the binding check because it costs one signal read.
Symptom: every word is shifted by one beat, consistently. Framing, not capture. Chapter 19.2 §14 — a burst_first arriving a cycle early or late, or a local counter used instead of it. N3 is the property.
Symptom: err_sample_without_frame asserts. A sample arrived outside any burst the receive path knows about, which means 19.4's gate admitted something. Not a capture problem; a gate problem presenting inside the capture path.
18. Misconceptions
“If the gate is correct, capture is correct.” §2 and §12 — different questions, different granularity, different failure signatures. Clue: a debug effort that stops once gate containment is verified.
“If the beat count is correct, the data is correct.” §12's whole point. Consequence: sampling-phase problems investigated as framing problems. Clue: a full beat count with varying bit errors.
“Sampling on a DQS edge gives good margin.” On a read the edge is the transition (20.2 §4), so the raw edge is the worst instant available. Clue: a receive path with no shift.
“always_ff @(posedge dqs) is a DDR receiver.” Chapter 19.1 §1. This chapter adds the specific reason: even with a perfect gate and perfect ordering, the instant is wrong. Clue: RTL that samples a strobe directly.
“A downstream ready can stall the burst.” §7 — the device is executing a schedule and there is no pause mechanism. Clue: a ready output on the arrival side of a receive buffer.
“Overflow can be solved by deasserting ready toward the DRAM.” The same error stated as a fix. The three real options are in §7's callout. Clue: a design with no derived buffer depth.
“A receive buffer depth of 4 is fine.” Fine under what stall bound? §8 — the depth without the assumption is a hidden contract. Clue: a depth with no derivation and no stated S_max.
“Every read bit error is a signal-integrity problem.” §11, §12 and §13 are three different causes with three different signatures, and only one of them is physical at all. Clue: an SI investigation opened before the beat count and the load correlation were checked.
“RTL simulation proves the sampling point is good.” §14 — the quantity is absent from the model. Clue: PHY sign-off from simulation.
“Module 20 should find its own sampling phase.” It consumes one. Module 21 discovers it, and mixing the two produces a chapter that teaches neither. Clue: a sweep loop inside a capture block.
19. Interview Reasoning
“What happens inside a DDR read capture?” Three stages — shift, sample, normalise-and-absorb — and only the third is portable RTL. The strong answer names which two are technology-specific and why.
“Can the gate be correct while capture is wrong?” Yes, and the signatures differ: displacement versus corruption. Then the diagnostic order: count the beats first.
“Why can't the PHY backpressure an arriving read burst?” The device is executing a schedule set CL cycles earlier and has no pause input. Then the consequence: elasticity must exist inside, and its depth is a derived quantity with a stated assumption.
“How deep should the receive buffer be?” B/2 + S_max for this rate model — and the answer is incomplete without naming S_max, because that is a contract on the consumer.
“Beats are in pairs that are swapped. Where do you look?” The edge interleave. Then the discriminator: reproducibility. A transposition repeats exactly; a phase problem does not.
“What can SVA prove about a receive path?” Ordering, framing, occupancy bounds, ownership, no silent drop. Not sample quality, margin, or aperture — those are absent from the model, not merely unproven.
“Why does Module 20 consume a trained phase rather than find one?” Because finding it requires measuring real margin on real hardware, which is a different activity with different tooling. Keeping them separate is what makes each tractable.
20. Exercises
1. With B = 16 and a required tolerance of a 3-cycle consumer stall, derive the minimum DEPTH from §8 and show the accounting.
2. §10's trace has * on cycles 1, 3, 5 and 7. Explain what those beats have in common and why they cannot be emitted in the same cycle as their partners.
3. CNT_W is $clog2(DEPTH + 1). For DEPTH = 8, what does occupancy read at full if it were sized $clog2(DEPTH) instead, and which property fires?
4. Remove || do_read from do_write. Construct the cycle where a beat is lost, and say how many beats per burst are lost if the consumer accepts one per cycle throughout.
5. Set RISE_IS_EVEN incorrectly. Give the observed beat order for injected rise A C E G and fall B D F H, and state which DV check fails first.
6. §12's failure asserts no error and satisfies every property. List the three observables that are correct and name the one thing that is not, then say what would detect it.
7. A colleague adds a capture_ready output to capture_rx_buffer and wires it toward the PHY's input stage. Describe what happens to an in-flight burst and why the signal cannot help.
8. Distinguish, by evidence alone, between §11, §12 and §13 given only: beat count, error flags, reproducibility across two identical bursts, and correlation with controller load.
21. Where This Goes
The box is open. Capture is a shift, a sample, and a digital path that orders and absorbs — and this chapter built the third while being explicit that the first two are not RTL.
Two questions are left, and they are the last two chapters.
Chapter 20.4 turns to the write direction, where the alignment problem is not against the data at all. 20.2 §4 established that the controller delivers a write strobe already centred in its data — so on a write the hard question is a different one: the device also requires the strobe to arrive in a defined relationship to CK, and on a modern module those two requirements are not satisfied by the same delay.
20.5 supplies what §12 deliberately withheld: the published figures. How wide the usable window actually is, what the device guarantees about it, and how much of a bit interval is left once the residual skew of 20.1 §3 is subtracted — which is what turns “sample near the centre” from advice into a budget.
Continue learning
Related tutorials
- Related topic
DDR (DDR1)
Double data rate doubles transfer opportunities per clock cycle, not the clock. Two mechanisms make that survivable: a 2n prefetch so a slow array can feed a fast interface, and a source-synchronous DQS strobe so data carries its own timing.
- Related topic
DQS — The Data Strobe
DQS carries the data's own timing reference along the same path as the data. It is directional, it is not free-running, and the strobe window is wider than the data window — three facts that make it nothing like CK.
- Related topic
Data Return
During a read the DRAM drives the bus and supplies the timing reference. Controller RTL never sees that — it receives already-captured beats from a boundary it must not cross.
- Related topic
DQS Handling
On a write the PHY generates the strobe. On a read it must decide when to listen, because the strobe is not driven between bursts — so the gate is an estimate of a window the PHY cannot see.
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.
