Skip to content
VLSI Mentor

DDR · Module 22

Reflections

A reflection is energy that had nowhere to go. It returns after a round trip, so it corrupts a later bit than the one that caused it — and that single fact explains why a training sweep finds a narrow region.

Chapter 22.1 built termination down to the cycle: three selectable values, a dynamic switch scoped to one rank, a latency tied to write latency. And it ended by admitting that every one of those decisions is a name — that no signal anywhere in two blocks and nine properties carries an impedance, and the controller cannot even determine the absolute value of the ratio it selected.

So the obvious question was left open. Termination absorbs something. What?

This chapter answers it, and it is the chapter in this module where the RTL has the least to say and the reasoning has the most. It is also where the narrow, fragmented pass regions Module 21 spent five chapters measuring finally acquire a cause.

The module's central law, with this chapter's qualification:

A DDR signal arrives degraded by effects that are electrical, not logical. Termination, encoding and protection do not remove those effects — they bound them, detect them, or trade one for another.

For reflections the qualification is sharp: termination does not cancel a reflection. It reduces the fraction of energy that returns. There is exactly one configuration in which nothing returns at all, §2 shows it is a single exact point rather than a region, and no real channel sits on it.

1. A Wave With Nowhere To Go

Start with what a signal on a DDR channel actually is, because the digital picture actively misleads here.

A digital designer thinks of a net as a node: the driver sets it, everything attached sees the new value. That model is adequate when the signal's rise time is long compared with the time it takes to traverse the net. On a DDR channel it is not. A transition takes a finite time to travel from the driver to the far end, and during that interval different points on the net are genuinely at different voltages.

While the transition is travelling, the driver is not charging a capacitor. It is launching energy into a structure that presents a characteristic impedance — a ratio of voltage to current that the geometry of the conductor and its return path establish. The driver sees that impedance, not the far-end load, for as long as the round trip takes.

Now the transition reaches something whose impedance is different. At that boundary there is a problem, and it is a conservation problem rather than an electrical subtlety:

The incoming wave carries a particular ratio of voltage to current. The structure ahead can only support a different ratio. Both cannot be satisfied at once, and no energy is allowed to vanish. So part of the wave continues forward and part of it turns around.

That returning part is the reflection. It is not noise, it is not interference from another signal, and it is not a fault. It is the original signal's own energy, coming back, because the channel could not accept all of it in the direction it was sent.

2. The Coefficient, and the One Exact Case

How much comes back is not a matter of judgement. For a wave travelling in a structure of characteristic impedance Z0 meeting a load ZL, the fraction of the voltage wave that reflects is a standard transmission-line result:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                    ZL - Z0
   reflection  =   ─────────
   coefficient      ZL + Z0

Expressed as a ratio r = ZL / Z0 so no absolute impedance is asserted, the values are exact:

LoadrCoefficientFraction returned
Open — an unterminated pininfinite1+100%
Four times Z043/5+60%
Twice Z021/3+33.3%
Matched100% — nothing returns
Half Z01/2−1/3−33.3%
A quarter of Z01/4−3/5−60%
Short to the return path0−1−100%

Three things in that table are worth extracting, and each one has a design consequence.

The sign matters as much as the magnitude. A positive coefficient returns energy of the same polarity, so a reflection from a high-impedance discontinuity adds to whatever the receiver is trying to resolve. A negative coefficient subtracts. Both are corrupting; they corrupt in opposite directions, which is why a channel's failures are not symmetric about the ideal level and why 21.4 §13's tilted pass regions exist.

An unterminated pin returns everything. The coefficient is exactly 1. This is not an approximation for a mildly mismatched load — it is the limit, and it is why §5's claim about idle devices is stronger than it first sounds.

Matched is the only exact case, and it is a point rather than a region. r = 1 gives exactly 0. Every other value of r, however close, returns something. A load within a few percent of Z0 returns a few percent, which is small and is not zero — and §3 explains why a few percent arriving at the wrong moment is not negligible.

3. Why It Arrives Late — and Attacks a Different Bit

This is the most important section in the chapter, and the one that converts reflections from a physics curiosity into a protocol problem.

A reflection has to travel. It leaves the driver, reaches the discontinuity, turns around, and comes back. So it arrives at the receiver after a round trip — not instantly, and not during the transition that launched it.

Measure that round trip in unit intervals, the time occupied by one transferred bit. Call it k.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   bit N launched

      │  travels to the discontinuity

   reflects

      │  travels back

   arrives at the receiver during bit N+k

The energy from bit N lands on top of bit N+k. Not on bit N. The bit that caused the reflection has already been resolved and is long gone; the victim is a later bit, and which later bit depends on a physical distance the protocol knows nothing about.

Four consequences follow, and together they explain most of what Module 21 observed without explaining.

The corruption is data-dependent. Whether bit N+k is helped or hurt depends on what bit N was and what bit N+k is. A long run of identical bits builds up a reflection that reinforces itself; an alternating pattern produces reflections that partly cancel. This is why 21.2 §5's four MPR patterns have graded transition densities of seven, three, one and zero, and why a pattern that passes proves less than it appears to.

It is therefore not random, and not repeatable either. For a fixed data pattern the reflection is deterministic. Change the pattern and it changes. That is why a training sweep can find a clean, stable pass region with one stimulus and a different region with another — and why 21.2 §16 had to say that a wide region is evidence rather than measurement.

Multiple discontinuities produce multiple arrivals at different k. Every mismatch on the channel has its own round trip. A receiver therefore sees a sum of delayed copies of past bits, each scaled by its own coefficient and each arriving at its own offset. There is no single reflection to reason about.

And k is what decides whether anything can be done about it. Chapter 22.4 covers DDR5's equaliser, which is specified as four taps, each corresponding to one unit interval, correcting up to four unit intervals after the current received bit. That number is not arbitrary — it is a statement about which round trips are reachable:

Round tripCorruptsWithin a four-tap equaliser's reach
k = 1 UIbit N+1yes
k = 2 UIbit N+2yes
k = 3 UIbit N+3yes
k = 4 UIbit N+4yes
k = 5 UIbit N+5no
k = 6 UIbit N+6no

So a discontinuity's distance decides whether it is correctable. Reflections from close discontinuities — package, via, the device's own pins — return within a few unit intervals and are addressable. Reflections from far ones return outside the equaliser's memory and are not. That is a board-layout conclusion derived from a receiver specification, and it is the cleanest example in this module of an electrical property setting a digital design limit.

4. Where the Discontinuities Are

A DDR channel is not a uniform structure with one load at the end. It is a sequence of different structures, and every junction between two of them is a discontinuity.

LocationWhy the impedance changes
Driver outputThe driver's own impedance is a programmed value (22.1 §2: RZQ/7 or RZQ/5), matched to the board only approximately
Controller package and its viasA different geometry from the board trace, over a short distance
Board traceThe reference structure — this is the Z0 everything else is compared against
ConnectorA DIMM socket is a mechanical joint with its own geometry
Module traceA different board, different stack-up, different Z0
Branch pointsWhere the net divides to reach several devices — §5
Device package and viasAgain a different geometry
Device pinsWhere the presented termination — or its absence — lives

