Skip to content
VLSI Mentor

DDR · Module 21

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.

Chapter 20.4 ended with a mechanism and an open question. The mechanism: the device samples CK with the write strobe and reports what it saw on DQ, one bit. The open question, in that chapter's own words, was how far from the boundary the final setting should sit — which 20.4 could not answer, because answering it requires a search.

This chapter is that search. It is also the first place in the curriculum where the training loop appears in full, so it carries more of the module's general machinery than the four chapters after it.

The central law, stated once and then qualified:

Training is controlled search over configurable PHY state, using observable pass/fail information to discover an operating point with usable timing margin.

And immediately: training does not create margin. It finds a setting inside margin that already exists physically. If the channel has no valid operating region, a correct algorithm must fail rather than return a number.

1. What the Algorithm Is Allowed to Know

Before any search, it is worth being precise about how little information this algorithm has.

It can set one number — a delay applied to the write strobe. It can ask for one probe. It gets back one bit. That is the entire interface, and 20.4 §8 built it deliberately narrow.

What the algorithm cannot do is worth listing, because every wrong mental model of training comes from assuming one of these:

The algorithm cannotWhy not
Measure the actual CK-to-strobe phaseNothing returns a phase. One bit returns a side.
Read a margin valueNo margin is published to the digital domain at all.
Observe the eyeThere is no eye in this procedure. The observable is a sampled logic level.
Know the physical size of one delay stepThe step is a technology-specific analog quantity (19.1 §5).
Verify its own answer independentlyThe only checker available is the same one-bit probe.

So the algorithm is not measuring anything. It is bisecting a space using a comparator, and the comparator is the device itself.

2. The Experiment Loop

Every training mechanism in this module is the same loop. It is worth naming its stages once, here, because the following four chapters vary the contents and not the structure.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   configure  ──►  stimulate  ──►  observe  ──►  classify
       ▲                                             │
       │                                             ▼
       └──────────────  update search  ◄────  commit or fail

Configure — apply a candidate setting to the configurable resource. Stimulate — cause the interface to do something observable. Observe — capture whatever the hardware reports. Classify — reduce that report to a decision. For write levelling, to one of 0 or 1. Update search — choose the next candidate, or stop. Commit or fail — install a setting, or declare no setting exists.

For write levelling the loop instantiates as follows, and this table is the one to carry into the rest of the module.

ElementWrite levelling
Controllable variableThe write strobe delay, in device-agnostic delay codes
StimulusA single strobe edge, with the device in levelling mode
ObservableThe level the device drives on DQ after tWLO
Pass criterionNot pass/fail — a side: the sampled CK level, 0 or 1
Search domainThe delay codes the hardware implements, 0 to DELAY_MAX
Stopping conditionA confirmed 0-to-1 transition, or domain exhausted
Selection policyThe first code whose filtered answer is 1, then an offset
Committed resulttrained_delay, with trained_valid asserted
Failure resulttrain_failed, trained_valid not asserted, safe state held

Note the fourth row. Write levelling is the one training step in this module whose observable is not a correctness result. Nothing here is being read back and compared. The device is acting as a phase comparator and reporting which side of CK the strobe edge landed on. 21.2 onward, the observable becomes did the data come back correct, which is a genuinely different kind of information — and §14 returns to why that distinction matters.

3. What the Standard Actually Specifies

The procedure is defined by JEDEC, and the details constrain the algorithm more than they first appear to.

Entry is a mode register write: MR1 bit A7 is Write Leveling Enable, 0 to disable and 1 to enable. Inside that mode the sequence the standard describes runs as follows.

The controller may drive the strobe low after a delay of tWLDQSEN, by which time the device has applied on-die termination to those signals. After tDQSL and tWLMRD, the controller provides a single strobe edge, which the device uses to sample CK. The standard notes that tWLMRD maximum is controller dependent — the device does not impose an upper bound on how long the controller takes.

The device then samples CK with the rising strobe edge and provides feedback on all the DQ bits asynchronously after tWLO. Two consequences follow, and both shape the RTL.

First: the feedback is not a read. The standard is explicit that no read strobes are needed for these DQ bits. There is no burst, no strobe to gate, no capture in the 20.3 sense. The feedback is a level driven on DQ, and the PHY samples it with its own clock after waiting. This is why 20.4 §7 insisted that write levelling is not read capture turned around: the read capture machinery is not involved.

Second: tWLO is nearly unbounded. For DDR4-1600, 1866 and 2133 the standard gives tWLO as minimum 0 ns and maximum 9.5 ns — the same pair at each of those grades. A window that wide is not a latency the algorithm can pipeline against; it is a settling time to wait out. This is precisely why 20.4's interface carries FEEDBACK_LATENCY as a caller-supplied parameter rather than a constant.

The standard also defines tWLOE, the write levelling output error, as the interval from the transition of the earliest DQ bit to the transition of the latest — an explicit allowance for mismatch between DQ bits. §6 is about what that allowance does to an algorithm that believes DQ.

