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:
ZL - Z0
reflection = ─────────
coefficient ZL + Z0Expressed as a ratio r = ZL / Z0 so no absolute impedance is asserted, the values are exact:
| Load | r | Coefficient | Fraction returned |
|---|---|---|---|
| Open — an unterminated pin | infinite | 1 | +100% |
Four times Z0 | 4 | 3/5 | +60% |
Twice Z0 | 2 | 1/3 | +33.3% |
| Matched | 1 | 0 | 0% — nothing returns |
Half Z0 | 1/2 | −1/3 | −33.3% |
A quarter of Z0 | 1/4 | −3/5 | −60% |
| Short to the return path | 0 | −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.
bit N launched
│
│ travels to the discontinuity
▼
reflects
│
│ travels back
▼
arrives at the receiver during bit N+kThe 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 trip | Corrupts | Within a four-tap equaliser's reach |
|---|---|---|
k = 1 UI | bit N+1 | yes |
k = 2 UI | bit N+2 | yes |
k = 3 UI | bit N+3 | yes |
k = 4 UI | bit N+4 | yes |
k = 5 UI | bit N+5 | no |
k = 6 UI | bit N+6 | no |
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.
| Location | Why the impedance changes |
|---|---|
| Driver output | The 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 vias | A different geometry from the board trace, over a short distance |
| Board trace | The reference structure — this is the Z0 everything else is compared against |
| Connector | A DIMM socket is a mechanical joint with its own geometry |
| Module trace | A different board, different stack-up, different Z0 |
| Branch points | Where the net divides to reach several devices — §5 |
| Device package and vias | Again a different geometry |
| Device pins | Where 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:
main path
driver ───┬──────────┬──────────┬────────► far end
│ │ │
device device device
│ │ │
terminating PARKED UNTERMINATED
│ │ │
absorbs absorbs reflects
less EVERYTHINGThe 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:
| Termination | r | Coefficient | What returns |
|---|---|---|---|
| None | infinite | +1 | Everything, same polarity |
| Too weak | 2 | +1/3 | A third, same polarity |
| Matched | 1 | 0 | Nothing |
| Too strong | 1/2 | −1/3 | A third, inverted |
| Far too strong | 1/4 | −3/5 | Most 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:
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 regionFly-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 need | Why it is unavailable |
|---|---|
Z0 of each structure | A geometry property of a specific board and stack-up |
ZL at each junction | Includes package parasitics and a termination whose absolute value the controller cannot know (22.1 §3) |
| Physical lengths | A layout property, different for every design |
| Propagation velocity | A dielectric property |
| Driver rise time | A technology property (19.1 §5) |
| Superposition of many arrivals | Continuous-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
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
// ---------------------------------------------------------------------
// 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;
endmoduleThe 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.
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.
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 gapThe 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.
// ---------------------------------------------------------------------
// 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);
endmoduleTwo 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
// ---------------------------------------------------------------------
// 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;
endmoduleThe 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.
| Unterminated | Terminated | |
|---|---|---|
interval_units | 100 | 100 |
junction_count | 6 | 6 |
allowance_per_junction | 12 | 12 |
| Effective per junction | 12 | 12 × 3 / 8 = 4 |
settling_consumed | 6 × 12 = 72 | 6 × 4 = 24 |
other_reserved | 33 | 33 |
total_consumed | 105 | 57 |
budget_closes | no | yes |
deficit / headroom | deficit 5 | headroom 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
// 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
| Case | Behaviour | Why |
|---|---|---|
All ranks OFF, bus idle | err_all_stubs, unmanaged | §5 — idle is when park matters most |
All ranks OFF, bus driving | Both error outputs assert | The worst configuration; §11's cycles 4–5 |
PARK_COUNTS_AS_MANAGED = 0 | Park-only intervals become unmanaged | Legal and stricter; use when park is weak |
| One rank absorbing out of four | Managed | Absorption is per junction (§6) — one rank does not cover another's branch |
park_only with bus driving | Managed, flagged weakly | The middle branch; not an error, and not healthy |
| Unmanaged run longer than the counter | Saturates, does not wrap | A wrapped run reads as short — the opposite of the truth |
MAX_UNMANAGED larger than any real run | over_policy never asserts | Policy is a choice; the run length is still published |
interval_units = 0 | err_zero_interval, budget cannot close | A zero interval is malformed, not tight |
junction_count = 0 | Settling consumes nothing | Degenerate; a channel with no discontinuities does not exist (§4) |
TERM_SCALE_N8 = 8 | Terminated equals unterminated | Encodes “termination achieves nothing” — a legal comparison baseline |
TERM_SCALE_N8 = 0 | Terminated settling is zero | Encodes 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.
// 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| Check | What 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 == 1 | The trace, reproduced from the RTL |
| Insert a managed cycle mid-run; expect two intervals, not one | P3, driven rather than assumed |
Sweep MAX_UNMANAGED; over_policy toggles at exactly the boundary | No off-by-one in the policy comparison |
Set PARK_COUNTS_AS_MANAGED = 0; park-only intervals become unmanaged | The parameter does what it says |
With the bookkeeper at k = 3, correlate arriving_unabsorbed against the detector | Whether 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 headroom | The boundary, which a strict comparison would fail |
Attempt to clear terms_excluded; expect P8 to fire | The honesty property is live |
The sixth check is the one that produces a genuinely interesting result rather than a pass:
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
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Occasional read errors, pattern-dependent | Reflections landing per §3 | Vary the stimulus pattern; a pattern-sensitive failure is the signature |
| Errors worsen with more ranks populated | Stubs scale with rank count — §5 | Depopulate and retest; check RTT_PARK is enabled |
| Errors only after a direction turnaround | Unmanaged gap — §11 | err_all_stubs, unmanaged_worst; check park across the turnaround |
| Margin poor with termination enabled | Over-termination — §6's table | The coefficient is negative; try a weaker code |
| Margin poor with strong termination and low amplitude | Driver loaded by too many absorbers | Count n_absorbing; 22.1's policy may terminate too widely |
| Works on one board, not another, same design | Z0 differs — §4 | The reference itself varies; termination codes may need re-selecting |
| Adding a connector degraded margin | One more junction — §14 | Settling cost is linear in junction count |
| Training region narrow at every lane | Channel-wide, not per-lane | 21.2 §15's third row; this chapter is the cause |
| Training region fragmented | Multiple arrivals at different offsets — §3 | Reflections sum; islands are a superposition signature |
| Fails at rate, passes at reduced rate | Round trip is a fixed time; UI shrinks with rate | At lower rate the same k spans fewer UI — §3 |
park_only asserted throughout | Park is carrying the whole channel | Legal; check whether RTT_NOM is being asserted at all |
| Budget closes but hardware is marginal | allowance_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
-
Recompute §2's table for
r = 3/2andr = 2/3as 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. -
§10 treats
PARKas 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. -
Using §17's bookkeeper, find the smallest
kfor 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. -
terms_excludedis 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). -
§14's comparison uses
TERM_SCALE_N8 = 3. Redo the table for8and for0, and state what each extreme is asserting about the physics. Which of the two is a defensible engineering bound and which is not? -
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. -
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?
-
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
Related tutorials
- Related topic
DDR2
A clock cycle has only two edges, so DDR2 raised bandwidth by deepening prefetch to 4n — making prefetch depth the frequency ratio between interface and core. Raising the rate on a multi-load bus then forced termination onto the die.
- Related topic
ODT — On-Die Termination Control
ODT is a control input selecting whether a device presents termination. Which device should terminate depends on which way data is flowing — and the one rule that never varies is that a driver must not terminate its own output.
- Related topic
DDR3
DDR3 took prefetch to 8n and paid twice: a fly-by command and clock bus that deliberately skews the clock at every device, and termination that had to calibrate itself against an external reference. Between them they made training mandatory.
- Related topic
Write Leveling
On a write the controller already centres the strobe in its data. The device imposes a second requirement — the strobe must meet CK — and on a fly-by module that relationship differs at every DRAM.
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.
