DDR · Module 10
Read Timing Analysis
A read crosses three timing domains, and the common analysis errors are attribution errors — measuring from the wrong event, comparing cycles against transfers, or blaming the layer that reported the fault.
Four chapters have built the pieces separately. A transaction is created at acceptance (10.1), its return is predicted (10.2), beats arrive through a boundary (10.3), and the burst is counted and validated (10.4).
This chapter puts them together and asks the question none of them can answer alone:
Given a trace, how do you reason from a read command to its returned data without confusing the timing domains it crosses?
The common errors here are not arithmetic. They are errors of attribution: measuring from the wrong event, comparing a cycle count against a transfer count, or assigning a fault to the layer that reported it rather than the one that caused it. Each produces a confident, wrong conclusion, and each is avoidable by method rather than by cleverness.
1. Three Domains, and Why That Is the Hard Part
A read crosses three timing domains, and almost every analysis error is a measurement taken in one and compared against another.
| Domain | Timed by | Counts in | Owns |
|---|---|---|---|
| Command | CK, at the command sampling event | clock cycles | when the read was issued |
| Return | DQS, sourced by the device | transfers | when data crossed the channel |
| Controller | the controller's own clock | clock cycles | when beats were consumed |
Three consequences, and each is a real mistake people make.
Cycles and transfers are not the same unit. Chapter 10.2 §3 established the factor of two and Chapter 10.4 §6 applied it to bursts. A latency in cycles added to a burst in beats is wrong by a factor of two on the second term, and the result looks plausible.
The return domain's reference is not the controller's clock. Chapter 10.3 §2 derived why: the strobe came back with the data, through a path the clock never took. So "when did the data arrive" has two different answers — when it crossed the channel, and when the controller saw it — separated by whatever the PHY contributes.
And the boundary between the return domain and the controller domain is where the PHY's own latency lives. Module 19 owns it. A controller's total command-to-beat offset is not the device's read latency; it is that plus the PHY's contribution, and confusing the two is Chapter 10.2 §9's mechanism 3.
2. The Methodology
This is the procedure for analysing a real read trace. The first three steps involve no waveform at all, and skipping them is why analyses go wrong before they start.
1 — Establish the generation. Burst length, latency terminology and the return's structure all depend on it. Chapter 10.4 §2 showed beat counts differing between DDR4 and DDR5; the same trace means different things under each.
2 — Establish the operating point. The marketed rate, and from it the clock period by Chapter 10.2 §3's derivation — MT/s ÷ 2 = MHz. Without this no cycle count converts to time.
3 — Establish the configuration. The mode-register settings actually in force: the latency values, any additive latency, and the burst configuration. Read them back where the generation permits rather than assuming what was programmed. Chapter 7.6 established that configuration is a correctness concern, and this is where that matters most.
4 — Identify the command sampling event. Not when the controller decided to issue, and not when a signal began to change. Chapter 6.1 and Chapter 7.1 §2 define it. This is the origin of every measurement that follows, and measuring from the wrong one shifts everything uniformly — which makes it look like a latency error.
5 — Determine the applicable read latency. The total command-to-first-data offset the device promises, which includes any additive component — not CL alone. Chapter 10.2 §2.
6 — Locate the expected return window. From steps 4 and 5, and note it is a window rather than an instant: the strobe has a preamble before data is valid (Chapter 6.10), and strobe-active is not data-valid.
7 — Determine the burst duration. In transfers, then converted to clock periods if you intend to compare it against anything in cycles. Chapter 10.4 §6.
8 — Compare the observation against the expectation. Both the first beat's position and the beat count. Record the measured offset even when it matches — Chapter 10.2 §8's argument for distributions over checks.
9 — Separate controller timing from capture timing. The offset the controller observes includes the PHY's contribution. A discrepancy that moves with temperature or after retraining is below the boundary; one that is fixed and reproducible is above it. That single test routes most read-timing faults to the right team.
3. What Integration Adds
Each earlier block knows one thing and is deliberately blind to the others. Putting them together produces a check none of them can perform.
Chapter 10.2's pipeline predicts and cannot observe. It will happily predict returns for a device that has stopped responding.
Chapter 10.3's boundary observes and cannot predict. It knows a beat arrived; it has no opinion about whether that was the right moment.
Chapter 10.4's collector counts and cannot time. A burst arriving at entirely the wrong moment with the right beats passes every one of its checks.
Together they can compare a prediction against an observation — which is the only way to detect a mispredicted latency, and the reason this chapter exists as a chapter rather than as a summary.
predicted first beat (10.2, from configuration)
observed first beat (10.3, from the boundary)
───────────────────────────
difference → early, on time, or late
and by how muchThat difference is the single most useful number in read debugging, and no earlier block could produce it.
4. RTL — The End-to-End Transaction Tracker
The engineering problem
Hold one read transaction from acceptance to completion, and measure the observed arrival of its first beat against the expected one — reporting early, late and overdue as distinct conditions.
Why hardware needs it
A controller that only knows whether data arrived cannot tell a correct system from one that is one cycle from failing. Measuring the offset is what turns a working design into a characterised one.
Classification
SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL. Layer C.
What it models
A single read transaction's lifetime; its age in cycles since acceptance; comparison of the observed first-beat arrival against the expected offset; beat progress; completion; and four reported anomalies.
What it does NOT model
Capture (Modules 19 to 21). Data correctness — it stores no data. Command-to-command timing legality (Modules 13, 14). Scheduling or multiple outstanding reads (Module 17). The device — EXPECT_LATENCY is a belief, and this block exists precisely to detect when the belief is wrong.
Interface and parameter contract
// ─────────────────────────────────────────────────────────────────────────
// read_txn_tracker
//
// Classification: SYNTHESIZABLE EDUCATIONAL CONTROLLER RTL.
//
// MODELS: one read transaction's lifetime, its age since acceptance, the
// OBSERVED first-beat arrival compared against the EXPECTED offset, beat
// progress, completion, and four reported anomalies.
//
// EXPECT_LATENCY IS AN EDUCATIONAL PIPELINE DEPTH, NOT A JEDEC VALUE. A
// real controller derives it from the configured read latency PLUS the
// PHY's contribution (Module 19) -- the observed offset and the device's
// read latency are different quantities.
//
// OVERDUE_CYCLES IS AN EDUCATIONAL THRESHOLD, NOT A PROTOCOL TIMEOUT. DDR
// has no read timeout: a device that does not respond reports nothing. The
// threshold turns a non-response into a reported event instead of a hang.
//
// ONE OUTSTANDING READ (Chapter 10.1). Not a scheduler, not a DRAM model.
//
// MODELS NO PHYSICAL OR ANALOG BEHAVIOUR.
// ─────────────────────────────────────────────────────────────────────────
module read_txn_tracker #(
parameter int TAG_W = 3,
parameter int BEATS_PER_READ = 8,
// EDUCATIONAL. See the header.
parameter int EXPECT_LATENCY = 6,
parameter int OVERDUE_CYCLES = 32,
parameter int CNT_W = (BEATS_PER_READ <= 1) ? 1 : $clog2(BEATS_PER_READ + 1),
// Wide enough to reach the threshold and stop there.
parameter int AGE_W = (OVERDUE_CYCLES <= 1) ? 1 : $clog2(OVERDUE_CYCLES + 1)
) (
input logic clk,
input logic rst_n,
// ── From Chapter 10.1's admission.
input logic accept,
input logic [TAG_W-1:0] accept_tag,
// ── From Chapter 10.3's boundary.
input logic beat_valid,
input logic beat_first,
input logic beat_last,
output logic txn_active,
output logic [TAG_W-1:0] txn_tag,
// Cycles since acceptance. SATURATES at OVERDUE_CYCLES: a wrapped age
// would make a badly overdue transaction look freshly issued, which is
// the same argument as Chapter 9.6's telemetry.
output logic [AGE_W-1:0] txn_age,
output logic [CNT_W-1:0] beats_seen,
output logic txn_complete,
output logic [TAG_W-1:0] complete_tag,
// ── THE INTEGRATION. Observed first-beat arrival against expected.
output logic first_beat_early,
output logic first_beat_late,
// Measured offset of the first beat, valid when it arrives.
output logic [AGE_W-1:0] measured_offset,
output logic measured_valid,
// ── Anomalies.
output logic txn_overdue,
output logic phantom_beat
);
if (TAG_W < 1) begin : g_tw
initial $fatal(1, "read_txn_tracker: TAG_W must be >= 1");
end
if (BEATS_PER_READ < 1) begin : g_bp
initial $fatal(1, "read_txn_tracker: BEATS_PER_READ must be >= 1");
end
if (EXPECT_LATENCY < 0) begin : g_el
initial $fatal(1, "read_txn_tracker: EXPECT_LATENCY must be >= 0");
end
// The threshold must exceed the expectation, or every transaction is
// overdue before its data was ever due.
if (OVERDUE_CYCLES <= EXPECT_LATENCY) begin : g_ov
initial $fatal(1, "read_txn_tracker: OVERDUE_CYCLES must exceed EXPECT_LATENCY");
end
logic active_q;
logic [TAG_W-1:0] tag_q;
logic [AGE_W-1:0] age_q;
logic [CNT_W-1:0] beats_q;
logic seen_first_q;
// ── A beat with no transaction outstanding. Chapter 10.3 reports this
// at the boundary; it is reported again here because the two blocks
// have different notions of "outstanding" and a disagreement between
// them is itself informative.
assign phantom_beat = beat_valid && !active_q;
// ── The comparison this chapter exists for. Evaluated only on the
// first beat of an active transaction.
logic first_beat_now;
assign first_beat_now = beat_valid && active_q && !seen_first_q;
assign measured_valid = first_beat_now;
assign measured_offset = age_q;
assign first_beat_early = first_beat_now && (age_q < AGE_W'(EXPECT_LATENCY));
assign first_beat_late = first_beat_now && (age_q > AGE_W'(EXPECT_LATENCY));
// Overdue: still waiting for the first beat, and the age has reached the
// threshold. Deliberately NOT "not complete" -- a burst in progress is
// not overdue, and conflating them would report a slow burst as a
// non-response.
assign txn_overdue = active_q && !seen_first_q
&& (age_q >= AGE_W'(OVERDUE_CYCLES));
// ── Completion: the configured beat count reached with the last flag.
// Chapter 10.4's contract, applied here to retire the transaction.
logic at_last_beat;
assign at_last_beat = (beats_q + CNT_W'(1)) == CNT_W'(BEATS_PER_READ);
assign txn_complete = beat_valid && active_q && beat_last && at_last_beat;
assign complete_tag = tag_q;
assign txn_active = active_q;
assign txn_tag = tag_q;
assign txn_age = age_q;
assign beats_seen = beats_q;
always_ff @(posedge clk) begin
if (!rst_n) begin
active_q <= 1'b0;
tag_q <= '0;
age_q <= '0;
beats_q <= '0;
seen_first_q <= 1'b0;
end else if (accept) begin
active_q <= 1'b1;
tag_q <= accept_tag;
age_q <= '0;
beats_q <= '0;
seen_first_q <= 1'b0;
end else if (active_q) begin
// Saturating age. See the port comment.
if (age_q < AGE_W'(OVERDUE_CYCLES))
age_q <= age_q + AGE_W'(1);
if (beat_valid) begin
seen_first_q <= 1'b1;
if (txn_complete) begin
active_q <= 1'b0;
beats_q <= '0;
seen_first_q <= 1'b0;
end else if (beats_q < CNT_W'(BEATS_PER_READ)) begin
beats_q <= beats_q + CNT_W'(1);
end
end
end
end
endmoduleState representation and transitions
An active flag, a tag, a saturating age, a beat count, and a first-beat-seen flag. The last is what separates waiting for data from receiving data, and it is why txn_overdue does not fire during a slow burst.
Sequential behaviour and reset
Nonblocking throughout. The age saturates rather than wrapping — Chapter 9.6 §5's argument, in a new place: a wrapped age makes a badly overdue transaction look freshly issued, which is failure in the flattering direction.
Reset abandons an in-flight transaction, and the remaining beats then appear as phantom_beat — matching Chapter 10.3's and Chapter 10.4's behaviour from the same cause. Three blocks producing a consistent signature for one event is what makes that event recognisable in a trace.
Cycle-by-cycle trace
EXPECT_LATENCY = 6, BEATS_PER_READ = 4, first beat arriving one cycle late:
| Cycle | Event | age | seen_first | measured_offset | Flag |
|---|---|---|---|---|---|
| 0 | accept, tag 3 | 0 | 0 | — | — |
| 1–6 | waiting | 1…6 | 0 | — | — |
| 7 | first beat | 7 | 1 | 7 | first_beat_late |
| 8–9 | beats 2, 3 | 8, 9 | 1 | — | — |
| 10 | beat 4 + last | 10 | — | — | txn_complete |
Cycle 7 is the whole point of the block. The transaction completed correctly, the data was fine, the beat count was right — and the first beat arrived at offset 7 where 6 was expected. Nothing else in this module would have noticed.
How to simulate, and expected output
Drive a nominal read and confirm measured_offset == EXPECT_LATENCY with neither flag. Then:
First beat early and late by one — confirm the correct flag and the measured value, and confirm the transaction still completes normally. A latency discrepancy is not a completion failure, and conflating them loses the diagnosis.
No beats at all — confirm txn_overdue asserts once the age reaches the threshold and that the age then stops. Confirm it does not assert during a slow burst, which is the seen_first_q guard.
Reset mid-burst — confirm abandonment and subsequent phantom_beat.
BEATS_PER_READ = 1 — first and last on one beat, completing immediately with a valid measurement.
OVERDUE_CYCLES <= EXPECT_LATENCY must not elaborate.
Synthesis implications
An age counter, a beat counter, a tag, two flags and a few comparators — under 20 flops at the defaults. Measuring the offset costs a counter, which is a small price for the difference between "it works" and "it works with two cycles of margin."
Corner cases
EXPECT_LATENCY == 0 is legal — a beat in the acceptance cycle would then be on time, though accept takes priority in the same cycle, so the earliest measurable offset is 1. OVERDUE_CYCLES must exceed EXPECT_LATENCY or every transaction is overdue before its data was due; enforced at elaboration. AGE_W is sized from the threshold and saturates there, so the age is not a general-purpose timer — it answers one question.
Failure modes and debugging clues
first_beat_late on every read by a constant amount is a configuration problem, not a device problem — Chapter 10.2 §9. txn_overdue with bus activity present means beats are not crossing the boundary, which is Chapter 10.3 §9. phantom_beat and Chapter 10.3's unexpected_beat disagreeing means the two blocks' notions of outstanding have diverged — which is a wiring or retirement bug and is worth checking explicitly, because each alone looks correct.
Limitations
One outstanding read. No data. The measured offset is in the controller's domain and therefore includes the PHY's contribution — it is not the device's read latency, and §1 explains why that distinction matters.
5. RTL — The Verification-Only Return Checker
The engineering problem
Watch the same transaction independently and report what a design cannot report about itself: the distribution of observed offsets, and returns that match no command.
Classification
VERIFICATION-ONLY EDUCATIONAL MODEL. Not intended for synthesis.
Interface and parameter contract
// ─────────────────────────────────────────────────────────────────────────
// read_return_checker
//
// Classification: VERIFICATION-ONLY EDUCATIONAL MODEL.
// Not intended for synthesis. It drives nothing and exists to observe.
//
// MODELS: independent measurement of command-to-first-beat offsets, their
// observed range, and returns matching no command.
//
// TAKES NO EXPECTED LATENCY, DELIBERATELY. A checker configured with the
// same constant as the controller is wrong together with it and reports a
// clean check (Chapter 10.2 Section 8). This MEASURES and reports the
// range; the judgement belongs to whoever knows the configuration.
//
// Counters SATURATE, never wrap -- a wrapped count reports a flattering
// number, as Chapter 9.6 argued.
//
// MODELS NO PHYSICAL OR ANALOG BEHAVIOUR.
// ─────────────────────────────────────────────────────────────────────────
module read_return_checker #(
parameter int OFF_W = 8,
parameter int ACC_W = 16
) (
input logic clk,
input logic rst_n,
// Observed events only. No configuration, no expectation.
input logic obs_read_cmd,
input logic obs_first_beat,
output logic [OFF_W-1:0] offset_min,
output logic [OFF_W-1:0] offset_max,
output logic range_valid,
output logic [ACC_W-1:0] cnt_reads,
output logic [ACC_W-1:0] cnt_returns,
// A first beat with no command awaiting one.
output logic [ACC_W-1:0] cnt_orphan_returns,
output logic any_saturated
);
if (OFF_W < 1) begin : g_ow
initial $fatal(1, "read_return_checker: OFF_W must be >= 1");
end
if (ACC_W < 2) begin : g_aw
initial $fatal(1, "read_return_checker: ACC_W must be >= 2");
end
localparam logic [OFF_W-1:0] OFF_MAX = {OFF_W{1'b1}};
localparam logic [ACC_W-1:0] ACC_MAX = {ACC_W{1'b1}};
logic waiting_q;
logic [OFF_W-1:0] age_q;
always_ff @(posedge clk) begin
if (!rst_n) begin
waiting_q <= 1'b0;
age_q <= '0;
offset_min <= OFF_MAX; // so the first sample wins
offset_max <= '0;
range_valid <= 1'b0;
cnt_reads <= '0;
cnt_returns <= '0;
cnt_orphan_returns <= '0;
any_saturated <= 1'b0;
end else begin
if (obs_read_cmd) begin
waiting_q <= 1'b1;
age_q <= '0;
if (cnt_reads == ACC_MAX) any_saturated <= 1'b1;
else cnt_reads <= cnt_reads + ACC_W'(1);
end else if (waiting_q) begin
// Saturate rather than wrap: an offset that wrapped would report
// a small, flattering number for a very late return.
if (age_q == OFF_MAX) any_saturated <= 1'b1;
else age_q <= age_q + OFF_W'(1);
end
if (obs_first_beat) begin
if (waiting_q) begin
waiting_q <= 1'b0;
if (cnt_returns == ACC_MAX) any_saturated <= 1'b1;
else cnt_returns <= cnt_returns + ACC_W'(1);
// The measurement. Reported as a RANGE, not checked.
if (!range_valid || (age_q < offset_min)) offset_min <= age_q;
if (!range_valid || (age_q > offset_max)) offset_max <= age_q;
range_valid <= 1'b1;
end else begin
// A return nobody asked for.
if (cnt_orphan_returns == ACC_MAX) any_saturated <= 1'b1;
else cnt_orphan_returns <= cnt_orphan_returns + ACC_W'(1);
end
end
end
end
endmoduleWhat it teaches
A spread between offset_min and offset_max is the finding. A stable configuration should produce a single value; a range means the offset is varying, which is either a configuration change mid-run or something genuinely unstable below the boundary.
cnt_reads minus cnt_returns is the number of reads that never returned — the check Chapter 10.4 §8 asked for, computed with two counters.
And cnt_orphan_returns is the phantom count, measured independently of the design's own notion of outstanding.
Limitations
One outstanding read assumed — waiting_q is a single flag. It measures the controller-domain offset, which includes the PHY's contribution. It reports a range rather than a distribution; a real environment would histogram.
6. A Complete Read, in Cycles
read_txn_tracker — prediction and observation compared
10 cyclesRead the trace as three separate verdicts, because that is the skill this chapter teaches.
Did the transfer happen? Yes — beats arrived, the bus changed hands, the boundary delivered them.
Was the burst well-formed? Yes — four beats, last on the fourth, completion at cycle 9 with the right tag.
Did it arrive when expected? No. The expectation was an offset of 4; the measurement is 5. Every other check in this module passes, and only the comparison between prediction and observation notices.
That is the integration. Chapter 10.2's pipeline would have predicted a return at cycle 4 and been satisfied. Chapter 10.3's boundary saw four good beats. Chapter 10.4's collector counted them correctly and completed. Only together do they produce the finding.
And note what the finding is not. It is not "the device is slow" — §2's step 9 has to run before anyone knows whether the extra cycle belongs to the device, the PHY, or an expectation that was wrong from the start.
EDUCATIONAL TRACE — NOT TO SCALE, NOT A JEDEC TIMING DIAGRAM. One beat per cycle is Chapter 10.4 §6's readability convention, and the latency of four corresponds to no device.
7. Five Assertions Worth Writing
// P1 -- no return without an outstanding transaction. The module's
// foundational safety property, checked here at the point where beats are
// finally attributed to a transaction rather than merely received.
property p_no_beat_without_transaction;
@(posedge clk) disable iff (!rst_n)
(beat_valid && !txn_active) |-> phantom_beat;
endproperty
assert property (p_no_beat_without_transaction);
// P2 -- completion requires the final beat AND the count, and retires
// exactly the transaction that was accepted. Chapter 10.4's contract plus
// identity, which is what stops a completion being reported for the wrong
// tag after a mid-burst disturbance.
property p_completion_is_earned;
@(posedge clk) disable iff (!rst_n)
txn_complete |-> beat_valid && beat_last && txn_active
&& (beats_seen == CNT_W'(BEATS_PER_READ - 1))
&& (complete_tag == txn_tag);
endproperty
assert property (p_completion_is_earned);
// P3 -- no duplicate completion. A transaction completes once; a second
// completion without an intervening acceptance would retire a record that
// is already gone and free a requester's resources twice.
property p_no_duplicate_completion;
@(posedge clk) disable iff (!rst_n)
// Weak `until`, deliberately: it does not require accept ever to occur,
// so this is a pure SAFETY property. `s_until` would additionally demand
// that another read is eventually issued, which is not a claim this
// block should make.
txn_complete |=> (!txn_complete until accept);
endproperty
assert property (p_no_duplicate_completion);
// P4 -- the measurement is taken once, on the first beat only. A measured
// offset republished on later beats would make every burst look like
// several returns at increasing offsets.
property p_measurement_is_once_per_transaction;
@(posedge clk) disable iff (!rst_n)
measured_valid |-> (beats_seen == '0) && txn_active;
endproperty
assert property (p_measurement_is_once_per_transaction);
// P5 -- overdue means waiting, not slow. A burst in progress is never
// overdue; conflating them would report a slow transfer as a
// non-response and send the investigation to the wrong layer entirely.
property p_overdue_means_no_first_beat;
@(posedge clk) disable iff (!rst_n)
txn_overdue |-> txn_active && (beats_seen == '0);
endproperty
assert property (p_overdue_means_no_first_beat);What these prove. P1 and P2 close the read path: nothing is delivered without a transaction, and nothing completes without earning it. P3 protects against double retirement. P4 is subtler and worth the space — a measurement republished per beat turns one late return into a fabricated pattern of increasingly late returns, which is a convincing and entirely false diagnosis. P5 keeps the overdue report meaningful.
What these do not prove. Nothing here proves the data is correct — no block in this module stores data, and correctness needs a reference model compared against a memory image. Nothing proves EXPECT_LATENCY is right: a tracker configured wrongly flags every read and satisfies every property, which is why §5's checker takes no expectation at all. Nothing proves JEDEC timing compliance — not read latency, and certainly not the command-to-command legality Modules 13 and 14 own. Nothing proves anything below the boundary. And P3's ordering assumption is this tracker's, not a universal rule — with multiple outstanding reads, completions may legitimately interleave in ways this property forbids.
8. DV — Separating the Checks
The module's verification argument, assembled. A read fails in four independent ways, and a single scoreboard collapses them.
| Check | Question | Component | Failure signature |
|---|---|---|---|
| Command | was the right read issued? | command monitor | wrong bank/column on the interface |
| Transfer | did data cross at all? | Chapter 10.3's boundary | no phy_rd_valid |
| Timing | did it arrive when expected? | §4's measurement | first_beat_late, or a spread in §5's range |
| Structure | right number of beats, right flags? | Chapter 10.4's collector | err_early_last, err_overrun |
| Data | were the values right? | scoreboard + reference memory | mismatch at a known location |
The five are genuinely independent, and every pair can fail separately. Correct data at the wrong time. Correct timing carrying the wrong values. A well-formed burst for a command nobody issued. A scoreboard that samples data inside a predicted window conflates timing with data, so a one-cycle latency error presents as total data loss and the actual comparison is never reached — Chapter 10.3 §8's point, and it is the single most common structural flaw in a read environment.
Four requirements for an environment that can localise:
Build expectations from accepted commands. Chapter 10.1 §8 — building from returns checks the return against itself.
Sample on the boundary's own valid, never on a predicted window. Then wrong-time data is observed rather than missed, and timing becomes a comparison between two observations.
Measure offsets and report the range, as §5 does. Do not configure the monitor from the same constant as the design.
Reconcile counts at the end of every run. Commands issued, transactions completed, beats received, orphan returns. cnt_reads − cnt_returns is reads that never came back; beats ÷ BEATS_PER_READ against completions catches structural faults. These four numbers take minutes to add and catch more than any per-beat check.
9. Debugging — Single Reads Pass, Back-to-Back Reads Fail
Symptom. Isolated reads return correct data. Reads issued close together fail — wrong data, missing completions, or data attributed to the wrong request. The failure rate rises with request density.
This symptom is diagnostic before any evidence is gathered: something in the read path has a single-transaction assumption that isolated traffic never violates.
Candidate mechanisms.
- The controller admits a second read while the first is outstanding, and the return association cannot tell them apart. Chapter 10.1's single-outstanding admission exists to prevent this — if it has been relaxed without building the association, this is the result.
- The tracker's
pendingoractivestate is shared, so the second acceptance overwrites the first transaction's record mid-flight. - Returns are interleaved or reordered, and the in-order assumption (Chapter 10.2 §7) does not hold in this system.
- One burst's beats overrun into the next transaction's window — a beat-count mismatch that is invisible on isolated reads because the surplus beats have nothing to corrupt. Chapter 10.4 §9.
- The monitor has a single-outstanding model and the design does not, so the monitor is wrong.
Evidence to collect. The number of transactions outstanding at each failure. phantom_beat and cnt_orphan_returns. err_restart from the collector. The tags on returned beats against the tags of outstanding requests. And whether the first read of each burst-of-requests is correct — that single fact separates a shared-state bug from an association bug.
Discriminator.
- Was more than one read outstanding at the failure? If not, the density is coincidental and the fault is elsewhere. If yes, mechanisms 1 to 5 are all live and the rest of the checks apply.
- Is
err_restartasserting? Mechanism 4 — afirstarrived while a burst was active, so returns are overlapping. That is the cleanest signature in the list. - Is the first read of a back-to-back pair correct and the second wrong? Mechanism 2 — the second acceptance disturbed the first's record. If both are wrong, it is more likely mechanism 1 or 3, where the association itself is broken rather than the storage.
- Do returned tags match outstanding requests? With one outstanding read the tag is trivially right, so a mismatch here is only visible once several are in flight — which is exactly why this bug hides.
- Does reordering the requests change which read fails? Mechanism 3. If the failure follows the issue position rather than the address, the association is order-dependent and the in-order assumption is being violated.
- Check the monitor against a known-good design. Mechanism 5 is more common than it deserves to be, and it is worth ruling out before redesigning anything.
Responsible layer. All of layer C, and that is the useful conclusion: a symptom that appears only under concurrency is almost never a device or PHY fault, because the device serves commands it is given without knowing how many the controller has in flight. The investigation belongs entirely above the boundary, which eliminates the most expensive place to look.
Fix. Per mechanism. And whichever it is, the structural fix is the same: make the outstanding-read model explicit, with one record per outstanding transaction and a stated association rule, then assert that every return matches exactly one record. §6's tracker is the single-transaction case of exactly that structure.
10. Common Misconceptions
"A waveform drawn with four cycles proves a four-cycle JEDEC latency."
Why it is tempting: the waveform is concrete and the number is visible.
Concrete failure: an educational figure is quoted in a review as a device parameter and a schedule is built on it.
Correct model: every waveform in this module is labelled educational. Real values come from the device's documentation for its configuration, via §2's steps 1 to 3.
Prevention: treat any number in a tutorial waveform as illustrative — including every one in this chapter.
"The observed command-to-data offset is the device's read latency."
Why it is tempting: it is the latency you can measure, so it feels like the latency.
Concrete failure: a controller's EXPECT_LATENCY is set from a datasheet's read latency, and every read arrives late by the PHY's contribution — which is then "fixed" by tuning the constant, and breaks after retraining.
Correct model: the controller-domain offset includes the PHY's share. §1 and §4's header. Module 19 owns that term.
Prevention: §2's step 9 — does the discrepancy move with temperature or retraining?
"The read completed, so the timing is correct."
Why it is tempting: completion is the visible success signal.
Concrete failure: a design working with zero margin passes every functional test and fails on the next board or at the next temperature, with no test having ever measured the margin.
Correct model: completion says the beats arrived and were counted. Timing is a separate measurement — §6's trace completes perfectly with a late first beat.
Prevention: measure the offset on every read and report the range, even when everything passes.
"A latency error and a data error look different."
Why it is tempting: they are different faults, so they should present differently.
Concrete failure: a scoreboard sampling inside a predicted window reports a one-cycle latency error as no data returned, and the investigation goes to the PHY and the device — neither of which is at fault.
Correct model: they look identical unless the environment samples on the boundary's valid rather than on a window. §8.
Prevention: never gate data capture on a predicted time in a verification environment.
"CAS latency and tRCD are the same kind of number."
Why it is tempting: both appear in the same part specification and both are cycle counts.
Concrete failure: a latency budget that counts one interval twice, or omits the row-opening term for accesses that needed an activate.
Correct model: they relate different event pairs. This module's is column-command-to-data; the other concerns the activate relationship and is Modules 13 and 14'. A row hit pays only this one.
Prevention: name the two events every latency relates before using it.
"A read timeout will catch a non-responding device."
Why it is tempting: every other protocol has one.
Concrete failure: an engineer waits for a timeout that does not exist and observes a hang with no diagnostic.
Correct model: DDR has no read timeout. A device that does not respond reports nothing. §4's OVERDUE_CYCLES is an educational threshold a controller may choose to impose so that a non-response becomes an event rather than a hang.
Prevention: build the threshold deliberately and label it as a design choice, never as a protocol feature.
11. Interview Reasoning
"Walk me through analysing a read trace you have never seen before."
Before opening it: establish the generation, the operating point, and the mode-register configuration actually in force — read the registers back rather than assuming what was programmed. Those three fix the burst length, the latency terminology and the clock period, and without them no measurement has anything to be compared against. Then identify the command sampling event, which is the origin of everything; determine the applicable read latency including any additive component; locate the expected window, remembering that a strobe's preamble means the window is not an instant; convert the burst duration into whichever unit you are comparing against. Then measure, and only then judge. Most "the latency is wrong" findings turn out to be "the expectation was wrong."
"What is the difference between command correctness and data-return correctness?"
They are independent, and a read can fail at either without touching the other. A command can be perfectly encoded, correctly targeted and legally issued, and the device can return the wrong data. Or the data can be exactly right and arrive at a time the controller was not looking. The practical consequence is structural: an environment must check the command, the transfer, the arrival time, the burst structure and the data values as five separate checks with five separate reports, because a scoreboard that samples data inside a predicted window turns a one-cycle timing error into apparent total data loss and never reaches the comparison that would have identified it.
"How would a scoreboard associate returned beats with a request?"
Not from anything on the interface, because no transaction identity crosses a DDR bus. It maintains its own outstanding-read model, built from accepted commands, and applies an association rule — typically that returns emerge in issue order, so the oldest outstanding record owns the next return. The rule must match the controller's, or both will attribute the same beats confidently and to different transactions, and every downstream data comparison is then checking correct data against the wrong request. With one read outstanding this is trivial; the difficulty appears exactly when concurrency does, which is why the bug hides under light traffic.
"How would you detect a one-cycle latency model error?"
By measuring the offset from the command event to the first beat and reporting the distribution, rather than checking it against a constant. A checker configured from the same constant as the design cannot detect a wrong constant — both are wrong together and the check passes. Measuring shows the value; a tight distribution at an unexpected offset is unambiguous. Then step 9 of the method attributes it: a discrepancy equal to the configured additive latency means the controller predicted from CL rather than read latency; a fixed one-cycle offset is usually a sampling-convention mismatch; and a discrepancy that moves with temperature or after retraining is the PHY's and not a controller bug at all.
"Reads work individually and fail back-to-back. What does that tell you?"
That something has a single-transaction assumption which isolated traffic never exercises — and immediately that the fault is above the PHY boundary, because the device serves the commands it is given without any notion of how many the controller has in flight. That eliminates the most expensive place to look before any evidence is gathered. Then the discriminators are narrow: if the first of a pair is right and the second wrong, a record is being overwritten; if both are wrong, the association itself is broken; if the failure follows issue position rather than address, the in-order assumption is being violated. And it is worth ruling out that the monitor is the thing with the single-outstanding model, which happens more often than it should.
12. Engineering Exercise
EXPECT_LATENCY = 6, BEATS_PER_READ = 8, OVERDUE_CYCLES = 32, a part marketed at 3200 MT/s.
1. Derive the clock period. Then express the educational latency of 6 cycles in nanoseconds.
2. How many clock periods does the burst occupy? What is the total educational command-to-last-beat span in cycles?
3. A read is accepted at cycle 100. Its first beat arrives at cycle 108. Which outputs assert and what is measured_offset?
4. A trace shows every read late by exactly the configured additive latency. Which of §2's steps was skipped, and what is the fix?
5. cnt_reads is 5,000 and cnt_returns is 4,998 with cnt_orphan_returns at 0. What happened, and what would cnt_orphan_returns = 2 instead have meant?
6. A monitor reports every read one cycle late. The design and the monitor use the same EXPECT_LATENCY. Explain why the report is trustworthy here and would not be if the monitor used a different constant.
13. Summary
A read crosses three timing domains — the command interface in cycles, the source-synchronous return in transfers, and the controller's own clock — and most analysis errors are a measurement from one compared against another.
The method starts before the waveform. Generation, operating point, and the configuration actually in force. Then the command sampling event, the applicable read latency, the expected window, and the burst duration in a stated unit. Most "the latency is wrong" findings are "the expectation was wrong."
Integration produces a check no single block can. The pipeline predicts and cannot observe; the boundary observes and cannot predict; the collector counts and cannot time. Only the comparison between prediction and observation detects a mispredicted latency, and that difference is the most useful number in read debugging.
The observed offset is not the device's read latency. It includes the PHY's contribution, and the test that separates them is whether the discrepancy moves with temperature or retraining.
Five independent checks: command, transfer, timing, structure, data. A scoreboard sampling inside a predicted window collapses timing into data and reports a one-cycle error as total data loss.
And a checker configured from the design's own constant cannot check it. Measure the offset and report its range; a tight distribution at an unexpected value is unambiguous, and no prediction was required to see it.
14. What Comes Next
Module 10 is complete. A read is now an executable transaction: created at acceptance with an identity it will never send anywhere, predicted across an interval nothing signals, delivered as beats through a boundary that separates digital reasoning from a specialised electrical problem, counted against an expectation that must come from configuration, and completed on evidence.
The module's claim in one sentence: a read command does not fetch data — it starts a timed return that the controller alone is responsible for anticipating, recognising, and attributing.
Module 11 — Write Operations takes the other direction, and the contrast is sharper than it looks. The ownership reverses: the controller's side drives DQ and sources DQS, so the timing reference is generated rather than received. The latency relationship changes with it, and a write introduces something a read has no equivalent of — the data must be delivered at a time the device is expecting it, rather than captured at a time the controller must discover.
And the questions this module deferred throughout — when may a command be issued relative to every other command — are Modules 13 and 14, which supply the parameters that turn every educational figure here into a real one.
Return to The Read Command for the transaction, CAS Latency for the prediction, Data Return for the boundary, Burst Reads for the beat count, DQS for the strobe that times the return, and Row Hits for the state a read required before any of this began.
Continue learning
Related tutorials
- Related topic
Write Timing Analysis
A write has five events owed by two parties, and only three appear on any interface. Reading a write trace means computing the rest — and every way that goes wrong produces silent corruption rather than an error.
- Related topic
Write (WR / WRA)
A write's data arrives after its command, which makes three events impossible to conflate: observed, accepted, and completed. A monitor, a protocol checker and a scoreboard each attach to a different one.
- Related topic
Command Scheduling
An eight-step procedure for deciding when a command becomes temporally legal, worked through a full trace by hand — and a timing checker built in the opposite representation to the design, so the two cannot share a bug.
- Related topic
Precharge (PRE / PREA)
Precharge closes a bank, and its scope is decided by an operand: one bank or all of them. It is also the command that exposes why a command stream does not fully describe device state — which is the hardest problem a DV monitor faces.
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.