Finally, the standard describes the search itself in one sentence worth paraphrasing carefully: the controller samples the incoming DQ bits and decides to increment or decrement the strobe delay, launching the next strobe pulse after a controller-dependent interval; once a 0-to-1 transition is detected, the controller locks the delay setting.

4. Why the Answer Is a Step Function

The device reports which side of CK the strobe edge landed on. Sweep the delay from low to high and the reported bit traces a step.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  delay code   0   1   2   3   4   5   6   7   8   9  10  11
  reported     0   0   0   0   0   0   1   1   1   1   1   1


                            the 0-to-1 transition:
                            the strobe edge has crossed CK

The transition is the whole signal. Its position is the measurement — the delay code at which the strobe edge coincides with the CK edge at that device. Because 20.4 §2 established that fly-by routing makes that coincidence occur at a different delay at every device on the module, this search runs per rank and per byte lane, and returns a different answer each time. That is not error; it is the quantity being measured.

Two things about the shape matter.

The step is monotonic in principle and not in practice. In an idealised sweep the bit is 0 below the boundary and 1 above it, once. Real sweeps show the transition smeared across two or three codes, and can show isolated flips well away from the boundary. §5 explains the first and §6 the second.

The absolute delay code is meaningless; only the transition is not. A code is an index into a chain of technology-specific delay elements. It has no defined size (19.1 §5), it is not guaranteed linear, and it does not compare across designs or even across process corners. What transfers is the code at which this device's boundary was found, in this system, at this moment.

5. Why One Probe Is Not an Answer

Here is the part that separates a working write-levelling implementation from one that appears to work.

The device samples CK using the strobe edge. That is a flip-flop, and the quantity being sampled is a clock edge. At the boundary, the setup and hold conditions of that sampling element are violated by definition — the boundary is the delay at which the two edges coincide.

So near the transition the device is not reporting a stable side. It is resolving a metastable sample, and the value it resolves to is not a function of the delay code alone. Probe the same code ten times and get a mix of 0 and 1.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   code            4     5     6     7     8
   probe 1         0     0     1     1     1
   probe 2         0     0     0     1     1
   probe 3         0     1     1     1     1
   probe 4         0     0     1     1     1
   probe 5         0     0     0     1     1
                         └─────┬─────┘
                     the uncertain band: the answer
                     here is a distribution, not a value

An algorithm that takes one probe per code and stops at the first 1 will stop somewhere inside that band, at a position that depends on which resolution it happened to catch. Run it twice and get two answers. That is the classic signature: write levelling that returns a different value on every boot, by a few codes, with no other symptom.

The fix is to stop treating the observable as a value and treat it as a sample from a distribution. Probe each candidate PROBES times and require AGREE of them to read 1 before calling the candidate a 1.

This does two things. It makes a single flip in the uncertain band unable to terminate the search, and — more usefully — it makes the width of the band observable. A candidate whose probes disagree is a candidate near the boundary, and that is information the algorithm can report even though it cannot measure a phase.

6. When the DQ Bits Disagree

tWLOE exists because the DQ bits do not all transition together. The standard defines it as earliest-transition to latest-transition and calls it an allowance for mismatch.

For an algorithm that samples DQ as a vector, this is a decision it cannot avoid making. At a candidate near the boundary, the byte lane may return 8'b0001_1111 — five bits saying one side, three saying the other.

There are only a few defensible policies, and the right one depends on what the value will be used for:

PolicyBehaviourWhen it is right
Unanimous 1Treat as 1 only if all bits agreeConservative; pushes the answer later, past the whole spread
MajorityVote across bitsRobust to one outlier bit; the common choice
Single reference bitBelieve one nominated bitMatches hardware that only wires one bit back
Any bit 1Treat as 1 if any bit is highPushes the answer earliest; rarely what is wanted

The block in §8 takes the majority across probes on an already-reduced bit, and pushes the cross-bit reduction out to the caller. That is a deliberate boundary: the number of DQ bits, whether all of them are even wired to the feedback path, and which policy the system wants are all integration facts, and a block that hard-coded one of them would be wrong in most systems that used it.

What must not happen is for the reduction to be implicit. A design that samples dq_feedback[0] because that is what the testbench drove has chosen the single-reference-bit policy without knowing it.

7. Candidate Is Not Committed

This is the discipline that makes training safe, and it is easy to get wrong because the natural implementation gets it wrong.

During the search the block must program the hardware with each candidate delay. That is not optional — the only way to observe a candidate is to apply it. So at any instant during training, the write strobe path is configured with an experimental value that has not been validated and may be badly wrong.

The normal datapath must not consume that value. It must consume a separate, committed one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   during search          cand_delay   ──►  strobe delay hardware
                                            (experimental, unvalidated)

   after commit           trained_delay ──► normal write path
                          trained_valid = 1

   after failure          trained_delay unchanged from its safe value
                          trained_valid = 0

Three rules follow, and the RTL enforces all three.

A candidate never becomes the committed value merely because it was the last one tested. A search that ends in failure has tested a final candidate, and that candidate is very often the worst setting in the domain — the far end of an exhausted sweep. Leaving it installed and simply not raising a flag is the single most dangerous shortcut available here, because the interface then appears configured.