Two observations about that list.

The board trace is the reference, not the truth. Z0 in §2's table is whatever the trace presents. Everything else is compared against it, which means “matched” is defined relative to a structure that itself varies with stack-up, manufacturing tolerance and temperature.

Most of the discontinuities are unavoidable. A connector has to exist for a module to be removable. A package has to exist for a die to be attached. Vias have to exist to change layers. The design question is never “remove the discontinuities” — it is “keep each one small, and manage the energy the remaining ones return.”

5. Every Idle Device Is a Stub

Here is where §2's exact-1 result does its work, and where 22.1 §1's claim about RTT_PARK stops being a configuration detail and becomes the chapter's most practical consequence.

A stub is a branch off the main path that goes somewhere and stops. Electrically it is a short transmission line with an open far end — and an open has a reflection coefficient of exactly 1.

Every device attached to a shared net, at every moment it is not terminating, is a stub:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                 main path
   driver ───┬──────────┬──────────┬────────► far end
             │          │          │
           device     device     device
             │          │          │
         terminating  PARKED    UNTERMINATED
             │          │          │
          absorbs    absorbs    reflects
                      less      EVERYTHING

The rightmost branch is the problem. Its pins present an open, its coefficient is 1, and the energy that goes down that branch comes back undiminished — delayed by the round trip to that device and back, arriving on some later bit per §3.

This is the concrete reason RTT_PARK exists and the reason 22.1 called it the value presented most of the time. A channel with park disabled has, during every idle interval on every idle rank, exactly the structure drawn on the right above.

6. What Termination Actually Does

With §2 and §3 in place, what termination does can be stated precisely — and it is narrower than the word suggests.

Termination presents an impedance at a discontinuity so that the coefficient there is small. That is all. It does not cancel a reflection, it does not remove energy from the channel, and it does not make the channel uniform. It converts energy that would have returned into energy dissipated at the termination.

Three precise statements follow, and each corrects a common overstatement.

Termination reduces the coefficient; it does not zero it. §2 showed r = 1 is the only exact case. A real termination is a programmed ratio against a calibrated reference (22.1 §3) attached through a package to a net whose Z0 varies. It gets r near 1, not at it.

Termination at the wrong place does nothing useful. The coefficient is a property of a junction. Terminating at device A does not reduce the reflection from device B's open pins. This is why 22.1 §10's multi-rank policy terminates non-target ranks: each rank's termination manages its own branch.

Stronger is not better. Chapter 22.1 §12 established the trade: a smaller termination resistance absorbs more but loads the driver more, reducing the amplitude the receiver has to work with. §2's table shows why the optimum is interior: overshooting r = 1 downward gives a negative coefficient, so over-termination reflects too, with inverted sign.

That last point is worth stating as a table, because “more termination is safer” is a genuinely common and genuinely wrong instinct:

TerminationrCoefficientWhat returns
Noneinfinite+1Everything, same polarity
Too weak2+1/3A third, same polarity
Matched10Nothing
Too strong1/2−1/3A third, inverted
Far too strong1/4−3/5Most of it, inverted

Both ends of that table are bad, and the specification gives eight codes because the right one is a property of a board. That is 22.1 §2's eight-code table explained.

7. Why Fly-By Helps, and What It Costs

Chapter 20.4 §2 introduced fly-by routing as the reason write levelling exists: the clock and command signals visit each device in turn rather than arriving simultaneously, so the strobe-to-clock relationship differs at every device. That chapter owned the consequence. This one owns the reason.

The alternative is a branched topology — a net that divides to reach several devices at once, each branch a stub by §5. The comparison is the whole argument:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   BRANCHED ("T" topology)          FLY-BY

   ───┬───┬───┬───                 ───┬───┬───┬───►
      │   │   │                       │   │   │
      D   D   D                       D   D   D

   every branch is a stub of        the main path CONTINUES past
   comparable length; their         each device; each device's own
   reflections return at            branch is short, so its round
   similar k and REINFORCE          trip is small and its reflection
                                    lands close in k -- §3's
                                    correctable region

Fly-by keeps the main path continuous and each device's branch short. Two benefits follow from §3 rather than from §2: the reflections are no smaller in coefficient, but their round trips are shorter and different from each other. Shorter means they land within a few unit intervals, which §3's table says is the reachable region for an equaliser. Different means they do not arrive together and reinforce.

The cost is exactly what 20.4 described. Continuing the path past each device means each device sees the clock at a different time. That is the skew write levelling exists to measure, and 21.1 exists to search for.

8. The Quantity This Chapter Cannot Compute

Before any RTL, an explicit statement of what is not going to appear — because this is the module's most analog chapter and the temptation is largest here.

Nothing in this chapter computes a reflection. Not its magnitude, not its arrival time, not its effect on a receiver's decision. The reasons are not squeamishness:

Would needWhy it is unavailable
Z0 of each structureA geometry property of a specific board and stack-up
ZL at each junctionIncludes package parasitics and a termination whose absolute value the controller cannot know (22.1 §3)
Physical lengthsA layout property, different for every design
Propagation velocityA dielectric property
Driver rise timeA technology property (19.1 §5)
Superposition of many arrivalsContinuous-time, continuous-amplitude behaviour

A block that took parameters for all of those and produced a number would be a circuit simulator written in the wrong language, and its output would be wrong in ways its user could not detect. Chapter 20.3 §1 refused to build a capture element for the same reason and stated it in one sentence: modelling it would teach that a DDR receiver is a flop.

So what is digital here? Two things, and they are genuinely useful.

Whether the channel is in a configuration where reflections are unmanaged. That is a function of the termination selections of 22.1 and the bus's activity — both digital, both knowable. §10 builds it.

Whether a budget closes. The settling allowance a design reserves for reflections to decay is a number the design chose. Whether the reserved allowance fits inside the available interval is arithmetic. §13 builds it, in abstract units, following the discipline 20.1 §6 established for a structurally identical problem.

Neither models a reflection. One detects a configuration; the other checks an accounting.

9. The Channel, as a Sequence of Junctions

A DDR channel drawn as a sequence of impedance discontinuities rather than as a single net. The top row follows the signal from the controller driver through the controller package and its vias, then a connector, then the board or module trace which is the reference impedance everything else is compared against. The second row shows a branch point where the net divides, a short stub reaching one device, and that device's package and vias. The third row shows the pins of two ranks and the termination each one presents, which is the selection chapter twenty-two point one owns. The bottom row is the receiver decision, which resolves the arriving signal plus every delayed copy of earlier bits that the junctions above returned. Edges follow the signal path from the driver through each junction to the ranks, from each rank into the presented termination, and from the termination to the receiver decision. Every junction in the path is a place where part of the wave turns around.Driver outputimpedance is programmedPackage + viasdifferent geometryConnectora mechanical jointBoard tracethis is the referenceBranch pointthe net dividesStubshort, and open-endedDevice packagegeometry changes againRank 0 pinsmay terminateRank 1 pinsmay be a stub — §5Presented RTT22.1 selects itReceiver decisionsignal + delayed copies12

