DDR · Module 21
Gate Training
The DQS gate exists only inside the PHY, so JEDEC defines no procedure for it — only a preamble the PHY can find. The resulting search has a one-sided pass region whose safe side produces no observable at all.
Chapter 19.4 built the DQS gate and was blunt about its status: the gate is an estimate, its correctness is a containment relationship against a window the PHY cannot see, and the gate_offset it consumes arrives from somewhere else. Chapter 21.2 §3 then assumed that estimate was already right, and showed that a wrong gate produces an empty pass region indistinguishable from a dead channel.
This chapter produces gate_offset. It is the third search in the module and the third kind of observable, and it is the only one for which the standard defines no procedure at all.
The central law, and its qualification:
Training is controlled search over configurable PHY state, using observable pass/fail information to discover an operating point with usable timing margin — and training does not create margin. A read path whose strobe never arrives within any reachable gate position must fail.
1. The Standard Does Not Know the Gate Exists
This is worth establishing first, because it explains the shape of everything that follows.
The DQS gate is a construct entirely internal to the PHY. 19.4 §2 derived why it must exist: between bursts the strobe line is not driven, so the capture path must be prevented from seeing it, and the PHY therefore opens a window around the interval in which it believes a burst will arrive.
The DRAM has no part in this. It drives a strobe when it has data to send, and it neither knows nor could know that the controller is gating. Searching JESD79-4 for a DQS gate, gate training, a read gate or a gate-open concept returns nothing — none of those terms appear, because there is nothing at the device interface for them to name.
The consequence is a genuine asymmetry with the two previous chapters:
| Observable comes from | Standard defines | |
|---|---|---|
| 21.1 Write levelling | The device, as a phase comparator | Mode, stimulus, feedback, terminating condition |
| 21.2 Read levelling | The device, as a pattern source | The stimulus mechanism (MPR), its formats and patterns |
| 21.3 Gate training | The PHY's own strobe detector | Nothing. Only an assist — §3 |
So gate training is vendor-specific in a stronger sense than the other steps. The other two have implementation freedom within a defined procedure. This one has no defined procedure to be free within.
2. What Is Actually Being Searched
Chapter 19.3 §2 decomposed a launch latency into two terms: a programmed term anyone can compute, and a measured term nobody can. 19.4 inherited exactly the same split on the receive side, and its gate controller takes both as separate inputs:
from 19.4's phy_dqs_gate_ctrl port list
cl_cycles the programmed term -- CAS latency, from 14.9.
Known. A register value.
gate_offset the measured term. OPAQUE in 19.4.
─────────────────────────────────
THIS CHAPTER'S OUTPUT.
gate_trained whether the above means anything yet.So the search has a precisely bounded target: the number of cycles by which a burst's actual arrival differs from what cl_cycles alone predicts. That difference exists because the round trip includes command flight time to the device, the device's own output delay, and data flight time back — none of which the controller can compute.
Two things about the target are worth being explicit about.
It is a round-trip quantity. It is not the strobe's delay, or the board's delay, or the device's delay. It is the whole loop from launching a command to the strobe appearing at the PHY's pin, minus the part that was already predictable. Nothing in the search can decompose it further.
It is measured in the PHY's clock cycles at coarse resolution. gate_offset in 19.4 is a cycle count, and a cycle count is all the gate needs to open in the right cycle. Positioning within a cycle is a separate, finer problem — §6.
3. What the Standard Supplies Instead
DDR4 gives the PHY two things that make the search tractable, and both are register-controlled.
A programmable read preamble. MR4 A11 selects a read preamble of 1 nCK or 2 nCK. The preamble is strobe activity that precedes the data — 6.10 §4 established it as part of why the strobe's window is wider than the data's.
A training mode for it. MR4 A10 is Read Preamble Training Mode, 0 to disable and 1 to enable. The standard's §4.20.3 explains its purpose directly: a programmable read preamble requires an additional MRS mode to train it, and in that mode the device drives the strobe with a toggle — 1 tCK or 2 tCK according to the preamble setting — so that the controller can locate it.
Two constraints on that mode matter to a sequencer:
It is only available in MPR mode. So MR3's MPR enable (21.2 §4) must already be set. Gate training and read levelling therefore share a prerequisite, and 21.5 has to order mode entry accordingly.
The command stream is restricted. The standard disallows illegal READ commands, and any command during or initiating the read process, while read preamble training is active. A sequencer that interleaves other traffic into the search is outside the mode's defined behaviour.
There is one more published fact that shapes the selection policy in §7, and it is easy to overlook:
The read postamble is fixed and short. DDR4 supports a fixed read postamble with a nominal value of 0.5 tCK, for both the 1 tCK and 2 tCK preamble modes. So the strobe activity that follows the last data is half a clock, while the activity that precedes the first data is one or two whole clocks.
1 tCK preamble 2 tCK preamble
──┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌── ──┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌──
└───┘ └─┘ └─┘ └─┘ └─┘ └─────┘ └─┘ └─┘ └─┘ └─┘
│←1tCK→│← data →│0.5 │← 2tCK →│← data →│0.5
the leading margin is programmable and can be DOUBLED;
the trailing margin is fixed at nominal half a clock.That asymmetry is not incidental. It is the physical reason the search's pass region is lopsided, and the reason the selection policy in §7 is not a midpoint.
4. A Presence Observable
The third observable in the module is different in kind from the first two, and the difference is worth naming precisely.
Write levelling's bit reported a side of a boundary. Read levelling's bit reported a conjunction — everything worked. Gate training's bit reports presence: was strobe activity seen inside the window the gate opened.
That sits between the other two in usefulness:
| Write levelling | Gate training | Read levelling | |
|---|---|---|---|
| Reports | A side | Presence in a window | A conjunction |
| Localises a failure | Yes — which side | Partly — present or not | No |
| Needs the data path correct | No | No | Yes |
| Needs a known pattern | No | No | Yes |
The third row is what makes gate training runnable before read levelling. Detecting that a strobe arrived does not require the data to be sampled correctly, assembled correctly, or compared against anything. It requires only that something toggled where something was expected to toggle.
Implementations differ in how they get that bit, and the difference is real:
A strobe detector. Dedicated logic observes the gated strobe input and reports whether activity occurred. Cheap, direct, and the reason this search can precede everything else.
A beat count. 20.3's capture path already produces one sample per strobe edge, so counting samples inside the window gives presence and completeness — did the expected number of edges arrive. This is strictly more informative than a boolean, and §5's RTL uses it.
Data correctness as a proxy. Reuse 21.2's comparison. This works and is a poor choice for a first search: it reintroduces the conjunction, so a gate failure and a capture failure become the same bit again.
5. Coarse Before Fine
The search decomposes into two stages with genuinely different characters, and conflating them produces a search that cannot converge.
Coarse: which cycle. The unknown is a whole number of PHY clock cycles. The domain is small — bounded by the maximum round trip the design supports, which is 19.4's MAX_LATENCY. The observable is presence, and the answer is gate_offset.
Fine: where within the cycle. Once the gate opens in the right cycle, the remaining question is sub-cycle positioning of the gate's edges relative to the strobe's actual arrival. The domain is delay codes, not cycles.
These must be done in that order, and the reason is not merely convenience: the fine search has no meaningful observable until the coarse search is right. If the gate is open in the wrong cycle entirely, every sub-cycle position produces the same answer — nothing detected — so the fine sweep returns an empty region and no information about which direction to move.
This chapter's RTL implements the coarse search, and the scope note is deliberate:
6. The Fine Stage, Described
Even though it is not built here, the fine stage's structure is worth stating because its selection policy differs from the coarse one.
The variable is a sub-cycle delay applied to the gate's opening edge. The observable is still presence, but now presence of the preamble specifically — and this is where the programmable preamble earns its existence.
With a 1 tCK preamble, the gate's opening edge must land inside a one-clock window to catch the preamble without clipping into the previous burst's postamble region. With 2 tCK, that window is twice as wide. The preamble length is the fine search's tolerance budget, directly: doubling it doubles the acceptable placement error.
The trailing edge has no such luxury. The postamble is fixed at nominal 0.5 tCK (§3), so the closing edge's tolerance is half a clock regardless of configuration — which is why 19.4 carries TRAIL_CYCLES as a separate parameter from LEAD_CYCLES, and why the two are not symmetric in a real configuration.
7. The Pass Region Is One-Sided
Here is the result that makes gate training different from 21.2, and it follows from §3's asymmetry plus §4's callout.
Sweep the gate's opening position from very early to very late, and count the beats detected inside the window:
gate opens ... early ─────────────────────────────► late
candidate 0 1 2 3 4 5 6 7 8 9
beats seen 4 4 4 4 4 4 4 3 2 1
pass P P P P P P P F F F
└──────────────┬──────────┘ └──┬──┘
all beats captured beats LOST
│
the boundary: opening later
than 6 starts clipping the
preamble and then the dataThe region is bounded on the late side by a hard, observable failure: beats are missing, the count is short, data is lost. It is bounded on the early side by nothing the search can see.
Opening the gate earlier and earlier keeps passing. Every beat still arrives inside the window. The count stays correct. And the gate is getting worse the whole time, because 19.4 §9 established the cost: a wider-open gate admits more interval during which the strobe line is not driven, and an undriven line is not a defined logic level.
observable cost unobservable cost
─────────────── ─────────────────
late beats missing -
early - undriven line admitted,
previous postamble, noiseSo the naive policy — sweep, find the region, commit the midpoint — is wrong here, and wrong in a specific way: the midpoint of the observed pass region sits as far early as it sits late, but the two directions do not cost the same and only one of them is measured. A midpoint policy will drift the gate earlier the wider the observable region happens to be.
The correct policy is relative to the observable boundary:
| Policy | Choice | Verdict |
|---|---|---|
| Midpoint of the pass region | Centre of codes 0..6, i.e. 3 | Wrong — treats an unmeasured side as if it were measured |
| Latest passing candidate | 6 | Wrong — sits exactly at the hard failure |
| Boundary minus a guard | 6 − guard | Correct in structure; the guard is 19.4's LEAD_CYCLES |
The third is what 19.4's LEAD_CYCLES parameter is for, and this chapter is where its value acquires a meaning: it is the distance back from the measured late boundary, chosen to cover the drift and uncertainty the search cannot observe, and paid for in admitted undriven line.
8. The Search, in Sequence
Message seven is the presence observable, in its more useful counting form. Message nine is the stopping condition, and it differs from both previous chapters: 21.1 stopped at a 0-to-1 transition, 21.2 swept the entire domain, and this one stops at the first failure — because §7 established that everything below the boundary passes, so there is nothing above it worth seeing and nothing below it that a further sweep would reveal.
9. The Coarse Search
// ---------------------------------------------------------------------
// gate_train_search -- coarse (cycle-granular) DQS gate search.
//
// CLASSIFICATION: educational, synthesisable. Produces the gate_offset
// and gate_trained that Chapter 19.4's phy_dqs_gate_ctrl consumes.
//
// WHAT IT DOES NOT MODEL:
// - sub-cycle gate positioning (§6) -- a delay-line function, and
// 19.1 §5 establishes it is not portable RTL
// - the strobe, the preamble, the postamble, or the detector
// - the undriven line admitted by an early gate: the cost that
// bounds the search's early side and produces NO observable (§7)
//
// The search stops at the FIRST short count, then steps back by a
// guard. It does not centre, because one side of the region is unmeasured.
// ---------------------------------------------------------------------
module gate_train_search #(
// Must match 19.4's MAX_LATENCY domain.
parameter int MAX_OFFSET = 32,
parameter int MAX_BEATS = 16,
// Reads per candidate. A short count is a definite failure, so one
// complete count is weaker evidence than one short count -- see §12.
parameter int REPEATS = 2,
// Cycles back from the measured late boundary. THIS IS 19.4's
// LEAD_CYCLES, and it is a POLICY value: it buys tolerance to drift
// the search cannot observe, and is paid for in admitted undriven
// line. It cannot be derived from anything in this module.
parameter int GUARD = 1,
parameter int OFF_W = (MAX_OFFSET <= 1) ? 1 : $clog2(MAX_OFFSET + 1),
parameter int BEAT_W = (MAX_BEATS <= 1) ? 1 : $clog2(MAX_BEATS + 1),
parameter int REP_W = (REPEATS <= 1) ? 1 : $clog2(REPEATS + 1)
) (
input logic clk,
input logic rst_n,
input logic start,
// How many beats a complete burst should deliver. From the burst
// length configuration, not a constant.
input logic [BEAT_W-1:0] expected_beats,
// ── Prerequisites imposed by the standard (§3): the device must be
// in MPR mode with read preamble training enabled. This block
// does not set them -- 21.5 does -- but it refuses to probe
// without them, because the strobe would not be toggling.
input logic mpr_mode_active,
input logic rpt_mode_active,
// ── To 19.4's gate controller, and to whatever issues reads.
output logic [OFF_W-1:0] cand_offset,
output logic read_req,
// ── The observable: how many strobe edges fell inside the window.
input logic read_done,
input logic [BEAT_W-1:0] beats_seen,
// ── THE COMMITTED RESULT -- 19.4's two inputs.
output logic [OFF_W-1:0] gate_offset,
output logic gate_trained,
output logic train_done,
output logic train_failed,
// ── Observability. The measured boundary is published separately
// from the committed offset so the guard is visible in a log.
output logic [OFF_W-1:0] late_boundary,
output logic boundary_found,
output logic [2:0] state_out,
output logic err_probe_outside_mode,
output logic err_never_complete
);
initial begin
if (GUARD < 0)
$fatal(1, "gate_train_search: GUARD must not be negative");
if (GUARD > MAX_OFFSET)
$fatal(1, "gate_train_search: GUARD (%0d) exceeds the domain", GUARD);
if (MAX_OFFSET > (2**OFF_W - 1))
$fatal(1, "gate_train_search: MAX_OFFSET does not fit OFF_W");
end
typedef enum logic [2:0] {
S_IDLE = 3'd0,
S_ARM = 3'd1,
S_READ = 3'd2,
S_CLASS = 3'd3,
S_STEP = 3'd4,
S_COMMIT = 3'd5,
S_FAIL = 3'd6
} state_e;
state_e state, next;
logic [OFF_W-1:0] cand;
logic [REP_W-1:0] reps;
logic all_complete; // every repeat delivered a full count
logic [OFF_W-1:0] boundary; // highest candidate that passed
logic have_boundary;
logic [OFF_W-1:0] committed;
logic committed_ok;
// This candidate passed only if every repeat delivered the full
// expected beat count. A SHORT count is the hard failure of §7.
logic this_pass;
assign this_pass = all_complete;
logic domain_done;
assign domain_done = (cand >= OFF_W'(MAX_OFFSET));
always_comb begin
next = state;
unique case (state)
S_IDLE : if (start && mpr_mode_active && rpt_mode_active) next = S_ARM;
S_ARM : next = S_READ;
S_READ : if (read_done)
next = (reps + REP_W'(1) >= REP_W'(REPEATS)) ? S_CLASS : S_READ;
// The stopping condition of §8: stop at the FIRST failure. Every
// candidate below the boundary passes (§7), so a full sweep adds
// nothing -- unlike 21.2, where islands make it mandatory.
S_CLASS : if (!this_pass) next = have_boundary ? S_COMMIT : S_FAIL;
else if (domain_done) next = S_COMMIT;
else next = S_STEP;
S_STEP : next = S_ARM;
S_COMMIT : next = S_IDLE;
S_FAIL : next = S_IDLE;
default : next = S_IDLE;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
cand <= '0;
reps <= '0;
all_complete <= 1'b1;
boundary <= '0;
have_boundary <= 1'b0;
// THE SAFE STATE. Offset zero with gate_trained low: 19.4's
// gate controller raises err_gate_untrained rather than opening
// a window on an untrained offset, so this is a state that
// refuses service rather than one that guesses.
committed <= '0;
committed_ok <= 1'b0;
end else begin
state <= next;
unique case (state)
S_IDLE : if (start && mpr_mode_active && rpt_mode_active) begin
cand <= '0;
reps <= '0;
all_complete <= 1'b1;
boundary <= '0;
have_boundary <= 1'b0;
committed_ok <= 1'b0;
end
S_READ : if (read_done) begin
reps <= reps + REP_W'(1);
// Any short count fails the candidate for good.
if (beats_seen != expected_beats) all_complete <= 1'b0;
end
S_CLASS : begin
if (this_pass) begin
boundary <= cand;
have_boundary <= 1'b1;
end
reps <= '0;
all_complete <= 1'b1;
end
S_STEP : cand <= cand + OFF_W'(1);
S_COMMIT : begin
// Step BACK from the measured boundary by the guard, floored
// at zero. Not the midpoint of the pass region: §7.
committed <= (boundary > OFF_W'(GUARD))
? boundary - OFF_W'(GUARD)
: '0;
committed_ok <= 1'b1;
end
default : ;
endcase
end
end
assign cand_offset = cand;
assign read_req = (state == S_READ) && !read_done;
assign gate_offset = committed;
assign gate_trained = committed_ok;
assign train_done = (state == S_COMMIT) || (state == S_FAIL);
assign train_failed = (state == S_FAIL);
assign late_boundary = boundary;
assign boundary_found = have_boundary;
assign state_out = state;
// Probing without the device modes set would sample a quiet line and
// report absence everywhere -- a failure with a misleading cause.
assign err_probe_outside_mode = read_req && !(mpr_mode_active && rpt_mode_active);
// The very first candidate already failed: no complete count anywhere.
assign err_never_complete = (state == S_FAIL) && !have_boundary;
endmoduleThe S_CLASS branch is where the chapter's argument becomes code. A failing candidate with a boundary already recorded is success — it is how the boundary gets confirmed as a boundary. A failing candidate with no boundary recorded is failure, because the very first, earliest-opening candidate could not capture a complete burst, and opening even earlier is not available.
10. A Coarse Sweep, Cycle by Cycle
Sweeping the gate later until beats are lost
10 cyclesRead the beats_seen row against the pass row and the one-sidedness of §7 is visible directly: the count is flat at four across seven candidates and then degrades monotonically. There is no upper shoulder — the region simply runs off the early end of the sweep, and the search has no way to know how much further it would have kept passing.
11. What the Assertions Prove
// Bind unit note: these properties reference gate_train_search's
// internal state and are written as though bound into the module, so
// clk and rst_n are visible directly.
// P1 -- the committed offset is the measured boundary minus the guard.
// This is §7's selection policy, stated checkably. It is the property
// that FAILS if someone "improves" the search by centring the region.
property p_commit_is_boundary_minus_guard;
@(posedge clk) disable iff (!rst_n)
$rose(gate_trained)
|-> (gate_offset == ((late_boundary > OFF_W'(GUARD))
? late_boundary - OFF_W'(GUARD)
: OFF_W'(0)));
endproperty
assert property (p_commit_is_boundary_minus_guard);
// P2 -- a commit never happens without a boundary having been found.
property p_commit_needs_boundary;
@(posedge clk) disable iff (!rst_n)
(state_out == 3'd5) |-> boundary_found;
endproperty
assert property (p_commit_needs_boundary);
// P3 -- gate_trained is never asserted by a failed search. 19.4's gate
// controller gates on exactly this signal, so a violation here would
// let it open a window on a meaningless offset.
property p_fail_never_trained;
@(posedge clk) disable iff (!rst_n)
train_failed |-> !$rose(gate_trained);
endproperty
assert property (p_fail_never_trained);
// P4 -- candidate/committed separation, as in 21.1 §7 and 21.2 P4.
property p_commit_only_on_commit;
@(posedge clk) disable iff (!rst_n)
(gate_offset != $past(gate_offset, 1))
|-> ($past(state_out, 1) == 3'd5);
endproperty
assert property (p_commit_only_on_commit);
// P5 -- the recorded boundary only ever moves up during a sweep, and
// only to the current candidate. A boundary that could regress would
// mean a passing candidate below a failing one was recorded later.
property p_boundary_monotonic;
@(posedge clk) disable iff (!rst_n)
(late_boundary != $past(late_boundary, 1))
|-> (late_boundary > $past(late_boundary, 1));
endproperty
assert property (p_boundary_monotonic);
// P6 -- a candidate passes only on a complete count from every repeat.
property p_pass_needs_complete_count;
@(posedge clk) disable iff (!rst_n)
((state_out == 3'd3) && ($past(state_out, 1) == 3'd2) && boundary_found
&& (late_boundary == cand_offset))
|-> $past(all_complete, 1);
endproperty
assert property (p_pass_needs_complete_count);
// P7 -- no read is requested without both device modes set (§3).
property p_probe_inside_modes;
@(posedge clk) disable iff (!rst_n)
read_req |-> (mpr_mode_active && rpt_mode_active);
endproperty
assert property (p_probe_inside_modes);
// ── Cover.
cover property (@(posedge clk) disable iff (!rst_n) $rose(gate_trained));
// The first candidate already failed: §12's dead-channel case.
cover property (@(posedge clk) disable iff (!rst_n) err_never_complete);
// The whole domain passed without ever finding a boundary -- §12's
// most dangerous case, because it looks like a wide healthy region.
cover property (@(posedge clk) disable iff (!rst_n)
(state_out == 3'd5) && (late_boundary == OFF_W'(MAX_OFFSET)));
// A guard larger than the boundary, so the commit floors at zero.
cover property (@(posedge clk) disable iff (!rst_n)
$rose(gate_trained) && (gate_offset == '0)
&& (late_boundary != '0));
// A partial count strictly between zero and complete: the gate
// clipping rather than missing entirely.
cover property (@(posedge clk) disable iff (!rst_n)
read_done && (beats_seen != '0)
&& (beats_seen != expected_beats));P1 is this chapter's signature property, and it exists to defend a non-obvious policy against a plausible-looking change. Someone reading 21.2 first and this chapter second will reasonably wonder why the gate is not centred in its region, and P1 fails immediately if they act on that instinct.
The third cover is the one worth wiring into a regression. A sweep that passes at every candidate up to MAX_OFFSET never observed the late boundary at all, so its “boundary” is just the top of the domain and the committed offset is the top minus a guard — a number with no measurement behind it.
12. Failure Is a Result
Three distinct outcomes need separating, and one of them is disguised as success.
Nothing complete anywhere. The first, earliest candidate already returns a short count. err_never_complete fires and the search fails. Causes: the strobe is not being driven at all — check that MPR and read preamble training mode were actually entered — or the round trip is longer than MAX_OFFSET, or the channel is dead.
A boundary found. The normal case. The search commits.
Everything passes to the top of the domain. No boundary was ever observed, so the search has not measured anything. The RTL above commits in this case, with late_boundary equal to MAX_OFFSET, and that is a defensible choice only because the third cover makes it visible. An implementation that treated this as a plain success would be reporting a trained gate whose position came from the size of a parameter rather than from the device.
That third case deserves the emphasis. It is the gate-training analogue of 21.1 §12's all-ones failure and 21.2 §13's whole-domain pass: an observable that never changed across the sweep means the experiment did not test the variable, and in all three chapters that situation superficially resembles the best possible outcome.
13. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
| First candidate fails | train_failed, err_never_complete | Nothing to step back from |
| Every candidate passes | Commits MAX_OFFSET − GUARD; the third cover fires | No boundary measured; the number comes from a parameter |
GUARD exceeds the boundary | Commits 0 | Floored rather than wrapped; wrapping would place the gate at the far end |
GUARD = 0 | Commits the boundary itself | Legal and inadvisable: sits exactly at the hard failure |
beats_seen zero at a candidate | Candidate fails | Total miss and partial clip are both failures here |
beats_seen exceeds expected_beats | Candidate fails | An over-wide gate admitting extra edges is not a pass — §4's callout |
| One repeat complete, one short | Candidate fails | all_complete clears on any short count |
| Modes drop mid-sweep | err_probe_outside_mode on the next request | The strobe would not be toggling |
start mid-sweep | Ignored outside S_IDLE | Would merge two sweeps' boundaries |
| Re-run after success | Boundary and commit discarded at S_IDLE | Round trip changes with temperature — 19.5 §4 |
The sixth row is worth its place. Treating a larger count as a pass is an easy mistake — more beats sounds like more signal — and it converts the one unobservable cost of §7 into a partially observable one, then ignores it. Requiring the count to be exactly right is what keeps an over-wide gate from scoring well.
14. DV — Model the Round Trip, Check the Policy
The checker models a device and channel with a hidden arrival cycle, and verifies the search's policy against it. It must not re-implement the sweep.
// Independent round-trip model: knows the true arrival; the engine
// does not. SIMULATION-ONLY.
class gate_channel_model;
int unsigned arrive; // the true arrival cycle, hidden
int unsigned preamble; // 1 or 2, per MR4 A11
int unsigned total_beats;
// Beats seen when the gate opens at `open_at`. Opening at or before
// the preamble start catches everything; later clips progressively.
function int unsigned beats(int unsigned open_at);
if (open_at <= arrive - preamble) return total_beats;
if (open_at >= arrive + total_beats) return 0;
return total_beats - (open_at - (arrive - preamble));
endfunction
endclass| Check | What it establishes |
|---|---|
gate_trained implies late_boundary == arrive − preamble | The boundary found is the real last-good cycle |
gate_offset == late_boundary − GUARD | The policy, independently recomputed |
With preamble = 2, the boundary is one cycle later than with preamble = 1 | §6's claim that the preamble is the tolerance budget |
Set arrive beyond MAX_OFFSET; expect err_never_complete | §12's first case |
Set arrive so every candidate passes; expect the third cover, not a silent pass | §12's third case |
Sweep GUARD from 0 to boundary+1; commit floors at 0, never wraps | The saturation path |
Feed a count above total_beats; expect the candidate to fail | §13's sixth row |
Two runs, same arrive; identical committed offset | A cycle-granular search should be exactly repeatable |
The last check is a genuine difference from the previous two chapters. 21.1 §5 and 21.2 §15 both had to tolerate run-to-run variance, because both observables are samples near a boundary. A cycle-granular presence count is not marginal in that way — a beat either landed in the window or it did not — so gate training's coarse stage should be bit-exact across runs, and variance is a symptom rather than a fact of life.
Here is the failure the third check catches:
GATE TRAINING PREAMBLE MISMATCH
MAX_OFFSET = 32, GUARD = 1, total_beats = 4
model truth : arrive = 19, preamble as configured
preamble = 1 (MR4 A11 = 0)
last complete candidate : 18 (= 19 - 1)
committed gate_offset : 17
preamble = 2 (MR4 A11 = 1)
last complete candidate : 17 (= 19 - 2)
committed gate_offset : 16
engine under test, run with the DEVICE at 2tCK preamble
but the model's expectation computed for 1tCK:
observed boundary 17, expected 18 --> MISMATCH
diagnosis : the register write that selected a 2tCK preamble and
the search's assumption about it disagree. The search
is not wrong -- it measured the channel it was given.
why it matters : the committed offset is one cycle EARLIER than
the checker expects, which is the SAFE direction, so
the interface works and the discrepancy is silent.
The same disagreement in the other direction -- device
at 1tCK, search assuming 2tCK -- commits one cycle
LATE and loses a beat under any drift.
caught by : this check only. No property in §11 fires, because the
search obeyed its policy exactly; the fault is in a
configuration the search cannot observe.That is the useful shape of a gate-training bug: a configuration mismatch that is asymptomatic in one direction and marginal in the other, with every assertion passing.
15. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
err_never_complete at every lane | Strobe not toggling — modes not entered | Confirm MR3 MPR enable and MR4 A10 |
err_never_complete, modes confirmed | Round trip exceeds MAX_OFFSET | Compare against the design's latency budget |
| Whole domain passes | The gate never closed late enough to clip | §12's third case; the offset is a parameter, not a measurement |
| Boundary varies run to run | Should not happen at cycle granularity | Suspect the detector, or a burst arriving at a non-deterministic cycle |
| Boundary differs per lane by a cycle | Plausible — round trips differ per lane | Compare with the routing; per-lane storage is needed |
| Gate trains, read levelling finds an empty region | Gate is in the right cycle, wrong sub-cycle | The fine stage of §6 has not run |
| Gate trains, occasional read corruption | Guard too small for actual drift | Raise GUARD; note the cost is admitted undriven line |
| Works cold, fails warm | Round trip drifted past the guard | Periodic retraining — 19.5 §4 |
| Beat count exceeds expected | Gate too wide, admitting extra edges | Should fail the candidate — §13 |
| Trains differently after a preamble change | Expected, by exactly the preamble delta | §14's mismatch report |
The sixth row is the one that sends people back to this chapter after they thought it was finished. A correct coarse offset with no fine positioning produces a gate that opens in the right cycle and still fails to frame the strobe usefully — and the symptom appears in 21.2, one step later, as an empty pass region.
The fourth row is worth trusting. Because the coarse observable is not marginal (§14), run-to-run variation in the boundary is not the expected noise it would be in the other two chapters; it means something else is moving.
16. Misconceptions
“Gate training is a JEDEC procedure.” It is not. §1: the standard has no gate concept, because the gate is internal to the PHY. What the standard defines is a preamble and a mode that makes the preamble findable.
“Read Preamble Training and gate training are the same thing.” One is a device mode, the other is a PHY search. The mode is the assist; the search is nobody's but the PHY's.
“The gate should be centred in its pass region.” §7. The region is one-sided: the late edge is a measured failure, the early edge is an unmeasured cost. Centring treats an unmeasured side as if it had been measured.
“An earlier gate is a safer gate.” Safer against the observable failure, and worse against the unobservable one. 19.4 §9 states the cost: more interval in which an undriven line is admitted.
“Detecting the strobe proves the gate is right.” §4's callout. It proves overlap. A grossly over-wide gate detects the strobe at every candidate.
“More beats than expected is a better result.” It means extra edges were admitted from outside the burst. §13 fails the candidate for it.
“The gate offset is the board delay.” §2. It is a round-trip residual — command flight, device output delay and data flight combined — after removing the part cl_cycles already predicted. It cannot be decomposed further by this search.
“Coarse and fine can be searched together.” §5. Until the coarse offset is right, every fine position returns the same non-observation, so the fine sweep has no gradient to follow.
“A 2 tCK preamble improves signal integrity.” It improves the trainability of the gate by widening the leading detection window (§6). Whether it helps the channel electrically is Module 22's question, not this one's.
“Gate training can replace read levelling.” They answer different questions with different observables. Presence in a window is not a sampling point, and 21.2 §3 depends on this chapter having already succeeded.
17. Interview Reasoning
Why is there no JEDEC gate-training procedure? Because the gate is not at the device interface. The DRAM drives a strobe when it has data; it has no notion that the controller windows its input. There is nothing for the standard to specify.
Then what does the standard contribute? A programmable read preamble and a mode in which the device toggles the strobe so the controller can locate it. That converts an unfindable arrival into a findable one, which is the hard part.
What quantity does gate training actually measure? The round-trip residual: the cycles between launching a read and the strobe appearing at the PHY, minus what CAS latency already accounted for. It is not the strobe delay or the board delay in isolation.
Why not centre the gate in its pass region? Because the region is one-sided. Opening late loses beats, which is observable. Opening early admits an undriven line, which is not. Centring would spend measured margin to buy protection against a cost the search never saw.
Your sweep passes at every candidate. What do you conclude? That the experiment did not test the variable. The gate never opened late enough to clip the burst, so no boundary was measured and the committed offset derives from the domain's size rather than from the device.
Why must coarse precede fine? Because a wrong coarse offset makes every fine position produce the same observation — nothing detected — so the fine search has no direction to move in.
Gate training passes and read levelling reports an empty region. Where is the fault? Most likely the gate is in the right cycle but positioned wrongly within it, so the strobe is admitted but not usefully framed. The coarse stage succeeded and the fine stage has not run or has failed.
Why should the coarse boundary be identical across runs, when write levelling's answer is not? Because a beat either fell inside the window or it did not — the coarse observable is not a marginal sample. Write levelling's bit is the resolution of a metastable comparison near a boundary. Run-to-run variance is expected there and is a symptom here.
What does a larger GUARD buy and what does it cost? It buys tolerance to drift toward the late boundary, which is the failure that loses data. It costs additional admitted undriven line. Neither quantity is observable in this search, which is why it is a parameter.
18. Exercises
-
Change
S_COMMITto commit the midpoint of the observed pass region instead of boundary-minus-guard. Which property in §11 fires? Then explain why the resulting gate would still pass every check in §14's table except two. -
The RTL commits when the whole domain passes. Argue for making that a failure instead. What information would a caller lose, and which cover property already makes the situation visible?
-
Using §14's model, compute the committed offset for
arrive = 24,preamble = 2,GUARD = 3, and confirm it against the RTL's arithmetic. Then find the smallestarrivefor which the commit floors at zero. -
beats_seenexceedingexpected_beatsfails the candidate. Construct the gate misconfiguration that produces an over-count, and explain why treating it as a pass would make the early side of §7 partially observable — and why that would be worse than it being wholly unobservable. -
Write the property that would detect §14's preamble mismatch, given an additional input carrying the configured preamble length. Then explain why that property is a configuration check rather than a search check.
-
The fine stage is described and not built. Specify its interface — inputs, outputs, and observable — such that everything it does not model is at the boundary rather than inside it. Compare your interface with 19.4's treatment of the same problem.
-
Gate training needs MPR mode, and so does read levelling. Read levelling also needs gate training to have succeeded. Draw the dependency graph including 21.1, and identify which orderings are legal. Compare with what 21.5 concludes.
-
All three chapters so far have a failure mode in which the observable never changes across the sweep. State the general principle that covers all three, and propose a single check that any search engine in this module could carry to detect it.
19. Where This Goes
Three searches, three observables, three different shapes.
A phase report gave 21.1 one transition and an early exit. A correctness conjunction gave 21.2 two edges and a mandatory full sweep. A presence count gave this chapter a one-sided region, a stopping condition at the first failure, and a selection policy that deliberately does not centre — because half the region's boundary is a cost no digital observable reports.
What all three share is a single search axis. One variable, swept; one region or transition, found; one code, committed.
Chapter 21.4 removes that. It adds a second axis — the receiver's reference level alongside the sampling position — and the search space stops being a line and becomes a grid. The midpoint arithmetic of 21.2 §6 does not generalise to two dimensions, the number of probes required grows as the product rather than the sum, and the standard specifies the second axis's codes with a step size that turns out not to correspond to a known physical step at all.
Continue learning
Related tutorials
- Related topic
Timing Alignment
A write command and its data leave the PHY on different paths and must arrive in a defined relationship. The device specifies the relationship; the PHY absorbs the difference between the paths.
- Related topic
Write Leveling Algorithm
Chapter 20.4 built a probe that asks the device one yes-or-no question. Turning it into a search means sweeping a delay, distrusting any single answer near the boundary, confirming a transition, and keeping the candidate out of the production datapath.
- Related topic
Read Leveling
The read side has no phase comparator, only a correctness bit. That forces a two-edge search over a full sweep, an MPR stimulus whose format decides what can be trained at all, and per-bit adjustment to recover per-pin skew.
- Related topic
Eye Centering
A second axis turns a line search into a grid search, the probe count into a product, and the centre into something undefined. The grid is a measured proxy for an eye — and JEDEC says of its own receiver mask that it is not the eye.
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.