trained_valid gates use, and it is not the same signal as train_done. A search that finished and failed is done. It is not valid. 19.5 already established this shape for phy_operational versus trained_valid, for the same reason.

On failure the committed state must be defined. Not last-candidate, not undefined — a stated safe value. The block below holds the reset value, and says so at the line.

8. The Search, in Sequence

One candidate evaluation during write levelling, across five participants: the training agent, the write levelling search engine of this chapter, the probe interface of chapter twenty point four, the strobe delay hardware, and the DRAM. The agent starts the search. The engine applies a candidate delay code to the strobe delay hardware, which is a technology-specific resource that merely accepts the code. The engine then requests a probe from the probe interface. The interface drives a single strobe edge to the DRAM. The DRAM samples the clock with that strobe edge and, after the write levelling output delay, drives the sampled level back on all its DQ pins asynchronously, with no read strobe. The probe interface samples that level after its caller-supplied feedback latency and returns one bit to the engine. The engine records the bit as one vote and repeats the whole probe two more times at the same candidate, because a single sample near the boundary is a metastable resolution rather than a value. Having collected three votes it classifies the candidate as a zero or a one, compares that with the previous candidate's classification, and either steps to the next code or, on a confirmed zero to one transition, commits a trained delay and asserts trained valid. Two notes mark the boundaries: the delay code has no defined physical size, and the engine never installs a candidate as the committed value.One candidate: apply, probe, repeat, classifyAgentWL searchProbe 20.4Delay HWDRAMstartapply cand_delayprobe_reqone strobe edgesample CK with thestrobelevel on all DQ,after tWLOprobe_done + one bitvote 1 of PROBESrepeat at the samecandidateclassify: 0 or 1commit:trained_delay, valid

Two boundaries are crossed in that diagram and neither is visible in it. The apply at message two hands a code to a resource whose step size is a technology-specific analog quantity (19.1 §5) — the engine is choosing an index, not a delay. And the commit at the final message is the only point at which anything the search touched becomes production configuration; the nine messages before it all operate on an experimental value.

The repeat at message nine is the part most implementations omit and then rediscover as a boot-to-boot variance bug.