The node to read is Receiver decision. Its caption is the chapter: what arrives there is the current bit plus a sum of delayed, scaled copies of earlier bits, one per junction above it, each at its own offset per §3. Nothing in the digital domain separates those contributions, which is why the receiver's only report is a bit and why 20.3 §12's silent corruption is possible at all.

10. The Unmanaged-Interval Detector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// unmanaged_interval_detector -- finds intervals in which a DDR channel
// is in a configuration where reflected energy is not being absorbed.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT DOES NOT MODEL:
//   - reflections, impedance, Z0, reflection coefficients, round trips,
//     amplitude, or superposition. NOTHING electrical appears here.
//   - whether a "managed" interval is electrically adequate. Presenting
//     a termination is not presenting the RIGHT termination (§6), and
//     this block cannot tell the difference.
//
// WHAT IT DOES: consumes the per-rank termination SELECTION of Chapter
// 22.1 (2 bits per rank: 0=OFF, 1=PARK, 2=NOM, 3=WR) plus the bus
// activity, and reports intervals where the net is active with no rank
// absorbing, or idle with ranks presenting nothing at all (§5's stub).
//
// An unmanaged interval is a CONFIGURATION, not an error. It is
// sometimes deliberate. What it must never be is unnoticed.
// ---------------------------------------------------------------------
module unmanaged_interval_detector #(
  parameter int NUM_RANKS = 4,
  // Longest interval, in cycles, the design is willing to leave the
  // channel unmanaged. POLICY: the defensible value depends on the
  // channel, which this block cannot see.
  parameter int MAX_UNMANAGED = 4,
  // Treat PARK as absorbing. It does (§5), but weakly, so a design may
  // want intervals covered only by PARK reported separately.
  parameter bit PARK_COUNTS_AS_MANAGED = 1'b1,
  parameter int CNT_W  = (MAX_UNMANAGED <= 1) ? 1 : $clog2(MAX_UNMANAGED + 2),
  // A COUNT of ranks up to NUM_RANKS needs clog2(NUM_RANKS+1).
  parameter int RKC_W  = $clog2(NUM_RANKS + 1)
) (
  input  logic                   clk,
  input  logic                   rst_n,

  // ── From Chapter 22.1's multi_rank_odt_arbiter, packed 2 bits/rank.
  input  logic [2*NUM_RANKS-1:0] rtt_sel_flat,

  // ── Bus activity. driving = someone is launching energy into the
  //    net; turnaround = ownership is changing and nobody drives.
  input  logic                   bus_driving,
  input  logic                   bus_turnaround,

  // ── Reports.
  output logic                   unmanaged_now,
  output logic [CNT_W-1:0]       unmanaged_run,      // current run length
  output logic [CNT_W-1:0]       unmanaged_worst,    // longest seen
  output logic                   over_policy,        // run exceeded MAX
  output logic [15:0]            cnt_intervals,
  output logic [RKC_W-1:0]       n_absorbing,
  output logic [RKC_W-1:0]       n_stubs,            // ranks at OFF (§5)
  output logic                   park_only,          // absorbing, weakly

  output logic                   err_driving_with_no_absorber,
  output logic                   err_all_stubs
);

  initial begin
    if (NUM_RANKS < 1)
      $fatal(1, "unmanaged_interval_detector: NUM_RANKS must be >= 1");
    if (MAX_UNMANAGED < 1)
      $fatal(1, "unmanaged_interval_detector: MAX_UNMANAGED must be >= 1");
  end

  localparam logic [1:0] SEL_OFF  = 2'd0;
  localparam logic [1:0] SEL_PARK = 2'd1;
  localparam logic [1:0] SEL_NOM  = 2'd2;
  localparam logic [1:0] SEL_WR   = 2'd3;

  logic [1:0] sel [NUM_RANKS];
  always_comb
    for (int r = 0; r < NUM_RANKS; r++)
      sel[r] = rtt_sel_flat[2*r +: 2];

  // ── Census of the channel this cycle.
  logic [RKC_W-1:0] n_abs, n_off, n_strong;
  always_comb begin
    n_abs    = '0;
    n_off    = '0;
    n_strong = '0;
    for (int r = 0; r < NUM_RANKS; r++) begin
      if (sel[r] == SEL_OFF) n_off = n_off + RKC_W'(1);
      if ((sel[r] == SEL_NOM) || (sel[r] == SEL_WR)) begin
        n_strong = n_strong + RKC_W'(1);
        n_abs    = n_abs    + RKC_W'(1);
      end else if ((sel[r] == SEL_PARK) && PARK_COUNTS_AS_MANAGED) begin
        n_abs = n_abs + RKC_W'(1);
      end
    end
  end

  // ── The configuration test. Two distinct bad shapes:
  //
  //    1. the net is being driven and NOTHING absorbs. Energy is being
  //       launched into a channel whose junctions all reflect (§2).
  //    2. every rank is at OFF. §5's structure, with every branch an
  //       open -- coefficient exactly 1, and no attenuation on repeat
  //       bounces because every power of 1 is 1 (§2's callout).
  logic driving_unabsorbed, all_off;
  assign driving_unabsorbed = bus_driving && (n_abs == '0);
  assign all_off            = (n_off == RKC_W'(NUM_RANKS));

  // A turnaround with nothing absorbing is the worst case and the
  // easiest to create by accident: 22.1 §11 showed termination for the
  // next transfer must be scheduled DURING the previous one, so a gap
  // here is a scheduling slip rather than a policy choice.
  logic turnaround_unabsorbed;
  assign turnaround_unabsorbed = bus_turnaround && (n_abs == '0);

  assign unmanaged_now = driving_unabsorbed || all_off || turnaround_unabsorbed;

  logic [CNT_W-1:0] run_q, worst_q;
  logic [15:0]      ivl_q;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      run_q   <= '0;
      worst_q <= '0;
      ivl_q   <= '0;
    end else begin
      if (unmanaged_now) begin
        // Saturate rather than wrap. A wrapped run length reads as a
        // short interval, which is the opposite of the truth.
        if (run_q != {CNT_W{1'b1}}) run_q <= run_q + CNT_W'(1);
        if ((run_q + CNT_W'(1)) > worst_q) worst_q <= run_q + CNT_W'(1);
        // Count the interval once, on its first cycle.
        if ((run_q == '0) && (ivl_q != 16'hFFFF)) ivl_q <= ivl_q + 16'd1;
      end else begin
        run_q <= '0;
      end
    end
  end

  assign unmanaged_run   = run_q;
  assign unmanaged_worst = worst_q;
  assign over_policy     = (run_q > CNT_W'(MAX_UNMANAGED));
  assign cnt_intervals   = ivl_q;
  assign n_absorbing     = n_abs;
  assign n_stubs         = n_off;
  // Absorbing, but only by PARK -- weakly (§5's middle branch).
  assign park_only       = (n_abs != '0) && (n_strong == '0);

  assign err_driving_with_no_absorber = driving_unabsorbed;
  assign err_all_stubs                = all_off;

endmodule

The park_only output is the one worth keeping in a real design. A channel where something is absorbing but only RTT_PARK is absorbing is neither the healthy case nor the alarming one — it is the middle branch of §5's diagram, and distinguishing it from “properly terminated” is exactly the distinction a single managed bit would destroy.

11. A Turnaround With a Gap

Here is the configuration §10 exists to catch, written out. Four ranks, a write to rank 1 followed by a read from rank 3 — 22.1 §11's turnaround, with the termination released one cycle too early.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  cycle   bus            R0    R1    R2    R3   absorbing  unmanaged
  ─────   ────────────   ───   ───   ───   ───  ─────────  ─────────
    0     driving (WR)   NOM   WR    NOM   PARK     4          no
    1     driving (WR)   NOM   WR    NOM   PARK     4          no
    2     driving (WR)   NOM   NOM   NOM   PARK     4          no
    3     turnaround     PARK  PARK  PARK  PARK     4          no
    4     turnaround     OFF   OFF   OFF   OFF      0        ** YES **
    5     turnaround     OFF   OFF   OFF   OFF      0        ** YES **
    6     driving (RD)   NOM   NOM   NOM   PARK     4          no

  unmanaged_run reaches 2, unmanaged_worst = 2, cnt_intervals = 1
  err_all_stubs asserted on cycles 4 and 5

  what happened: RTT_PARK was disabled for a power experiment, so the
  interval between releasing the write's termination and establishing
  the read's has NOTHING absorbing. Every rank is an open (§2: the
  coefficient is exactly 1) and every junction in §9's chain returns
  its energy undiminished.

  what it costs: energy launched before cycle 4 is still in flight. It
  bounces during 4 and 5 and arrives, per §3, on bits transferred in
  the READ that starts at cycle 6 -- a burst in the opposite direction
  from the one that caused it.

  why it is invisible: no functional failure. The read returns data.
  Whether that data is correct depends on how much energy is still in
  flight and on what the bits happen to be (§3's data dependence), so
  the symptom is an occasional, pattern-dependent read error with no
  error signal anywhere in the digital domain.

That last paragraph is the whole reason this chapter exists. A two-cycle configuration gap during a turnaround produces corruption in a later, opposite-direction transfer, and there is no digital observable that connects cause to effect. The only things available are the configuration itself — which §10 checks — and the pass-region narrowing Module 21 would measure afterwards without being able to say why.

12. Closing the Gap

§11 detects the failure. It does not prevent it, and the prevention is a genuinely digital problem worth building — because 22.1 §6 established that termination is posted, so the fix cannot be reactive.

The requirement, stated precisely: across a direction turnaround, the termination that serves the outgoing transfer must not be released before the termination that serves the incoming one is established. Since both take effect DODTL cycles after they are asserted, both assertions happen before either effect appears — so the scheduler has to reason about two overlapping pipelines rather than about the current cycle.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   NAIVE (what §11 shows)

   assert for WR ──────────┐
                           └─► effective ──┐
                                           └─► released
                                                   GAP
                                                       ┌─► assert for RD
                                                       └─► effective

   CORRECT

   assert for WR ──────────┐
                           └─► effective ─────────────┐
                                                      └─► released
              assert for RD ──────────┐
                                      └─► effective ──┘
                                        OVERLAP, not a gap

The overlap is the whole design. It costs a few cycles of both terminations being presented at once — which §12's cost discussion in 22.1 says is real static power — and it buys the elimination of an interval in which every junction in §9's chain reflects undiminished.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// turnaround_term_scheduler -- schedules termination assertion across a
// direction turnaround so the two posted effects OVERLAP instead of
// leaving an unmanaged interval (§11).
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT DOES NOT MODEL:
//   - reflections, impedance, coefficients, or energy in flight
//   - whether the overlap it produces is LONG ENOUGH. That depends on
//     §3's round trip k, which is a physical distance. This block
//     guarantees "no gap", never "enough overlap".
//
// THE PROBLEM IT SOLVES: termination is POSTED by DODTL cycles (22.1
// §6). A scheduler that releases on the last cycle of a burst and
// asserts for the next transfer afterwards produces a gap of 2*DODTL.
// Both assertions must therefore be issued before either takes effect.
// ---------------------------------------------------------------------
module turnaround_term_scheduler #(
  // Posted latency, WL-3 per 22.1 §5. Both directions are equal.
  parameter int DODTL = 9,
  // Cycles of deliberate overlap to hold both terminations presented.
  // POLICY: buys coverage against energy still in flight, costs static
  // power. The right value depends on §3's k, which is not knowable
  // here -- so it is a parameter with no defensible default.
  parameter int OVERLAP = 2,
  // Longest burst this scheduler supports, in cycles.
  parameter int MAX_BURST = 8,
  parameter int L_W = (DODTL     <= 1) ? 1 : $clog2(DODTL + 1),
  parameter int B_W = (MAX_BURST <= 1) ? 1 : $clog2(MAX_BURST + 1),
  parameter int O_W = (OVERLAP   <= 1) ? 1 : $clog2(OVERLAP + 1)
) (
  input  logic             clk,
  input  logic             rst_n,

  // ── A committed transfer, from 17.1's commit point as seen here.
  input  logic             xfer_commit,
  input  logic             xfer_is_write,
  input  logic [B_W-1:0]   xfer_cycles,

  // ── Assertions this block issues. Consumed by 22.1's arbiter as its
  //    per-rank odt input; this block owns only the TIMING.
  output logic             assert_for_write,
  output logic             assert_for_read,

  // ── Observability.
  output logic             both_asserted,     // the overlap, visible
  output logic [L_W-1:0]   w_lead,
  output logic [L_W-1:0]   r_lead,
  output logic             gap_would_occur,   // the §11 condition

  output logic             err_commit_while_busy,
  output logic             err_gap_scheduled
);

  initial begin
    if (DODTL < 0)
      $fatal(1, "turnaround_term_scheduler: DODTL must not be negative");
    if (OVERLAP < 1)
      $fatal(1, "turnaround_term_scheduler: OVERLAP must be at least 1");
    if (MAX_BURST < 1)
      $fatal(1, "turnaround_term_scheduler: MAX_BURST must be at least 1");
  end

  // ── Two independent assertion pipelines, one per direction. They are
  //    separate because their lifetimes OVERLAP by construction, and a
  //    single shared timer could not express that.
  logic             w_active, r_active;
  logic [L_W-1:0]   w_cnt,    r_cnt;
  logic [B_W-1:0]   w_hold,   r_hold;
  logic [O_W-1:0]   w_tail,   r_tail;

  // Declared with the state because the continuous assignments below
  // read them -- declare before use.
  logic             w_as, r_as;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      w_active <= 1'b0; r_active <= 1'b0;
      w_cnt    <= '0;   r_cnt    <= '0;
      w_hold   <= '0;   r_hold   <= '0;
      w_tail   <= '0;   r_tail   <= '0;
      // SAFE STATE: neither asserted. 22.1's arbiter then presents
      // PARK, which §5 establishes is the correct idle behaviour.
      w_as     <= 1'b0; r_as     <= 1'b0;
    end else begin
      // ── Write-direction pipeline.
      if (xfer_commit && xfer_is_write) begin
        w_active <= 1'b1;
        w_cnt    <= L_W'(DODTL);
        w_hold   <= xfer_cycles;
        w_tail   <= O_W'(OVERLAP);
        w_as     <= 1'b1;             // assert NOW; effect is posted
      end else if (w_active) begin
        if (w_cnt != '0) begin
          w_cnt <= w_cnt - L_W'(1);
        end else if (w_hold != '0) begin
          w_hold <= w_hold - B_W'(1);
        end else if (w_tail != '0) begin
          // The tail is the deliberate overlap: hold the write's
          // termination past its burst so it is still present while
          // the read's is coming up.
          w_tail <= w_tail - O_W'(1);
        end else begin
          w_active <= 1'b0;
          w_as     <= 1'b0;
        end
      end

      // ── Read-direction pipeline, identical shape.
      if (xfer_commit && !xfer_is_write) begin
        r_active <= 1'b1;
        r_cnt    <= L_W'(DODTL);
        r_hold   <= xfer_cycles;
        r_tail   <= O_W'(OVERLAP);
        r_as     <= 1'b1;
      end else if (r_active) begin
        if (r_cnt != '0) begin
          r_cnt <= r_cnt - L_W'(1);
        end else if (r_hold != '0) begin
          r_hold <= r_hold - B_W'(1);
        end else if (r_tail != '0) begin
          r_tail <= r_tail - O_W'(1);
        end else begin
          r_active <= 1'b0;
          r_as     <= 1'b0;
        end
      end
    end
  end

  assign assert_for_write = w_as;
  assign assert_for_read  = r_as;
  assign both_asserted    = w_as && r_as;
  assign w_lead           = w_cnt;
  assign r_lead           = r_cnt;

  // ── The §11 condition, as a predicate rather than a post-hoc report:
  //    a transfer is committed while the other direction's pipeline has
  //    already retired, so nothing will be asserted when this one's
  //    effect is still pending.
  assign gap_would_occur = xfer_commit && !w_as && !r_as;

  assign err_commit_while_busy =
           xfer_commit && ((xfer_is_write && w_active) || (!xfer_is_write && r_active));
  // A gap was scheduled despite a non-zero overlap policy: the tails
  // are not doing their job, which means the retire order is wrong.
  assign err_gap_scheduled = gap_would_occur && (OVERLAP > 0) && (w_active || r_active);

endmodule

Two things about this block are worth noticing.

The two pipelines are separate, and that is the point. A single timer cannot express two assertions whose lifetimes deliberately overlap. Sharing one would force a release before the next assertion — which is precisely §11's bug, reintroduced structurally.

gap_would_occur is a predicate, not a report. §10's detector says a gap happened; this says one will happen, one commit in advance, because the posted latency gives that much warning. That is the difference between a monitor and a scheduler, and it is only possible because the latency is known and fixed (22.1 §6).

And the honest limit, stated at the block: OVERLAP has no defensible default. How long both terminations should be presented depends on how long energy stays in flight, which is §3's k — a physical distance. The block guarantees no gap; it cannot guarantee enough overlap.

13. The Settling Budget

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// settling_budget_accountant -- does the allowance a design reserved
// for reflected energy to decay fit inside the interval it has?
//
// CLASSIFICATION: educational, synthesisable. Structurally identical to
// Chapter 20.1's arrival_uncertainty_budget, and for the same reason:
// the accounting is checkable, the physics is not.
//
// WHAT IT DOES NOT MODEL:
//   - reflections, coefficients, Z0, impedance, distance, velocity,
//     amplitude, or time. ALL UNITS ARE ABSTRACT AND DIMENSIONLESS.
//   - whether the reserved allowance is SUFFICIENT. It checks that a
//     chosen budget closes, never that the choice was right.
//
// USE IT TO COMPARE TWO CONFIGURATIONS, never to size a real channel.
// ---------------------------------------------------------------------
module settling_budget_accountant #(
  parameter int UNIT_W = 12,
  // Terminated or not. Not a resistance -- a DISCIPLINE selector, as
  // 20.1's block used for the two clocking disciplines.
  //   0 = terminated   : each junction's allowance is scaled down
  //   1 = unterminated : full allowance per junction (§2: the open
  //                      case returns everything and does not
  //                      attenuate on repeat bounces)
  parameter bit DISCIPLINE_UNTERMINATED = 1'b0,
  // How much a terminated junction's allowance shrinks, as a
  // numerator over 8. POLICY, and abstract.
  parameter int TERM_SCALE_N8 = 3
) (
  input  logic                clk,
  input  logic                rst_n,

  // ── The interval available, in abstract units.
  input  logic [UNIT_W-1:0]   interval_units,
  // ── How many junctions (§4) the design has, and the allowance the
  //    design reserves per junction. BOTH ARE THE DESIGN'S NUMBERS.
  input  logic [UNIT_W-1:0]   junction_count,
  input  logic [UNIT_W-1:0]   allowance_per_junction,
  // ── Allowance reserved for everything else in the interval:
  //    setup, hold, skew. Consumed before settling gets any.
  input  logic [UNIT_W-1:0]   other_reserved,

  // ── Results.
  output logic [UNIT_W+3:0]   settling_consumed,
  output logic [UNIT_W+3:0]   total_consumed,
  output logic                budget_closes,
  output logic [UNIT_W+3:0]   headroom,        // 0 when it does not close
  output logic [UNIT_W+3:0]   deficit,         // 0 when it does
  // Named, machine-readable list of what is NOT in this arithmetic.
  // 20.1 established that a budget block must publish its own gaps.
  output logic [7:0]          terms_excluded,

  output logic                err_zero_interval
);

  initial begin
    if (TERM_SCALE_N8 < 0 || TERM_SCALE_N8 > 8)
      $fatal(1, "settling_budget_accountant: TERM_SCALE_N8 must be 0..8");
    if (UNIT_W < 4)
      $fatal(1, "settling_budget_accountant: UNIT_W too small");
  end

  // ── Per-junction allowance after the discipline is applied. The
  //    unterminated case is NOT scaled, which is §2's exact-1 result
  //    expressed as arithmetic rather than as a claim about volts.
  logic [UNIT_W+3:0] per_junction_eff;
  always_comb begin
    if (DISCIPLINE_UNTERMINATED)
      per_junction_eff = {4'b0, allowance_per_junction};
    else
      per_junction_eff = ({4'b0, allowance_per_junction} * (UNIT_W+4)'(TERM_SCALE_N8))
                           >> 3;
  end

  logic [UNIT_W+3:0] settle, total;
  always_comb begin
    settle = per_junction_eff * {4'b0, junction_count};
    total  = settle + {4'b0, other_reserved};
  end

  logic closes;
  assign closes = (total <= {4'b0, interval_units}) && (interval_units != '0);

  assign settling_consumed = settle;
  assign total_consumed    = total;
  assign budget_closes     = closes;
  assign headroom          = closes ? ({4'b0, interval_units} - total) : '0;
  assign deficit           = closes ? '0 : (total - {4'b0, interval_units});
  assign err_zero_interval = (interval_units == '0);

  // ── What this arithmetic DOES NOT contain. Published as a bit vector
  //    so a checker can assert it is non-zero: a budget block that
  //    claimed completeness would be the actual error.
  //    bit 0 reflection coefficients      bit 4 driver rise time
  //    bit 1 round-trip offsets (§3's k)  bit 5 data dependence
  //    bit 2 superposition of arrivals    bit 6 crosstalk  (22.3)
  //    bit 3 frequency-dependent loss     bit 7 equalisation (22.4)
  assign terms_excluded = 8'hFF;

endmodule

The terms_excluded output is fixed at all-ones and that is deliberate. This block excludes every physical term there is. Publishing that as a value a property can check — §15's P8 asserts it is non-zero — makes the block's incompleteness a verified fact rather than a comment someone might delete.

14. Two Disciplines, Side by Side

The budget's use is comparison, so here is the comparison, with every number recomputed rather than asserted. Abstract units throughout, TERM_SCALE_N8 = 3 so a terminated junction is charged three eighths of the unterminated allowance.

UnterminatedTerminated
interval_units100100
junction_count66
allowance_per_junction1212
Effective per junction1212 × 3 / 8 = 4
settling_consumed6 × 12 = 726 × 4 = 24
other_reserved3333
total_consumed10557
budget_closesnoyes
deficit / headroomdeficit 5headroom 43

The unterminated configuration does not close — it needs 105 units of a 100-unit interval, and the deficit is 5. The terminated one closes with 43 units spare.

Two honest observations about that table, and they matter more than the numbers.

The numbers are a comparison, not a measurement. TERM_SCALE_N8 = 3 was chosen, not derived. Change it and the terminated column changes. What the table establishes is the shape of the dependence — that settling allowance scales with junction count and that the discipline multiplies it — which is a structural fact. The magnitudes are not.

The interesting quantity is junction_count, and it is a layout number. Six junctions of §4's eight is a modest channel. The arithmetic says settling cost is linear in the number of discontinuities, which is the budget-level statement of §5's rank-scaling claim and §7's argument for fly-by. A design that adds a connector has added a junction and has spent more of an interval it did not grow.

15. What the Assertions Prove

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind unit note: P1-P6 reference unmanaged_interval_detector's
// internals and are written as though bound into it. P7-P9 reference
// settling_budget_accountant, which is purely combinational and has no
// clk port of its own; they are written for a bind unit that supplies
// a sampling clock, and they constrain a function rather than a
// sequence.

// P1 -- driving the net with nothing absorbing is always reported.
// §11's configuration, forbidden from passing unnoticed.
property p_driving_unabsorbed_reported;
  @(posedge clk) disable iff (!rst_n)
    (bus_driving && (n_absorbing == '0)) |-> unmanaged_now;
endproperty
assert property (p_driving_unabsorbed_reported);

// P2 -- every rank at OFF is always reported. §5's all-stub structure.
property p_all_stubs_reported;
  @(posedge clk) disable iff (!rst_n)
    (n_stubs == RKC_W'(NUM_RANKS)) |-> unmanaged_now;
endproperty
assert property (p_all_stubs_reported);

// P3 -- the run length only grows while unmanaged, and resets
// otherwise. A run that survived a managed cycle would merge two
// intervals and understate how many there were.
property p_run_resets_when_managed;
  @(posedge clk) disable iff (!rst_n)
    !unmanaged_now |=> (unmanaged_run == '0);
endproperty
assert property (p_run_resets_when_managed);

// P4 -- the worst-ever run never decreases. A high-water mark that
// could fall would lose the event it exists to record.
property p_worst_is_monotonic;
  @(posedge clk) disable iff (!rst_n)
    (unmanaged_worst >= $past(unmanaged_worst, 1));
endproperty
assert property (p_worst_is_monotonic);

// P5 -- the worst-ever run is always at least the current run.
property p_worst_bounds_current;
  @(posedge clk) disable iff (!rst_n)
    (unmanaged_worst >= unmanaged_run);
endproperty
assert property (p_worst_bounds_current);

// P6 -- park_only and a strong absorber are mutually exclusive, so the
// middle branch of §5 can never be confused with the healthy case.
property p_park_only_is_exclusive;
  @(posedge clk) disable iff (!rst_n)
    park_only |-> (n_absorbing != '0);
endproperty
assert property (p_park_only_is_exclusive);

// P7 -- the budget closes only if the total fits. Stated over the
// block's own outputs so it catches a comparison written the wrong way
// round, which is the single most likely bug in an accountant.
property p_closes_iff_fits;
  @(posedge clk) disable iff (!rst_n)
    budget_closes |-> (total_consumed <= {4'b0, interval_units});
endproperty
assert property (p_closes_iff_fits);

// P8 -- THE property that keeps this chapter honest. The budget must
// always declare that it excludes physical terms. A future edit that
// "completed" the model and cleared this vector would be the error.
property p_excludes_are_declared;
  @(posedge clk) disable iff (!rst_n)
    (terms_excluded != 8'h00);
endproperty
assert property (p_excludes_are_declared);

// P9 -- headroom and deficit are never both non-zero, and exactly the
// right one is non-zero for the outcome.
property p_headroom_xor_deficit;
  @(posedge clk) disable iff (!rst_n)
    budget_closes ? (deficit == '0) : (headroom == '0);
endproperty
assert property (p_headroom_xor_deficit);

// ── Cover.
cover property (@(posedge clk) disable iff (!rst_n) err_all_stubs);
cover property (@(posedge clk) disable iff (!rst_n) over_policy);
// The §11 shape specifically: a turnaround with nothing absorbing,
// entered from a managed cycle.
cover property (@(posedge clk) disable iff (!rst_n)
                  !unmanaged_now ##1 (bus_turnaround && (n_absorbing == '0)));
// park_only actually occurred -- the middle branch was exercised.
cover property (@(posedge clk) disable iff (!rst_n) park_only);
// A budget that fails, and one that closes. Both matter: a suite that
// only ever closes has not tested the comparison.
cover property (@(posedge clk) disable iff (!rst_n) !budget_closes);
cover property (@(posedge clk) disable iff (!rst_n)
                  budget_closes && (headroom == '0));
// An unmanaged run long enough to breach policy AND recover, which is
// the pattern a scheduling slip produces rather than a policy choice.
cover property (@(posedge clk) disable iff (!rst_n)
                  over_policy ##[1:8] !unmanaged_now);

P8 is unusual and it is the property this chapter most needs. Every other assertion here checks that the blocks do what they claim. P8 checks that they continue to admit what they cannot do — and it fails for the one edit that would do the most damage, which is someone adding impedance parameters and clearing the excluded-terms vector because the model now “handles” reflections.

16. Corner Cases

CaseBehaviourWhy
All ranks OFF, bus idleerr_all_stubs, unmanaged§5 — idle is when park matters most
All ranks OFF, bus drivingBoth error outputs assertThe worst configuration; §11's cycles 4–5
PARK_COUNTS_AS_MANAGED = 0Park-only intervals become unmanagedLegal and stricter; use when park is weak
One rank absorbing out of fourManagedAbsorption is per junction (§6) — one rank does not cover another's branch
park_only with bus drivingManaged, flagged weaklyThe middle branch; not an error, and not healthy
Unmanaged run longer than the counterSaturates, does not wrapA wrapped run reads as short — the opposite of the truth
MAX_UNMANAGED larger than any real runover_policy never assertsPolicy is a choice; the run length is still published
interval_units = 0err_zero_interval, budget cannot closeA zero interval is malformed, not tight
junction_count = 0Settling consumes nothingDegenerate; a channel with no discontinuities does not exist (§4)
TERM_SCALE_N8 = 8Terminated equals unterminatedEncodes “termination achieves nothing” — a legal comparison baseline
TERM_SCALE_N8 = 0Terminated settling is zeroEncodes perfect matching; §2 says this is one exact point, so it is a bound, not a config

The last two rows are worth their place as the extremes of §14's comparison. Setting the scale to eight or zero turns the budget into a sensitivity study rather than a model, and it is the only defensible way to use a number nobody derived.

17. DV — Model a Channel With a Known Bounce

The checker for §10 drives configurations and checks the report. The checker for §13 drives numbers and checks the arithmetic. Neither models physics — but a testbench can usefully carry a bookkeeping model that tracks when energy would still be in flight, without claiming any amplitude.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Independent in-flight bookkeeper. Tracks WHETHER energy launched in
// the recent past would still be bouncing, given a round trip of k
// cycles -- and nothing about its size.
// SIMULATION-ONLY. It is a shift register with a name, deliberately:
// anything more would be claiming amplitude.
class inflight_model;
  int unsigned k;                 // round trip, in cycles (§3)
  bit [63:0]   launched;          // 1 = the net was driven that cycle

  function void tick(bit driving);
    launched = (launched << 1) | (driving ? 64'd1 : 64'd0);
  endfunction

  // Would energy from k cycles ago still be arriving now?
  function bit arriving_now();
    return (k > 0 && k < 64) ? launched[k] : 1'b0;
  endfunction

  // The case the chapter cares about: energy arriving during an
  // interval in which nothing is absorbing.
  function bit arriving_unabsorbed(bit unmanaged);
    return arriving_now() && unmanaged;
  endfunction
endclass
CheckWhat it establishes
Drive all-OFF with the bus idle; expect err_all_stubs and a growing run§5's structure reported
Drive §11's exact sequence; expect unmanaged_worst == 2, cnt_intervals == 1The trace, reproduced from the RTL
Insert a managed cycle mid-run; expect two intervals, not oneP3, driven rather than assumed
Sweep MAX_UNMANAGED; over_policy toggles at exactly the boundaryNo off-by-one in the policy comparison
Set PARK_COUNTS_AS_MANAGED = 0; park-only intervals become unmanagedThe parameter does what it says
With the bookkeeper at k = 3, correlate arriving_unabsorbed against the detectorWhether the detector's intervals coincide with in-flight energy
Budget: sweep junction_count; settling_consumed is exactly linear§14's structural claim
Budget: sweep TERM_SCALE_N8 0 to 8; consumption is monotonic and hits both bounds§16's extremes
Budget: drive total == interval exactly; expect budget_closes with zero headroomThe boundary, which a strict comparison would fail
Attempt to clear terms_excluded; expect P8 to fireThe honesty property is live

The sixth check is the one that produces a genuinely interesting result rather than a pass:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  IN-FLIGHT ENERGY VERSUS DETECTED INTERVALS
    §11's sequence, bookkeeper at k = 3 cycles

    cycle  driving  unmanaged  launched[3]  arriving_unabsorbed
    ─────  ───────  ─────────  ───────────  ───────────────────
      0      yes       no          -                no
      1      yes       no          -                no
      2      yes       no          -                no
      3      no        no         yes                no
      4      no       YES         yes              ** YES **
      5      no       YES         yes              ** YES **
      6      yes       no         no                 no

    the detector flags cycles 4 and 5. The bookkeeper says energy
    launched at cycles 1 and 2 is arriving during exactly those cycles.
    They coincide.

    BUT: run the same sequence with k = 7.
    cycle  driving  unmanaged  launched[7]  arriving_unabsorbed
      4      no       YES          no                no
      5      no       YES          no                no
      8      yes       no         yes                no

    the detector still flags 4 and 5. The bookkeeper says nothing was
    arriving then -- the energy from the write arrives at cycle 8,
    during the READ, when the channel is managed again.

    diagnosis : the detector's report is CORRECT in both cases and
                MEANS something different in each. With a short round
                trip the unmanaged interval is when the reflection
                lands. With a long one, the unmanaged interval merely
                FAILED TO ABSORB energy that lands later, somewhere
                else entirely.
    why it matters : the detector cannot distinguish these, because k
                is a physical distance and nothing digital knows it.
                The severity of an unmanaged interval depends on a
                quantity the detector does not have.
    what NOT to conclude : that the detector is inadequate. It is
                reporting exactly what it claims -- a configuration.
                Reading it as "this is where corruption happened" is
                the error, and §3 is why.

That report is the most useful output of this chapter's verification, and it is not a bug. It is a precise statement of the gap between a configuration check and a physical consequence — and it is the reason §15's callout says a managed channel can still be marginal.

18. Debugging

SymptomLikely causeHow to confirm
Occasional read errors, pattern-dependentReflections landing per §3Vary the stimulus pattern; a pattern-sensitive failure is the signature
Errors worsen with more ranks populatedStubs scale with rank count — §5Depopulate and retest; check RTT_PARK is enabled
Errors only after a direction turnaroundUnmanaged gap — §11err_all_stubs, unmanaged_worst; check park across the turnaround
Margin poor with termination enabledOver-termination — §6's tableThe coefficient is negative; try a weaker code
Margin poor with strong termination and low amplitudeDriver loaded by too many absorbersCount n_absorbing; 22.1's policy may terminate too widely
Works on one board, not another, same designZ0 differs — §4The reference itself varies; termination codes may need re-selecting
Adding a connector degraded marginOne more junction — §14Settling cost is linear in junction count
Training region narrow at every laneChannel-wide, not per-lane21.2 §15's third row; this chapter is the cause
Training region fragmentedMultiple arrivals at different offsets — §3Reflections sum; islands are a superposition signature
Fails at rate, passes at reduced rateRound trip is a fixed time; UI shrinks with rateAt lower rate the same k spans fewer UI — §3
park_only asserted throughoutPark is carrying the whole channelLegal; check whether RTT_NOM is being asserted at all
Budget closes but hardware is marginalallowance_per_junction chosen too small§15's callout; terms_excluded is all-ones for a reason

Row ten is the one that explains the classic DDR symptom. A reflection's round trip is a fixed physical time; a unit interval is not. Double the data rate and the same discontinuity's round trip now spans twice as many unit intervals, so it lands further out in k — which per §3's table can move it beyond an equaliser's reach. That is why a channel that is comfortable at one grade can fail at the next with nothing changed but the clock.

Row nine is worth trusting as a diagnostic. A fragmented pass region is not usually a training bug (21.2 §7 had to handle it as one), and it is not usually noise. It is what a sum of several delayed arrivals at different offsets looks like when you sweep a sampling point through it.

19. Misconceptions

“A reflection is noise.” §1. It is the signal's own energy returning. It has the same spectrum, it is correlated with the data, and no filter separates it from the signal.

“Termination cancels reflections.” §6. It reduces the coefficient at one junction. §2 shows zero is a single exact point, and no real channel sits on it.

“More termination is safer.” §6's table. Over-termination gives a negative coefficient — it reflects too, with inverted polarity, and it loads the driver into a smaller swing.

“The reflection corrupts the bit that caused it.” §3. It arrives after a round trip and lands on a later bit. This is the single most consequential fact in the chapter.

“Reflections are random.” §3. For a fixed pattern they are deterministic. They change when the pattern changes, which is why a passing training pattern proves less than it appears to.

“An idle device is electrically absent.” §5. Its pins are on the net. Unterminated, its coefficient is exactly 1, and repeated bounces do not attenuate at all.

RTT_PARK is an optimisation.” §5. Without it, every idle rank is the rightmost branch of §5's diagram during every interval nobody thinks about.

“Terminating one rank protects the channel.” §6. The coefficient is a property of a junction. Rank A's termination does nothing for rank B's open pins.

“Fly-by exists to make routing easier.” §7. It keeps the main path continuous so each device's branch is short, which puts its reflection close in k. It costs routing convenience — and it is what created write levelling.

“If it works at this rate it will work slower.” §18's row ten. Usually true, and the mechanism is that a fixed round trip spans fewer unit intervals at a lower rate — so the reason is worth knowing rather than assuming.

“The RTL here checks the channel.” §15's callout. It checks a configuration and an accounting. §17's report shows the configuration check cannot even tell you whether corruption coincided with the interval it flagged.

“A budget that closes means the design is safe.” §14 and §15. terms_excluded is all-ones. The arithmetic is checkable; the allowance it uses was chosen, not derived.

20. Interview Reasoning

Why does a reflection exist at all? Because the incoming wave carries a fixed ratio of voltage to current and the structure ahead can only support a different one. Both cannot be satisfied, energy is conserved, so part of the wave turns around.

How much comes back from an unterminated pin? All of it. The coefficient is exactly 1, and because every power of 1 is 1, repeated bounces off an open do not attenuate.

Which bit does a reflection corrupt? A later one. The energy makes a round trip, so it lands k unit intervals after the bit that launched it, where k depends on a physical distance the protocol knows nothing about.

Why does that make reflections data-dependent? Because whether bit N+k is helped or hurt depends on what bit N was. A run of identical bits reinforces; an alternating pattern partly cancels.

Why is over-termination a real failure mode? The coefficient is (ZL − Z0)/(ZL + Z0). Below a match it goes negative, so energy returns inverted — and the heavier load reduces the swing the receiver has to resolve.

Why does adding ranks reduce the achievable rate? Each rank is a branch. Unterminated it is a stub with a coefficient of 1; even parked it is an imperfect absorber. More ranks means more delayed copies summed at the receiver.

Why does fly-by help, and what did it cost? It keeps the main path continuous so each device's branch is short, putting its reflection close in k and stopping the branches from reinforcing. It cost per-device clock skew — which is why write levelling exists and why Module 21 has an algorithm for it.

A channel passes at DDR4-2133 and fails at 2400 with nothing else changed. Mechanism? The round trip is a fixed time; the unit interval shrank. The same discontinuity now lands further out in k, potentially beyond what an equaliser can reach.

Your pass region is fragmented rather than narrow. What does that suggest? Several delayed arrivals at different offsets summing at the receiver. A single dominant reflection narrows a region; multiple ones at different k can put holes in it.

What can RTL tell you about reflections? Whether the channel is in a configuration where nothing is arranged to absorb them, and whether a chosen settling budget closes. Not whether a reflection occurred, how big it was, or when it landed — and §17's report shows the configuration check cannot even be read as locating corruption.

21. Exercises

  1. Recompute §2's table for r = 3/2 and r = 2/3 as exact fractions. Then explain why the two magnitudes are equal and the signs opposite, and what that symmetry means for choosing between a slightly weak and a slightly strong termination.

  2. §10 treats PARK as absorbing by default. Construct the channel configuration for which that default is actively misleading, and write the assertion you would add to catch it. Relate your answer to §5's middle branch.

  3. Using §17's bookkeeper, find the smallest k for which §11's unmanaged interval does not coincide with arriving energy. Then explain why the detector's output is still correct and still worth having.

  4. terms_excluded is hard-wired to all-ones and P8 asserts it is non-zero. Argue whether a block should ever be allowed to clear individual bits, and specify precisely what would have to be added to the block to justify clearing bit 1 (round-trip offsets).

  5. §14's comparison uses TERM_SCALE_N8 = 3. Redo the table for 8 and for 0, and state what each extreme is asserting about the physics. Which of the two is a defensible engineering bound and which is not?

  6. Settling cost is linear in junction_count. Work out what a design would have to change to make it sub-linear, and explain from §3 why that is a layout question rather than a termination question.

  7. Extend §10 to report, separately, unmanaged intervals that occur during a turnaround and those that occur while the bus is driving. Why is the distinction worth the extra counter, given §11?

  8. This chapter asserts no absolute impedance, length or time. Identify the three places where introducing one number would make the content more concrete, and for each, state what would then become unverifiable and whether the trade is worth it.

22. Where This Goes

Reflections now have a cause, a magnitude, a sign, and — most importantly — an arrival time. Energy turns around at an impedance discontinuity, comes back after a round trip, and lands on a bit that is k unit intervals later than the one that launched it. That single fact explains the data dependence, the pattern sensitivity, the rank scaling, the fragmentation of a pass region, and why a channel that works at one rate fails at the next.

It also explains why this chapter's RTL is modest. §3's late arrival means the corrupting energy belongs to a bit the receiver has already resolved and discarded. There is no moment at which a digital observer could see both the cause and the effect, which is why §10 checks configurations and §13 checks arithmetic and §15's callout is the longest in the module.

What is still missing is the rest of the degradation. A reflection is one mechanism; a receiver's margin is eaten by several, and they interact.

Chapter 22.3 names them: inter-symbol interference — which §3 has already described without using the term — together with crosstalk from neighbouring lines and frequency-dependent loss. It also introduces the first genuinely digital mitigation in this module: data bus inversion, which does not improve the channel at all but changes the pattern presented to it, and whose register bits and encoding are specified precisely enough to build.

22.4 then takes §3's table seriously and asks what a receiver can do about arrivals it can predict.

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.