9. The Search Engine

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// wl_search_engine -- write-levelling search over a one-bit observable.
//
// CLASSIFICATION: educational, synthesisable. Drives the probe
// interface of Chapter 20.4 (write_level_feedback_if) and implements
// the algorithm that chapter deliberately excluded.
//
// WHAT IT DOES NOT MODEL:
//   - any delay element, phase, or analog quantity
//   - the DRAM's sampling of CK, or its metastability near the
//     boundary (§5) -- that behaviour is on the far side of the probe
//   - tWLO / tWLOE, which the probe interface absorbs
//   - the cross-DQ-bit reduction of §6, which is the caller's policy
//
// The observable is one bit. Everything physical is outside.
// ---------------------------------------------------------------------
module wl_search_engine #(
  // Width of a delay code. A code is an index into a technology-
  // specific delay resource; it has no defined physical size.
  parameter int DELAY_W   = 6,
  // Inclusive top of the search domain. The hardware may implement
  // fewer codes than DELAY_W can express, so this is separate.
  parameter int DELAY_MAX = 63,
  // Probes taken per candidate, and votes needed to call it a 1.
  // PROBES > 1 is what makes the §5 uncertain band survivable.
  parameter int PROBES    = 3,
  parameter int AGREE     = 2,
  // A COUNT of votes up to PROBES needs clog2(PROBES+1), not
  // clog2(PROBES): three votes need two bits to express the value 3.
  parameter int VOTE_W    = (PROBES <= 1) ? 1 : $clog2(PROBES + 1),
  // Where the committed value sits relative to the boundary. The
  // boundary is the first code reporting 1; a real system offsets
  // past it. This is a POLICY value -- see §11.
  parameter int COMMIT_OFFSET = 0
) (
  input  logic                 clk,
  input  logic                 rst_n,

  // ── From the training sequencer (21.5 owns the ordering).
  input  logic                 start,

  // ── The device must already be in levelling mode. This block does
  //    not enter or exit that mode: mode entry is an MRS command and
  //    belongs to whatever issues commands, not to a search engine.
  input  logic                 wl_mode_active,

  // ── To Chapter 20.4's probe interface.
  output logic [DELAY_W-1:0]   delay_setting,
  output logic                 probe_req,

  // ── From Chapter 20.4's probe interface. ONE bit per probe, already
  //    reduced across DQ bits by the caller's §6 policy.
  input  logic                 probe_done,
  input  logic                 probe_result,

  // ── THE COMMITTED RESULT. Consumed by the normal write path only
  //    when trained_valid is high (§7).
  output logic [DELAY_W-1:0]   trained_delay,
  output logic                 trained_valid,

  // ── Completion. done and valid are DIFFERENT signals: a search that
  //    finished and failed is done, and is not valid.
  output logic                 train_done,
  output logic                 train_failed,

  // ── Observability. cand_delay is the EXPERIMENTAL setting; it is
  //    published so a checker can prove it never leaks into the
  //    committed value.
  output logic [DELAY_W-1:0]   cand_delay,
  output logic [VOTE_W-1:0]    votes,
  output logic [2:0]           state_out,
  // Asserted for a candidate whose probes did not agree unanimously:
  // the §5 uncertain band, made visible. Not an error.
  output logic                 near_boundary,

  output logic                 err_probe_outside_mode,
  output logic                 err_done_while_busy
);

  // ── Elaboration guards. A misparameterised search is not a subtle
  //    bug: AGREE > PROBES can never be satisfied, so the machine
  //    would classify every candidate as 0 and always fail.
  initial begin
    if (AGREE > PROBES)
      $fatal(1, "wl_search_engine: AGREE (%0d) exceeds PROBES (%0d)", AGREE, PROBES);
    if (AGREE < 1)
      $fatal(1, "wl_search_engine: AGREE must be at least 1");
    if (DELAY_MAX > (2**DELAY_W - 1))
      $fatal(1, "wl_search_engine: DELAY_MAX (%0d) exceeds DELAY_W", DELAY_MAX);
    if (COMMIT_OFFSET < 0)
      $fatal(1, "wl_search_engine: COMMIT_OFFSET must not be negative");
  end

  typedef enum logic [2:0] {
    S_IDLE   = 3'd0,
    S_ARM    = 3'd1,
    S_PROBE  = 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 [DELAY_W-1:0] cand;
  logic [VOTE_W-1:0]  ones;          // probes at this candidate reading 1
  logic [VOTE_W-1:0]  taken;         // probes taken at this candidate
  logic               prev_class;    // previous candidate's classification
  logic               prev_valid;    // is prev_class meaningful yet?
  logic [DELAY_W-1:0] committed;
  logic               committed_ok;

  // Classification of the candidate just finished.
  logic this_class;
  assign this_class = (ones >= VOTE_W'(AGREE));

  // A candidate is "near the boundary" when its probes disagreed.
  assign near_boundary = (state == S_CLASS) && (ones != '0) && (ones != VOTE_W'(PROBES));

  // The transition we are searching for: previous candidate read 0,
  // this one reads 1. prev_valid excludes the first candidate, which
  // has no predecessor -- see §12 for why that case is a FAILURE and
  // not a commit.
  logic transition_found;
  assign transition_found = prev_valid && (prev_class == 1'b0) && (this_class == 1'b1);

  logic domain_exhausted;
  assign domain_exhausted = (cand >= DELAY_W'(DELAY_MAX));

  always_comb begin
    next = state;
    unique case (state)
      S_IDLE   : if (start && wl_mode_active) next = S_ARM;
      // ARM applies the candidate and gives the delay resource a cycle
      // to take it before any edge is driven.
      S_ARM    : next = S_PROBE;
      S_PROBE  : if (probe_done)
                   next = (taken + VOTE_W'(1) >= VOTE_W'(PROBES)) ? S_CLASS : S_PROBE;
      S_CLASS  : if (transition_found)      next = S_COMMIT;
                 else if (domain_exhausted) next = S_FAIL;
                 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;
      ones         <= '0;
      taken        <= '0;
      prev_class   <= 1'b0;
      prev_valid   <= 1'b0;
      // THE SAFE STATE. On failure the committed value holds this,
      // not the last candidate tested (§7). Zero is chosen because it
      // is the domain's defined start, and because it is wrong in an
      // obvious direction rather than a plausible one.
      committed    <= '0;
      committed_ok <= 1'b0;
    end else begin
      state <= next;

      unique case (state)
        S_IDLE : if (start && wl_mode_active) begin
          // A new search discards every previous result, including a
          // previously committed one: the relationship it measured may
          // no longer hold (19.5 §4).
          cand         <= '0;
          ones         <= '0;
          taken        <= '0;
          prev_valid   <= 1'b0;
          prev_class   <= 1'b0;
          committed_ok <= 1'b0;
        end

        S_PROBE : if (probe_done) begin
          taken <= taken + VOTE_W'(1);
          if (probe_result) ones <= ones + VOTE_W'(1);
        end

        S_CLASS : begin
          prev_class <= this_class;
          prev_valid <= 1'b1;
          ones       <= '0;
          taken      <= '0;
        end

        S_STEP : cand <= cand + DELAY_W'(1);

        S_COMMIT : begin
          // The boundary is the FIRST code classified 1, which is the
          // candidate just classified. The offset is added here and
          // saturated, because a policy offset must never wrap the
          // code into the bottom of the domain.
          committed    <= (cand + DELAY_W'(COMMIT_OFFSET) > DELAY_W'(DELAY_MAX))
                            ? DELAY_W'(DELAY_MAX)
                            : cand + DELAY_W'(COMMIT_OFFSET);
          committed_ok <= 1'b1;
        end

        // S_FAIL deliberately writes nothing: committed and
        // committed_ok keep the values they already hold.
        default : ;
      endcase
    end
  end

  // The experimental setting goes to the hardware. The committed one
  // never does -- the normal path reads trained_delay.
  assign cand_delay     = cand;
  assign delay_setting  = cand;
  assign probe_req      = (state == S_PROBE) && !probe_done;

  assign trained_delay  = committed;
  assign trained_valid  = committed_ok;
  assign train_done     = (state == S_COMMIT) || (state == S_FAIL);
  assign train_failed   = (state == S_FAIL);

  assign votes          = ones;
  assign state_out      = state;

  // Probing outside levelling mode is a sequencing error by 21.5, and
  // the resulting bit would be a normal read, not a phase report.
  assign err_probe_outside_mode = probe_req && !wl_mode_active;
  // done must not be claimed while the machine is still mid-candidate.
  assign err_done_while_busy    = train_done && (taken != '0);

endmodule

10. A Search, Cycle by Cycle

Three candidates, one transition, one commit

10 cycles
Ten cycles covering the end of a write levelling search, with one probe per candidate shown for legibility rather than the three the parameters default to. The clock runs throughout. At the first cycle the candidate delay code is four and a probe request is raised; the probe completes two cycles later returning zero, so code four is classified as below the boundary. The candidate steps to five, is probed, and also returns zero. The candidate steps to six, is probed, and returns one. Because the previous candidate was classified zero and this one is classified one, a zero to one transition is confirmed and the machine commits. The trained delay register takes the value six and trained valid asserts on the final cycle. Before that cycle trained valid is low throughout, which is the point: the candidate delay is being applied to the hardware the whole time while the committed value stays untouched. Nothing in this diagram indicates how much margin the committed code has.code 4 reports 0 — below the boundarycode 4 reports 0 — belowthe boundaryfirst 1 — the boundary is locatedfirst 1 — the boundary islocatedcommit: candidate becomes trainedcommit: candidate becomestrainedCKprobe_reqcand_delay4445556666probe_doneprobe_resulttrained_validtrained_delay0000000006t0t1t2t3t4t5t6t7t8t9

The row to watch is trained_valid. It is low for nine of ten cycles while cand_delay is being driven into the strobe path. During that whole interval the write path is configured with an unvalidated value and the committed value is untouched — which is §7 made concrete.

11. Where the Final Value Sits

The boundary is not the answer. It is the worst place to operate: it is by definition the delay at which the device's sampling of CK is marginal.

20.4 §16 left this question open, and 20.5 supplied the missing half — the published window figures that say how much there is to spend. The selection policy has to place the committed value away from the boundary, and COMMIT_OFFSET is where that policy lives.

What the offset cannot be is computed by this block, and the reason is §4: a delay code has no defined physical size. An offset of two codes might be a large fraction of a bit interval or a negligible one. So the offset is a parameter supplied by whoever integrated the delay resource and knows its granularity.

What the algorithm can contribute is the uncertain band from §5. A candidate whose probes disagreed is near the boundary; the width of the disagreeing region is a code-space measure of how sharp the transition is, and a system that records it has a genuinely useful diagnostic — not a margin, but a repeatable number that changes when the channel changes.

12. Failure Is a Result

A search that finds no transition must fail. Three distinct situations produce that, and conflating them costs real debug time.

No transition anywhere: all codes report 0. The strobe edge never reaches CK within the domain. Either the delay range is too short for the board's skew, or the strobe is not arriving at all. Distinguishable from the next case by the constant 0.

No transition anywhere: all codes report 1. The strobe edge is already past CK at code zero. The domain starts too late. Note that a naive implementation searching for a 0-to-1 transition cannot commit here even though every probe passes, and that is correct behaviour: the boundary is outside the searchable range, so its position is unknown. The RTL's prev_valid term is what enforces this — the first candidate has no predecessor and therefore cannot itself constitute a transition.

A transition, but not a usable one. Multiple transitions across the sweep, or a transition whose uncertain band is very wide. The algorithm above stops at the first one and would take a spurious flip as the boundary; a production implementation continues the sweep and requires the 1 region to be contiguous to the top of the domain. That refinement belongs to the same family as 21.2 §10's fragmented-region handling.

In all three cases trained_valid stays low and the committed value holds its safe reset value. The interface is not configured, and it does not pretend to be.

13. What the Assertions Prove

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind unit note: these properties reference wl_search_engine's
// internal state and are written as though bound into the module, so
// they see clk and rst_n directly.

// P1 -- the central safety property of §7. The committed value only
// ever changes on a commit. If this holds, no candidate can leak into
// the production setting by any path.
property p_commit_only_on_commit;
  @(posedge clk) disable iff (!rst_n)
    (trained_delay != $past(trained_delay, 1))
      |-> ($past(state_out, 1) == 3'd5);
endproperty
assert property (p_commit_only_on_commit);

// P2 -- validity is never claimed by a failed search.
property p_fail_never_valid;
  @(posedge clk) disable iff (!rst_n)
    train_failed |-> !$rose(trained_valid);
endproperty
assert property (p_fail_never_valid);

// P3 -- done and valid are different signals, and this is the case
// that proves it: a done cycle may carry either polarity of valid.
property p_done_is_not_valid;
  @(posedge clk) disable iff (!rst_n)
    train_done |-> (train_failed != trained_valid);
endproperty
assert property (p_done_is_not_valid);

// P4 -- the terminating condition actually held. A commit implies the
// classification just made was 1 and its predecessor was 0. Written
// over a two-cycle window because both terms are sampled in S_CLASS,
// the cycle before S_COMMIT is entered.
property p_commit_implies_transition;
  @(posedge clk) disable iff (!rst_n)
    ($past(state_out, 1) == 3'd3) && (state_out == 3'd5)
      |-> $past(transition_found, 1);
endproperty
assert property (p_commit_implies_transition);

// P5 -- no probe is ever requested outside levelling mode. The error
// output is the mechanism; this proves it is never needed.
property p_probe_inside_mode;
  @(posedge clk) disable iff (!rst_n)
    probe_req |-> wl_mode_active;
endproperty
assert property (p_probe_inside_mode);

// P6 -- every candidate gets the full probe count. This is the §5
// property: a classification that used fewer probes than PROBES would
// be a single-sample decision wearing a filter's clothing.
property p_full_probe_count;
  @(posedge clk) disable iff (!rst_n)
    (state_out == 3'd3) |-> ($past(taken, 1) == VOTE_W'(PROBES));
endproperty
assert property (p_full_probe_count);

// P7 -- the search only ever moves up. A sweep that could step
// backwards could revisit a code and terminate on a different
// resolution of the same metastable sample.
property p_monotonic_sweep;
  @(posedge clk) disable iff (!rst_n)
    (cand_delay != $past(cand_delay, 1)) && (cand_delay != '0)
      |-> (cand_delay == $past(cand_delay, 1) + 1);
endproperty
assert property (p_monotonic_sweep);

// ── Cover: the states worth knowing were reached.
cover property (@(posedge clk) disable iff (!rst_n) train_failed);
cover property (@(posedge clk) disable iff (!rst_n) $rose(trained_valid));
// The §5 band was actually exercised -- probes disagreed at some
// candidate. A regression that never covers this has not tested the
// filter at all.
cover property (@(posedge clk) disable iff (!rst_n) near_boundary);
// Commit at the top of the domain: the offset saturation path.
cover property (@(posedge clk) disable iff (!rst_n)
                  (state_out == 3'd5) && (cand_delay == DELAY_W'(DELAY_MAX)));
// A failure with every probe reading 1 -- §12's second case, which is
// the one most likely to be mistaken for success.
cover property (@(posedge clk) disable iff (!rst_n)
                  train_failed && $past(probe_result, 1));

P1 is the property worth keeping if only one survives review. It is a complete statement of the candidate/committed separation, and it fails loudly for the most tempting wrong implementation — assigning trained_delay <= cand inside the sweep and setting a flag at the end.

P6 is the one most likely to be omitted, and it is the guard against §5's failure mode being reintroduced by a later optimisation. An engineer shortening the search by exiting early on a confident-looking probe breaks P6 immediately.

14. The Training State Machine

The write levelling search state machine, with seven states. The machine starts in idle. On a start request with levelling mode already active it moves to arm, where the candidate delay code is applied to the delay resource and given a cycle to settle. From arm it moves to probe, which requests probes from the chapter twenty point four interface and counts the returned bits; probe loops on itself until the full probe count for this candidate has been collected. It then moves to classify, where the collected votes are reduced to a single zero or one and compared against the previous candidate's classification. Classify has three exits. On a confirmed zero to one transition it moves to commit, which is the only state that writes the trained delay register and asserts trained valid. If the search domain is exhausted without a transition it moves to fail, which deliberately writes nothing so the committed value holds its safe reset state. Otherwise it moves to step, which advances the candidate code and returns to arm for the next candidate. Both commit and fail return to idle, and both assert train done, but only commit asserts trained valid.IDLEARMPROBECLASSIFYSTEPCOMMITFAILstart, mode activestart, mode activecandidate appliedcandidate appliedvote taken, more neededvote taken,more neededPROBES votes collectedPROBES votes collectedPROBESvotes…0 to 1 confirmed0 to 1 confirmeddomain exhausteddomain exhaustedno transition yetno transition yetnext codenext codetrained_valid heldtrained_valid heldsafe state heldsafe state held

Two structural points, which recur in every training machine in this module.

PROBE has a self-loop and CLASSIFY does not. The repeat lives inside the observation stage, not around the whole loop. That is what makes the filter a filter over one candidate rather than an average across candidates — §5's distinction, in the state graph.

COMMIT and FAIL are separate states rather than one done state with a flag. They behave differently: one writes the committed registers, the other writes nothing. Merging them and distinguishing by a flag is how the §7 failure gets built by accident.

15. Corner Cases

CaseBehaviourWhy
start while already searchingIgnored — S_IDLE is the only state that accepts itA restart mid-sweep would mix votes from two searches
wl_mode_active drops mid-searcherr_probe_outside_mode fires on the next requestThe bit returned would be array data, not a phase report
Boundary at code 0Fails, does not commitNo predecessor, so no transition is observable — §12
Boundary at DELAY_MAXCommits, offset saturatesSaturation is explicit so a policy offset cannot wrap
PROBES = 1, AGREE = 1Legal, and reintroduces §5's bugPermitted deliberately: the parameters make the filter's presence a visible choice
AGREE > PROBES$fatal at elaborationUnsatisfiable: every candidate would classify 0 and every search would fail
All probes read 1 throughouttrain_failed§12's second case — passing probes, unknown boundary
probe_done with no requestNot counted; S_PROBE gates on its own requestA stray completion must not become a vote
Re-run after a successful searchPrevious result discarded at S_IDLEThe measured relationship may have drifted — 19.5 §4

The third row is the one that surprises people. A working interface whose boundary sits at the bottom of the delay range trains unsuccessfully, and that is right: the algorithm has not located a boundary, it has merely failed to see one below where it started.

16. DV — Model the Device, Not the Engine

A checker for this block must not re-implement the sweep, because then it agrees with the engine's bugs. What it should do is model the device and independently confirm the property the search claims.

The device model is small: pick a hidden boundary code, and return 1 for any probe at or above it, 0 below — with a band of width w around it where the returned bit is random.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Independent device model. Knows the answer; the engine does not.
// SIMULATION-ONLY -- it contains a random resolution, and nothing
// with a random output belongs in synthesisable RTL.
class wl_device_model;
  int unsigned boundary;      // hidden truth
  int unsigned band;          // width of the metastable region (§5)

  function bit probe(int unsigned code);
    if (code >= boundary + band)      return 1'b1;
    else if (code + band < boundary)  return 1'b0;
    // Inside the band the device resolves a marginal sample. The
    // model returns a coin flip, which is the honest representation:
    // the value is not a function of the code.
    else                              return bit'($urandom_range(1));
  endfunction
endclass

The checks the testbench then makes are about the relationship between the hidden truth and the committed value, not about the sweep:

CheckWhat it establishes
trained_valid implies abs(trained_delay − boundary) <= band + COMMIT_OFFSETThe engine landed near the real boundary, within the uncertainty the device actually has
Run the same model twice with different seeds; committed values differ by at most bandThe filter suppressed §5's variance instead of inheriting it
Set boundary = 0; expect train_failed§12's boundary-at-zero case, which a naive engine commits
Set boundary > DELAY_MAX; expect train_failed and constant 0 probes§12's first case
Set band wider than the domain; expect train_failed or a near_boundary report at every candidateA channel with no usable region must not yield a confident answer
Count probes; expect exactly PROBES per candidate visitedIndependent confirmation of P6

The second check is the one that catches the real bug. A single-probe engine passes the first check most of the time — it does land near the boundary — and fails the second immediately, because its answer moves with the seed. That is the boot-to-boot variance of §5, reproduced deterministically in a testbench, which is the only comfortable place to meet it.

17. Debugging

SymptomLikely causeHow to confirm
Trained value differs by a few codes every bootPROBES = 1, or an early exit — §5Log the per-candidate vote counts; a healthy log shows a disagreeing band
Trained value differs by many codes every bootA spurious flip is being taken as the boundary — §12Log the full sweep; look for a 1 with 0s above it
train_failed with every probe reading 1Boundary below the domain — §12Check the constant 0/1 distinction in the sweep log
train_failed with every probe reading 0Strobe not arriving, or domain too shortConfirm levelling mode was actually entered (MR1 A7)
Trains successfully, writes still failOnly the CK relationship was trained — §11The data-to-strobe requirement is a separate search, 21.4
Trains differently per byte lane by a large amountExpected on a fly-by module — 20.4 §2Compare against the module's routing, not against the other lanes
Trains differently per rankExpected; ranks are different loads and positionsPer-rank storage is required, not optional
err_probe_outside_mode during a sweepSequencing bug in 21.5Mode entry and search start are separate events
Sweep never terminatesprobe_done never returnsThe probe's FEEDBACK_LATENCY is shorter than the real tWLO

The fifth row is the most common real-world confusion, and it comes from believing write levelling trains “the write path.” It trains one of two independent requirements.

The last row is worth dwelling on: tWLO has a maximum of 9.5 ns and a minimum of 0, so a FEEDBACK_LATENCY chosen from a simulation model rather than from the device's datasheet is a plausible way to build an interface that works on one part and hangs on another.

18. Misconceptions

“Write levelling measures the CK-to-DQS skew.” It locates a boundary in code space. The physical skew is never produced as a number by anything in the procedure, and a delay code has no defined size (§4).

“The device tells you whether the alignment is correct.” It tells you which side of CK the strobe edge landed on. That is a comparator output, not a verdict. A 1 does not mean “good.”

“One probe per delay setting is enough.” §5. Near the boundary the device is resolving a metastable sample and the answer is a distribution.

“If every probe passes, training succeeded.” §12's second case: all-1 means the boundary is below the searchable range, so its position is unknown. This is a failure that looks maximally like success.

“Training creates margin.” It finds a setting within margin that already exists. If no valid region exists, the correct outcome is failure.

“The last tested setting can stay installed if training fails.” §7. It is usually the far end of an exhausted sweep — the worst code in the domain.

“It is one algorithm, so every PHY does it the same way.” §3: the standard fixes the mode, the stimulus, the feedback and the terminating condition. Step size, sweep direction, probe count, cross-bit policy and final offset are all open.

“Write levelling is read capture in reverse.” 20.4 §7 refuted this, and §3 gives the standard's own reason: the feedback needs no read strobe, so the capture path is not involved.

“One trained value serves the whole module.” Fly-by makes the boundary per-device. The search runs per rank and per byte lane, and the answers legitimately differ.

19. Interview Reasoning

Why does the device sample CK with DQS rather than the other way round? Because the quantity in question is where the controller's strobe lands relative to the device's clock, and only the device can observe that. Sampling CK with DQS puts the comparator on the side that has both signals in their arrived form.

Why is one probe insufficient? The comparator is a flip-flop sampling a clock edge, and at the boundary its setup and hold conditions are violated by construction. The output there is a metastable resolution, so the observable is a random variable whose distribution shifts across the band.

You find the transition at code 6. Why not commit 6? Because 6 is where the device's sampling became marginal — the worst operating point found. The commit must sit away from it, by an offset whose size only the delay resource's integrator can choose, since a code has no defined physical size.

Every probe at every code returns 1. Has training passed? No. It means the boundary lies below the domain's start, so its location is unknown. Committing here would install code 0 as though it had been validated.

What is the difference between train_done and trained_valid? done says the search terminated. valid says it terminated with a usable answer. A failed search is done and not valid, and the normal datapath must gate on valid.

Why must the candidate setting be applied to the hardware during the search? Because applying it is the only way to observe it. Which is exactly why the committed value has to be a separate register — during the whole search the live configuration is unvalidated.

Two boards, same design; one trains to code 12 and the other to code 31. Which is broken? Neither, necessarily. The boundary is a property of the routing, the load and the device position. A large difference is a question for the layout, not a training bug. What would indicate a bug is the same board training to two different codes on consecutive boots.

Why does this procedure say nothing about whether writes will work? It trains one of the write direction's two independent requirements — strobe to CK. The strobe's placement within the write data is a different relationship, trained by a different search with a different observable.

20. Exercises

  1. Set PROBES = 1 and AGREE = 1 and run §16's model with band = 3 across twenty seeds. Record the committed value each time. Then set PROBES = 5, AGREE = 3 and repeat. Report the spread in both cases, and explain which check in §16's table distinguishes them.

  2. The RTL fails when the boundary is at code 0. Modify it to report that case distinctly — an error output separate from train_failed — without allowing it to commit. Why is a distinct report more useful than a generic failure here?

  3. COMMIT_OFFSET saturates at DELAY_MAX. Argue for and against instead treating offset-past-the-top as a failure. Which choice would you defend in a product, and what does the answer depend on?

  4. Write the property that P7 (monotonic sweep) would need if the engine were changed to search downward from DELAY_MAX. Then explain what P4 would have to become, given that the terminating condition is no longer a 0-to-1 transition.

  5. The engine takes an already-reduced probe_result. Write the cross-bit reduction for the majority policy of §6 as a separate module, with an output that flags when the bits disagreed. Why does making the disagreement visible matter more here than the reduction itself?

  6. Extend §16's device model so that the boundary drifts by one code every 10,000 probes. Which of the table's checks begins to fail, and what does that tell you about how often a trained value should be revisited? Relate the answer to 19.5 §4.

  7. A colleague proposes speeding up the search with a binary search over the delay domain instead of a linear sweep. Identify the assumption binary search requires, and use §4 and §5 to explain when that assumption fails. Under what added condition would binary search be safe?

  8. The chapter claims no assertion in §13 can establish margin. Construct the strongest property you can that uses only the module's outputs and would fail for a committed setting with no margin. Then explain why it cannot exist, in terms of the three columns of §1's callout.

21. Where This Goes

The loop is built. A controllable variable, a stimulus, a one-bit observable, a filter over repeats, a terminating condition, a committed result and a defined failure — and a state machine whose shape the rest of the module reuses.

What made this chapter the simplest of the five is the observable. The device told us a side. One bit, directly reporting the quantity of interest, from a procedure the standard defines.

The remaining searches do not get that. Their observable is did the data come back correct, which is a much weaker signal: it conflates every possible cause of corruption into a single bit, it depends on the pattern used to produce it, and a pass does not localise anything. Chapter 21.2 takes up the read side with that weaker observable, and has to find two edges rather than one transition — which is where the pass-region map, and the question of what to do when the region is not contiguous, first become the central problem.

21.3 then searches for something the standard does not define at all.

Continue learning

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.