Ethernet · Module 16
What Limits Accuracy
Path asymmetry enters at exactly half the imbalance and leaves every variance unchanged; granularity survives four timestamps; and jitter compounds down a chain of clocks.
Four chapters have each removed a term and each removed it by moving it. Chapter 16.1 moved the capture below the software. Chapter 16.2 turned one equation into two. Chapter 16.3 turned a recomputation into a linear patch. Chapter 16.4 moved the path's delay into a field.
Three terms are left and none of them can be moved.
Path asymmetry enters at exactly half the imbalance — Chapter 16.2 §6 — and it is a bias, not noise. Which has a consequence that decides how the whole chapter is organised: it displaces every sample by the same amount, so it changes the mean of a measurement and leaves every variance, every spread and every moment about the mean exactly as they were. No statistic reveals it, and Section 5 proves that rather than asserting it.
Timestamp granularity survives four timestamps in a way that is worth knowing precisely. Chapter 16.1 §14 bounded one capture at period/√12; the offset combines four of them and divides by two, and √4 / 2 = 1 — so the offset's quantisation noise is exactly one capture's, neither better nor worse.
And jitter is what Chapter 16.4's servo reduces and does not remove, compounding down a chain of clocks each disciplining the next — as √N between independent stages and as N when they share a cause.
1. Scope — What This Chapter Owns
This chapter owns the residual: the exact form of the asymmetry error and why no statistic detects it, the granularity propagated through the full protocol, the jitter compounded down a chain, the calibration procedure, and the assembled budget.
It does not own the protocol. Chapter 16.2 derived the equations and named the symmetry assumption. This chapter takes the assumption's failure as its subject.
It does not own the timestamp unit. Chapter 16.3 placed the capture and built the in-flight rewrite. Section 13 stores a calibration into the field that unit writes.
It does not own the loop. Chapter 16.4 derived the bandwidth and built the transparent clock. Section 10 takes the servo's residual as one term of a budget.
And it does not own Chapter 17.1's question. A bounded latency and a bounded clock error are different requirements with a shared dependency; Section 25 says how they meet.
2. Three Terms Nothing Removes
State them together before pricing them, because the differences between them are what the chapter is about.
| Term | Kind | Magnitude, one hop | Reduced by |
|---|---|---|---|
| path asymmetry, uncalibrated | systematic | 25 ns — 10 m of fibre | calibration only |
| path asymmetry, calibrated to ±1 m | systematic | 2.5 ns | a better calibration |
| timestamp granularity, 156.25 MHz | random | 1.85 ns | a faster capture clock |
| transparent-clock residual, 1 hop | random | 1.31 ns | the same |
| PHY characterisation error | systematic | 2.5 ns | better characterisation |
| Chapter 4.4's elastic buffer | random | ~10 ns | nothing at this layer |
| Chapter 16.4's servo residual | random | 16.2 ns | a better oscillator or more samples |
The last row is the largest single term in a well-built system, which is worth noticing: at 10 ns of measurement noise and a 0.1 ppm oscillator, Chapter 16.4 §4's optimum is 16.2 ns, and that dominates everything except an uncalibrated asymmetry.
And the first row is the largest term overall and the only one that is invisible.
Combine them the way their kinds require — random in quadrature, systematic linearly:
| Uncalibrated | Calibrated | |
|---|---|---|
| random terms, in quadrature | 19.17 ns | 19.17 ns |
| systematic terms, summed | 27.5 ns | 5.0 ns |
| total | ≈46.7 ns | ≈24.2 ns |
Which is Chapter 16.1 §2's "tens of nanoseconds" arrived at from below, term by term — and it shows where the factor of two between a commissioned deployment and an uncommissioned one comes from. Not from better hardware; from a constant somebody measured once.
3. RTL 1 — The Asymmetry Model
A model rather than a mechanism, because the point is to make a quantity explicit that the protocol has no access to.
// -----------------------------------------------------------------------
// synacc_pkg -- shared types for the accuracy analysis.
// -----------------------------------------------------------------------
package synacc_pkg;
localparam int NS_W = 48;
typedef logic signed [NS_W-1:0] ns_t;
// Every error term is one of two kinds, and the kind decides how it
// combines and what removes it. Section 2.
typedef enum logic [0:0] { ERR_RANDOM = 1'b0, ERR_SYSTEMATIC = 1'b1 } err_kind_e;
typedef struct packed {
err_kind_e kind;
ns_t magnitude;
} term_t;
// A stored per-link calibration. Section 13.
typedef struct packed {
logic valid;
ns_t imbalance_ns; // d_sm - d_ms, signed
logic [15:0] link_mode; // the mode it was measured at -- 16.3 section 4
logic [31:0] measured_at_sec;
} calib_t;
endpackage// -----------------------------------------------------------------------
// asymmetry_model -- computes the offset error a given imbalance
// produces, and demonstrates that it is a constant displacement.
//
// This module exists to be instantiated in a testbench alongside the
// real datapath, so an experiment can impose a known imbalance and
// check that the observed error is exactly half of it. In silicon it
// is the calibration's applier -- section 13.
// -----------------------------------------------------------------------
module asymmetry_model
import synacc_pkg::*;
(
input logic clk,
input logic rst_n,
// The two one-way delays, known only to a testbench.
input logic sample_valid,
input ns_t d_ms,
input ns_t d_sm,
input ns_t true_offset,
// What 16.2 section 7 would compute from them.
output ns_t measured_offset,
output ns_t measured_path,
output ns_t offset_error,
output ns_t imbalance,
output logic error_is_exactly_half,
output logic [31:0] c_samples,
output logic [31:0] c_half_violations
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
measured_offset <= '0; measured_path <= '0;
offset_error <= '0; imbalance <= '0;
error_is_exactly_half <= 1'b0;
c_samples <= '0; c_half_violations <= '0;
end else if (sample_valid) begin
automatic ns_t imb, err;
imb = d_sm - d_ms;
// 16.2 equations (3) and (4), with the symmetry assumption
// applied. The path is EXACT; the offset is not.
measured_path <= (d_ms + d_sm) >>> 1;
measured_offset <= true_offset + (imb >>> 1);
imbalance <= imb;
err = imb >>> 1;
offset_error <= err;
// The chapter's central claim, checked rather than asserted:
// the error is EXACTLY half the imbalance, to the nanosecond,
// for any offset, any path length and any traffic.
error_is_exactly_half <= ((err <<< 1) == imb) ||
((err <<< 1) == (imb - 1)); // odd imbalance
if (!(((err <<< 1) == imb) || ((err <<< 1) == (imb - 1))))
c_half_violations <= c_half_violations + 1;
c_samples <= c_samples + 1;
end
end
endmoduleClassification: a reference model. It computes what the protocol would compute, from inputs the protocol cannot see.
What it teaches: that the measured path is exact and the measured offset is not, from the same four numbers. (d_ms + d_sm)/2 is a genuine measurement — Chapter 16.2 §6's equation (3) — and it is correct at any imbalance. measured_offset carries the whole error. A design that reports both and treats them as equally trustworthy has conflated a measurement with a model.
And it teaches that the error's exactness is a checkable property, which is unusual for an error. Most error terms are bounded or distributed; this one is (d_sm − d_ms)/2 to the nanosecond, for any offset, any path length and any traffic. c_half_violations should be zero forever, and a non-zero value means the datapath is not implementing Chapter 16.2 §7's arithmetic — which is a much stronger test than a tolerance.
Deliberately simplified: d_ms and d_sm are testbench inputs, and in silicon they do not exist. The module's silicon form is Section 13's applier, which holds a measured imbalance and subtracts it. The model and the applier share their arithmetic and differ entirely in where the imbalance comes from.
Production implication: the odd-imbalance case in the equality check is not pedantry. imb >>> 1 is an arithmetic shift and rounds toward negative infinity, so an imbalance of 51 ns gives an error of 25 ns and not 25.5 — a half-nanosecond systematic bias on top of the one being modelled. At these magnitudes it is negligible; at Chapter 16.1 §15's 10 ns target it is 5% of the budget, and round-to-nearest costs one adder.
4. The Asymmetry Error, Derived Exactly
Chapter 16.2 §6 produced the result in passing. It is worth deriving once more in isolation, because every claim in this chapter rests on the error's exactness rather than on its size.
From Chapter 16.2 §6's two equations:
t2 - t1 = d_ms + θ (1)
t4 - t3 = d_sm - θ (2)The protocol computes:
θ_measured = ((t2 - t1) - (t4 - t3)) / 2
= ((d_ms + θ) - (d_sm - θ)) / 2
= (d_ms - d_sm + 2θ) / 2
= θ + (d_ms - d_sm) / 2So:
θ_measured - θ_true = (d_ms - d_sm) / 2 = -(d_sm - d_ms) / 2Three properties of that expression matter and each one is used later.
It is exact. Not approximately half, not half plus a term — exactly half, algebraically, with no approximation anywhere in the derivation.
It is independent of everything else. The true offset does not appear. The path's total length does not appear — only the difference. The traffic, the message rate, the clock quality and the servo's gains do not appear. A 10 km symmetric fibre pair contributes zero and a 10 m mismatch contributes 25 ns.
And it is constant while the imbalance is. Which makes it calibratable, and is the entire basis of Section 11.
And the third property has a corollary that is the chapter's hinge: a constant error added to every sample is a bias, and a bias is invisible to every statistic computed about the mean.
| Statistic | With no bias | With a bias b |
|---|---|---|
| mean | θ | θ + b |
| median | θ | θ + b |
| variance | σ² | σ² — unchanged |
| standard deviation | σ | σ — unchanged |
| max − min | ≈6σ | ≈6σ — unchanged |
| any central moment | — | unchanged |
| Allan deviation | — | unchanged |
Every row after the second is unchanged, and every instrument this module has built measures one of them. Chapter 16.2 §15's path_spread_ns is a max minus a min. Chapter 16.4 §3's spread_ns is the same. Chapter 16.4 §14's achievable_ns is computed from a spread and a drift.
None of them moves when the bias appears, which is why Section 20's directed test is constructed the way it is and why Section 19 refuses the property it does.
5. RTL 2 — Why No Statistic Reveals It
Section 4 asserted that a bias leaves every central statistic unchanged. This module demonstrates it, because the claim is the reason the chapter exists and a demonstration is worth more than an argument.
// -----------------------------------------------------------------------
// bias_vs_noise_detector -- computes every statistic a monitoring
// system might use, on two sample streams differing only by a constant
// displacement, and reports which of them moved.
//
// The answer is: only the ones about the MEAN. Which is why a
// deployment can be 25 ns wrong with every dashboard green.
// -----------------------------------------------------------------------
module bias_vs_noise_detector
import synacc_pkg::*;
#(
parameter int WINDOW = 64
)(
input logic clk,
input logic rst_n,
input logic sample_valid,
input ns_t clean_sample, // offset with no bias
input ns_t biased_sample, // the same, plus a constant
output ns_t clean_mean, output ns_t biased_mean,
output ns_t clean_spread, output ns_t biased_spread,
output ns_t clean_var_x, output ns_t biased_var_x, // scaled variance
output logic spread_differs,
output logic variance_differs,
output logic mean_differs,
output logic [31:0] c_windows
);
ns_t cw [WINDOW];
ns_t bw [WINDOW];
logic [$clog2(WINDOW)-1:0] wp;
logic [$clog2(WINDOW):0] fill;
function automatic ns_t mean_of(input ns_t a [WINDOW]);
int i; logic signed [NS_W+8:0] s;
begin
s = '0;
for (i = 0; i < WINDOW; i++) s = s + a[i];
mean_of = ns_t'(s / WINDOW);
end
endfunction
function automatic ns_t spread_of(input ns_t a [WINDOW]);
int i; ns_t mx, mn;
begin
mx = a[0]; mn = a[0];
for (i = 1; i < WINDOW; i++) begin
if (a[i] > mx) mx = a[i];
if (a[i] < mn) mn = a[i];
end
spread_of = mx - mn;
end
endfunction
// Variance scaled by WINDOW to avoid a second divide. Any monotone
// function of the variance serves the comparison.
function automatic ns_t varx_of(input ns_t a [WINDOW]);
int i; ns_t m; logic signed [2*NS_W:0] s;
begin
m = mean_of(a); s = '0;
for (i = 0; i < WINDOW; i++) s = s + (a[i] - m) * (a[i] - m);
varx_of = ns_t'(s / WINDOW);
end
endfunction
always_ff @(posedge clk or negedge rst_n) begin
int i;
if (!rst_n) begin
for (i = 0; i < WINDOW; i++) begin cw[i] <= '0; bw[i] <= '0; end
wp <= '0; fill <= '0;
clean_mean <= '0; biased_mean <= '0;
clean_spread <= '0; biased_spread <= '0;
clean_var_x <= '0; biased_var_x <= '0;
spread_differs <= 1'b0; variance_differs <= 1'b0;
mean_differs <= 1'b0; c_windows <= '0;
end else if (sample_valid) begin
cw[wp] <= clean_sample;
bw[wp] <= biased_sample;
wp <= (wp == WINDOW-1) ? '0 : (wp + 1'b1);
if (fill != WINDOW) fill <= fill + 1'b1;
if ((wp == WINDOW-1) && (fill == WINDOW)) begin
clean_mean <= mean_of(cw); biased_mean <= mean_of(bw);
clean_spread <= spread_of(cw); biased_spread <= spread_of(bw);
clean_var_x <= varx_of(cw); biased_var_x <= varx_of(bw);
// The finding, computed rather than claimed.
mean_differs <= (mean_of(cw) != mean_of(bw));
spread_differs <= (spread_of(cw) != spread_of(bw));
variance_differs <= (varx_of(cw) != varx_of(bw));
c_windows <= c_windows + 1;
end
end
end
endmoduleClassification: an experiment in hardware. It computes nothing the design uses and settles the chapter's central question.
What it teaches: that spread_differs and variance_differs are always low and mean_differs is always high, for any bias and any noise distribution. The demonstration is not statistical — it is algebraic: subtracting a constant from every sample subtracts it from the mean and leaves every deviation from the mean unchanged, so every function of those deviations is identical. The module makes the algebra observable.
And it teaches why this matters operationally rather than as a curiosity. Every instrument Module 16 has built measures a deviation: Chapter 16.2 §15's path_spread_ns, Chapter 16.4 §3's spread_ns, §14's achievable_ns, and the performing_to_inputs judgement built on them. All of them read identically with and without a 25 ns bias — so a deployment can be systematically wrong with every green indicator it has.
Deliberately simplified: the variance is computed over a 64-sample window with a full second pass, which is O(N) twice and a 2N-bit multiply. A production design would use a streaming sum-of-squares; it is written this way because the comparison is the point and a streaming form obscures it.
Production implication: this module belongs in a testbench and not in silicon, and stating that is itself the production implication. There is no runtime instrument that detects a bias, because detecting one requires a second, independent measurement of the same quantity — which is Chapter 16.1 §19's instrument problem, arriving here in a different form. What silicon can do is apply a calibration; what it cannot do is discover one.
==
6. Sources of Asymmetry, Priced
Chapter 16.1 §15 listed these once without the protocol. With Chapter 16.4's transparent clocks in place, one row has vanished and the rest have not.
| Source | Imbalance | Offset error | With the protocol |
|---|---|---|---|
| fibre length mismatch, per metre | 5 ns | 2.5 ns | unchanged — calibratable |
| 10 m mismatch | 50 ns | 25 ns | unchanged |
| bidirectional fibre, 1310/1550 nm over 10 km | ~10 ns | 5 ns | unchanged |
| PHY TX vs RX pipeline difference | ~40 ns | 20 ns | unchanged — per mode |
| FEC enabled on one direction only | ~300 ns | 150 ns | a configuration error |
| uncorrected store-and-forward, 1 Gb/s | 12 140 ns | 6070 ns | REMOVED — Chapter 16.4 §13 |
The last row is the one Module 16 removed and it was by far the largest. What remains is between 2.5 ns and 150 ns per link, and the distribution across the rows is what makes calibration worthwhile: the large rows are constants of the hardware and the cabling, not of the traffic.
Row five deserves its own note because it is a configuration error that presents as an accuracy problem. FEC — Chapter 3.7 — adds hundreds of nanoseconds and adds them per direction. A link with FEC enabled on one side only is asymmetric by the whole FEC latency, which at 150 ns of offset error is six times a 10 m fibre mismatch. And it looks exactly like a cabling problem.
Row three is the one that surprises people. A bidirectional fibre carries both directions on one strand at two wavelengths, and the two wavelengths propagate at slightly different speeds — about 1 ns per kilometre of difference. So a single-strand link is asymmetric by construction, and a two-strand link with matched lengths is not. Which reverses the usual assumption that one fibre is simpler than two.
And the practical ranking that falls out is worth carrying:
| Rank | Source | Typical | Fix |
|---|---|---|---|
| 1 | asymmetric FEC or mode configuration | 150 ns | fix the configuration |
| 2 | PHY TX/RX difference | 20 ns | the vendor's figure, per mode |
| 3 | fibre length mismatch | 2.5 ns/m | match the lengths, or calibrate |
| 4 | bidirectional wavelength | 5 ns per 10 km | calibrate |
Rank one is free to fix and is checked by comparing two link configurations. Ranks two to four are what Section 11's calibration is for — and rank three is the only one an installer controls.
7. RTL 3 — Granularity, With the Protocol In Place
Chapter 16.1 §14 bounded one capture. The offset uses four, and the arithmetic has a coincidence in it worth knowing.
// -----------------------------------------------------------------------
// granularity_propagator -- propagates per-capture quantisation noise
// through 16.2's offset and path formulae, and through a chain of
// transparent clocks.
//
// The result is a small surprise: the offset combines FOUR independent
// quantisations and divides by two, and sqrt(4)/2 = 1 exactly -- so
// the offset's quantisation noise equals ONE capture's.
// -----------------------------------------------------------------------
module granularity_propagator
import synacc_pkg::*;
#(
parameter int CAPTURE_MHZ = 156 // 6.4 ns period
)(
input logic clk,
input logic rst_n,
input logic [7:0] n_transparent, // hops with a transparent clock
input logic eval,
output logic [15:0] per_capture_ps,
output logic [15:0] offset_sigma_ps,
output logic [15:0] path_sigma_ps,
output logic [15:0] chain_sigma_ps,
output logic [15:0] total_sigma_ps
);
// sigma of a uniform distribution over one period: period / sqrt(12).
// sqrt(12) = 3.4641, so period * 1000 / 3464 in picoseconds.
localparam int PERIOD_PS = 1_000_000 / CAPTURE_MHZ;
localparam int SIGMA_PS = (PERIOD_PS * 1000) / 3464;
// isqrt for the chain term. Small, and only evaluated on `eval`.
function automatic int unsigned isqrt(input int unsigned v);
int unsigned r, b;
begin
r = 0; b = 1 << 15;
while (b != 0) begin
if ((r + b) * (r + b) <= v) r = r + b;
b = b >> 1;
end
isqrt = r;
end
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
per_capture_ps <= '0; offset_sigma_ps <= '0;
path_sigma_ps <= '0; chain_sigma_ps <= '0; total_sigma_ps <= '0;
end else if (eval) begin
automatic int unsigned ch, tot;
per_capture_ps <= 16'(SIGMA_PS);
// offset = ((t2-t1) - (t4-t3)) / 2
// variance = (1/4) * 4 * sigma^2 = sigma^2
// => sigma_offset = sigma. The /2 exactly cancels the sqrt(4).
offset_sigma_ps <= 16'(SIGMA_PS);
// path = ((t2-t1) + (t4-t3)) / 2 -- identical structure.
path_sigma_ps <= 16'(SIGMA_PS);
// Each transparent clock adds two captures to the Sync path,
// and 16.2's formula halves them: sigma * sqrt(2N) / 2.
ch = (SIGMA_PS * isqrt(2 * 32'(n_transparent))) / 2;
chain_sigma_ps <= 16'(ch);
// Independent terms combine in quadrature.
tot = isqrt(SIGMA_PS*SIGMA_PS + ch*ch);
total_sigma_ps <= 16'(tot);
end
end
endmoduleClassification: an arithmetic model. It computes a budget, not a behaviour.
What it teaches: that the offset's quantisation noise equals exactly one capture's, which is neither the intuitive answer nor an approximation. Four independent quantisations combine as √4 = 2, and Chapter 16.2 §6's formula divides by 2 — so the two cancel exactly. A design that budgeted 4σ has over-estimated by four; one that budgeted σ/2 because of the division has under-estimated by two.
And it teaches that transparent clocks add granularity, which is the price of removing a much larger term. Each one contributes two more captures — ingress and egress SFDs — to the Sync's path only, so the chain term is σ√(2N)/2. At ten hops that is 4.13 ns against the endpoints' 1.85 ns, and the total is 4.53 ns. Chapter 16.4 §13 removed 6070 ns per hop and added 1.31 ns per hop; this is the same trade expressed as a noise budget.
Deliberately simplified: all captures are assumed to be at the same clock rate. A real path mixes 1 Gb/s access links at 125 MHz with 100 Gb/s cores at 644 MHz, so the per-hop terms differ by a factor of five — and the slow links dominate the quadrature sum, which is the opposite of where a designer's attention usually goes.
Production implication: the whole module is a handful of constants and one integer square root, and its value is that it turns "what capture clock do we need" into a number. A deployment targeting 20 ns with three transparent hops needs total_sigma_ps well inside that: at 125 MHz it is 3.6 ns and at 156.25 MHz it is 2.9 ns, both comfortable — so the answer is that the capture clock is not the constraint, which is worth knowing before specifying one.
==
8. Granularity Through Four Timestamps
Section 7's coincidence is worth a section because it is the only place in this module where an error term does not grow.
Each capture is quantised uniformly over one clock period, so its standard deviation is period/√12 — Chapter 16.1 §14.
The offset uses four of them:
θ = ((t2 - t1) - (t4 - t3)) / 2Four independent terms, each with variance σ², summed:
Var(numerator) = 4σ²
Var(θ) = 4σ² / 4 = σ²
σ_θ = σThe division by two contributes a factor of 1/4 to the variance and the four terms contribute a factor of 4. They cancel.
| Capture clock | Period | σ per capture | σ of the offset |
|---|---|---|---|
| 125 MHz | 8.00 ns | 2.31 ns | 2.31 ns |
| 156.25 MHz | 6.40 ns | 1.85 ns | 1.85 ns |
| 322.27 MHz | 3.10 ns | 0.90 ns | 0.90 ns |
| 644.53 MHz | 1.55 ns | 0.45 ns | 0.45 ns |
And the same structure applies to the path delay, which is the sum rather than the difference and has identical variance.
Which gives a clean statement: PTP's four-timestamp exchange costs nothing in quantisation noise compared with a hypothetical single perfect measurement. The protocol's shape — four timestamps, two equations, a division by two — is quantisation-neutral.
And the chain is where it grows:
| Transparent clocks | Chain term | Total σ at 156.25 MHz |
|---|---|---|
| 0 | 0 | 1.85 ns |
| 1 | 1.31 ns | 2.26 ns |
| 3 | 2.26 ns | 2.92 ns |
| 5 | 2.92 ns | 3.46 ns |
| 10 | 4.13 ns | 4.53 ns |
Ten transparent hops take the quantisation noise from 1.85 ns to 4.53 ns — a factor of 2.4 — while removing 60 700 ns of uncorrected asymmetry. Which is the trade stated in the units that make it obvious, and it is why nobody hesitates over it.
9. RTL 4 — Jitter and Its Propagation
The third term. It is the one a servo reduces, and the reduction is bounded by Chapter 16.4 §4's optimum rather than by effort.
// -----------------------------------------------------------------------
// jitter_chain_model -- propagates a per-stage residual down a chain of
// clocks, distinguishing the two ways stages combine.
//
// A TRANSPARENT clock forwards the exchange, so its errors are
// measurement noise on one path. A BOUNDARY clock terminates the
// exchange and re-originates it, so its residual becomes the next
// stage's REFERENCE error -- which compounds very differently.
// -----------------------------------------------------------------------
module jitter_chain_model
import synacc_pkg::*;
(
input logic clk,
input logic rst_n,
input logic eval,
input logic [15:0] stage_residual_ps, // 16.4 section 4's achievable
input logic [7:0] n_boundary,
input logic [7:0] n_transparent,
input logic stages_correlated, // shared oscillator, shared design
output logic [31:0] boundary_chain_ps,
output logic [31:0] transparent_chain_ps,
output logic [31:0] total_ps,
output logic boundary_dominates
);
function automatic int unsigned isqrt(input int unsigned v);
int unsigned r, b;
begin
r = 0; b = 1 << 15;
while (b != 0) begin
if ((r + b) * (r + b) <= v) r = r + b;
b = b >> 1;
end
isqrt = r;
end
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
boundary_chain_ps <= '0; transparent_chain_ps <= '0;
total_ps <= '0; boundary_dominates <= 1'b0;
end else if (eval) begin
automatic int unsigned b, t;
// A boundary clock's residual becomes the next stage's reference
// error. Independent designs combine in quadrature; identical
// designs sharing a systematic cause combine LINEARLY -- and a
// rack of identical switches is the second case.
b = stages_correlated
? (32'(stage_residual_ps) * 32'(n_boundary))
: (32'(stage_residual_ps) * isqrt(32'(n_boundary)));
// A transparent clock does not re-originate, so it contributes
// only measurement noise -- section 7's chain term.
t = 32'(stage_residual_ps) * isqrt(32'(n_transparent)) / 4;
boundary_chain_ps <= b;
transparent_chain_ps <= t;
total_ps <= isqrt(b*b + t*t);
boundary_dominates <= (b > t);
end
end
endmoduleClassification: an error-budget model with a correlation switch. The switch is the module's whole content.
What it teaches: that a boundary clock and a transparent clock compound completely differently, and the difference is architectural rather than a matter of degree. A transparent clock forwards the exchange and adds measurement noise — Section 7's √(2N)/2. A boundary clock terminates the exchange and becomes a master for the next segment, so its own residual is the reference error every downstream device inherits. Ten boundary clocks at 16.2 ns each give 51.2 ns if independent and 162 ns if they share a systematic cause.
And it teaches that stages_correlated is usually true and is usually assumed false. A fabric built from one vendor's switches, one PHY family, one oscillator part number has the same PHY mischaracterisation at every hop, the same temperature coefficient, and the same firmware — so the residuals are correlated and add linearly. Assuming independence under-reports the chain by √N, which at ten hops is a factor of 3.2.
Deliberately simplified: the correlation is a boolean where reality is a coefficient. A production budget uses a correlation factor between 0 and 1 and interpolates; the boolean is here because the decision it forces — "are these stages independent?" — is the one designers skip, and a boolean makes skipping it impossible.
Production implication: this model is what decides boundary or transparent for a fabric's design, and the answer is nearly always transparent. A transparent chain's error grows as √N of a small term; a boundary chain's grows as N of a large one — at ten hops, 4.53 ns against 162 ns. Boundary clocks exist for a different reason — they terminate a domain and reduce the master's load, Chapter 16.2 §18's 131 328 messages per second — and the accuracy cost of using them for depth is what this module prices.
==
10. Jitter Through a Chain of Clocks
Section 9's two scalings, as numbers, because the gap between them decides a fabric's topology.
A transparent clock forwards the exchange. The master is still the master; the slave still measures the whole path; the transparent clocks merely report what they cost.
A boundary clock terminates the exchange, disciplines its own clock as a slave, and then acts as a master for the next segment. So its residual becomes the next segment's reference error.
| Hops | Transparent — σ at 156.25 MHz | Boundary, independent | Boundary, correlated |
|---|---|---|---|
| 1 | 2.26 ns | 16.2 ns | 16.2 ns |
| 2 | 2.61 ns | 22.9 ns | 32.4 ns |
| 3 | 2.92 ns | 28.1 ns | 48.6 ns |
| 5 | 3.46 ns | 36.2 ns | 81.0 ns |
| 10 | 4.53 ns | 51.2 ns | 162 ns |
The gap at ten hops is a factor of 36 between transparent and correlated-boundary, and it is entirely structural: a transparent clock adds a measurement term and a boundary clock adds a whole servo's residual.
And the middle column's assumption — independence — is the one to interrogate. Two boundary clocks are independent if their residuals have unrelated causes. A rack of identical switches has: the same PHY family and therefore the same characterisation error; the same oscillator part and therefore the same temperature response; the same firmware and therefore the same servo tuning. Their residuals are correlated, and the right column applies.
Which gives the fabric rule: use transparent clocks for depth and boundary clocks for scale. Depth is accuracy — Section 10's table. Scale is the master's message load — Chapter 16.2 §18's 131 328 messages per second at a thousand slaves, which a boundary clock terminates and a transparent clock does not.
And the two can be combined, which is what large deployments do: transparent clocks within a pod, a boundary clock at the pod's edge to terminate the message load, and the accuracy cost paid once per pod rather than once per hop.
11. RTL 5 — The Calibration Procedure
Section 5 established that no runtime instrument discovers a bias. A commissioning procedure can, and this is the mechanism that makes one possible.
// -----------------------------------------------------------------------
// calibration_engine -- measures a link's asymmetry against a reference
// whose symmetry is known, and produces a constant to store.
//
// The reference is the whole procedure: a loopback, or a second path
// known to be symmetric. Nothing inside the protocol supplies one --
// 16.2 section 8's table -- so this module's inputs come from outside
// the network.
// -----------------------------------------------------------------------
module calibration_engine
import synacc_pkg::*;
#(
parameter int SAMPLES = 256,
parameter int STABLE_NS = 50 // the path must be this quiet
)(
input logic clk,
input logic rst_n,
input logic start,
input logic abort,
// The measured offset while a KNOWN-symmetric reference is in place.
input logic sample_valid,
input ns_t measured_offset,
input ns_t path_spread, // 16.2 section 15
// The offset the reference says is true. For a loopback this is
// zero by construction; for a transfer standard it is its reading.
input ns_t reference_offset,
input logic reference_is_symmetric,
output logic busy,
output logic done,
output logic failed,
output ns_t imbalance_ns, // d_sm - d_ms, to be stored
output ns_t residual_spread_ns,
output logic [15:0] samples_taken,
output logic [31:0] c_calibrations,
output logic [31:0] c_aborted_unstable
);
logic signed [NS_W+16:0] acc;
ns_t mx, mn;
logic [15:0] n;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
busy <= 1'b0; done <= 1'b0; failed <= 1'b0;
acc <= '0; n <= '0; mx <= '0; mn <= '0;
imbalance_ns <= '0; residual_spread_ns <= '0;
samples_taken <= '0; c_calibrations <= '0;
c_aborted_unstable <= '0;
end else begin
done <= 1'b0;
failed <= 1'b0;
if (start && !busy) begin
// Gate: the procedure is meaningless without a reference whose
// symmetry is known. There is no way to bootstrap one from
// inside the protocol.
if (!reference_is_symmetric) begin
failed <= 1'b1;
end else begin
busy <= 1'b1; acc <= '0; n <= '0;
mx <= '0; mn <= '0;
end
end else if (busy) begin
if (abort) begin
busy <= 1'b0; failed <= 1'b1;
end else if (sample_valid) begin
// A moving path has a moving asymmetry, so a constant
// measured on it is not a constant -- 16.2 section 15's
// path_is_stable, as a precondition rather than a judgement.
if (path_spread > ns_t'(STABLE_NS)) begin
busy <= 1'b0;
failed <= 1'b1;
c_aborted_unstable <= c_aborted_unstable + 1;
end else begin
acc <= acc + (measured_offset - reference_offset);
if ((n == 0) || (measured_offset > mx)) mx <= measured_offset;
if ((n == 0) || (measured_offset < mn)) mn <= measured_offset;
n <= n + 1'b1;
if (n == SAMPLES-1) begin
// The mean displacement IS half the imbalance -- section
// 4. So the imbalance is twice the mean displacement.
imbalance_ns <= ns_t'((acc / SAMPLES) <<< 1);
residual_spread_ns <= mx - mn;
samples_taken <= n + 1'b1;
busy <= 1'b0;
done <= 1'b1;
c_calibrations <= c_calibrations + 1;
end
end
end
end
end
end
endmoduleClassification: an averaging measurement with two preconditions. It is the only place in Module 16 where averaging is the right tool.
What it teaches: that averaging is correct here and wrong everywhere else in the module, and the reason is what is being measured. Chapter 16.2 §22's fourth misconception and Chapter 16.4 §22's third both warn that averaging cannot remove a bias. This module is measuring the bias — so the noise is the nuisance and the mean is the signal, which is exactly the case averaging is for. Two hundred and fifty-six samples reduce the noise by sixteen.
And it teaches that reference_is_symmetric is a gate with no software implementation. Chapter 16.2 §8's table: no amount of protocol activity separates d_ms from d_sm. The reference must come from outside — a physical loopback whose two directions traverse the same fibre, a calibrated transfer standard, or a measurement of the cable itself. A design that lets the procedure run without one produces a confidently wrong constant.
Deliberately simplified: reference_offset is an input, and for the common case — a loopback — it is zero by construction, because a frame returning on the same path has a true offset of zero. The loopback case is the one worth building, because it needs no external instrument at all and it measures exactly the PHY and connector asymmetry that Section 6 ranked second.
Production implication: residual_spread_ns is what says whether the calibration is worth storing. A spread of 200 ns over 256 samples means the mean is uncertain by 12.5 ns, which on a 25 ns bias is half the quantity being measured. The procedure must report its own uncertainty, and a deployment that stores a constant without one has stored a number it cannot defend.
12. What a Commissioned Deployment Does
Section 11 is a mechanism. This is the procedure it belongs to, stated as the sequence a commissioning engineer actually runs.
| Step | What it measures | Reference |
|---|---|---|
| 1 — verify the configuration | FEC and mode symmetric on both ends | the configuration itself |
| 2 — record the PHY figures | TX and RX pipeline, per mode | the vendor's characterisation |
| 3 — loopback each port | the port's own TX/RX asymmetry | a physical loopback — offset is zero |
| 4 — measure or record cable lengths | the fibre or copper mismatch | an OTDR, or the installation record |
| 5 — store the per-link constant | the sum of 2, 3 and 4 | — |
| 6 — verify against a transfer standard | the whole path | a calibrated portable clock |
| 7 — record the date and the link mode | so Chapter 16.3 §4's stale check can fire | — |
Step 1 is free and catches the largest single source — Section 6's rank one, 150 ns of offset error from FEC enabled on one direction only. It requires no instrument and it is skipped constantly.
Step 3 is the one that does real work with no external equipment. A loopback's two directions traverse the same fibre, so the path is symmetric by construction and the true offset is zero — any measured offset is the port's own asymmetry, which is Section 6's rank two at 20 ns.
Step 4 is where an installer's discipline shows up as nanoseconds. Two fibres cut from the same reel to the same length are symmetric; two cut to convenient lengths differ by metres, and each metre is 2.5 ns.
Step 7 is the one that is always forgotten and it is what makes the calibration maintainable. A constant measured at 10 Gb/s with FEC is wrong at 1 Gb/s without it — Chapter 16.3 §4's stale-correction case — so the stored record must carry the mode it was measured at, and a link that renegotiates must invalidate it rather than silently using it.
And the difference the whole procedure makes is Section 2's last table: 46.7 ns uncalibrated against 24.2 ns calibrated, a factor of nearly two, from measurements that take an afternoon and a constant that fits in eight octets.
Which is the honest characterisation of PTP deployment and it is not a technology statement. The protocol, the timestamp unit, the servo and the transparent clocks are all engineering. The last factor of two is a procedure, and a deployment that skipped it has hardware it is not using.
13. RTL 6 — Storing and Applying a Calibration
A constant is worthless unless it is applied to the right link in the right mode. This module is the bookkeeping that makes it so.
// -----------------------------------------------------------------------
// calibration_store -- holds a per-link asymmetry constant with the
// conditions under which it was measured, and refuses to apply it when
// those conditions no longer hold.
//
// The value goes into 16.4 section 12's correction accumulator, which
// writes it into correctionField via 16.3 section 10's rewriter. So a
// calibration measured once at commissioning is applied by hardware in
// the datapath, invisibly to the slave's arithmetic.
// -----------------------------------------------------------------------
module calibration_store
import synacc_pkg::*;
#(
parameter int NUM_PORTS = 24
)(
input logic clk,
input logic rst_n,
input logic wr_en,
input logic [7:0] wr_port,
input ns_t wr_imbalance_ns,
input logic [15:0] wr_link_mode,
input logic [31:0] wr_timestamp_sec,
input logic [7:0] q_port,
input logic [15:0] q_link_mode, // the mode the link is in NOW
output logic cal_valid,
output ns_t cal_egress_ns, // HALF the imbalance, one side
output logic cal_stale,
output logic [31:0] c_applied,
output logic [31:0] c_stale_refused,
output logic [31:0] c_missing
);
calib_t tbl [NUM_PORTS];
always_ff @(posedge clk or negedge rst_n) begin
int i;
if (!rst_n) begin
for (i = 0; i < NUM_PORTS; i++) tbl[i] <= '0;
cal_valid <= 1'b0; cal_egress_ns <= '0; cal_stale <= 1'b0;
c_applied <= '0; c_stale_refused <= '0; c_missing <= '0;
end else begin
if (wr_en) begin
tbl[wr_port].valid <= 1'b1;
tbl[wr_port].imbalance_ns <= wr_imbalance_ns;
tbl[wr_port].link_mode <= wr_link_mode;
tbl[wr_port].measured_at_sec <= wr_timestamp_sec;
end
cal_valid <= 1'b0;
cal_stale <= 1'b0;
if (!tbl[q_port].valid) begin
c_missing <= c_missing + 1;
end
// 16.3 section 4's operational trap: a link that renegotiated,
// or enabled FEC, has invalidated its constant. Applying it
// anyway is worse than applying none, because it is confidently
// wrong rather than absent.
else if (tbl[q_port].link_mode != q_link_mode) begin
cal_stale <= 1'b1;
c_stale_refused <= c_stale_refused + 1;
end
else begin
// The stored value is the IMBALANCE. The correction applied on
// one side is HALF of it -- section 4's derivation. Applying
// the whole imbalance is a factor-of-two error that looks like
// a correct calibration of a different cable.
cal_valid <= 1'b1;
cal_egress_ns <= tbl[q_port].imbalance_ns >>> 1;
c_applied <= c_applied + 1;
end
end
end
endmoduleClassification: a small indexed store with a validity predicate. Twenty-four entries and one comparison.
What it teaches: that the stored value is the imbalance and the applied value is half of it, and conflating them is a factor-of-two error that hides perfectly. Section 4: the offset error is (d_sm − d_ms)/2. A store that applies the whole imbalance over-corrects by exactly the amount it was correcting — so a 50 ns imbalance becomes a 25 ns error in the opposite direction, which looks like a correct calibration of a cable 10 m mismatched the other way.
And it teaches that refusing a stale calibration is better than applying one. A missing constant leaves the known error in place — 25 ns, and an operator who knows the link is uncalibrated. A stale constant applies a correction measured under different conditions, which on a link that renegotiated from 10 Gb/s with FEC to 1 Gb/s without it is hundreds of nanoseconds in an arbitrary direction. c_stale_refused makes the refusal visible, and cal_stale is what a dashboard should alarm on.
Deliberately simplified: one constant per port rather than one per port per mode. A production store is indexed by (port, mode), so a link that renegotiates finds a constant measured for the mode it is now in — which is the difference between a calibration that survives an auto-negotiation and one that is invalidated by it.
Production implication: measured_at_sec is stored and never read by this module, and that is deliberate: its consumer is a human. A calibration measured three years ago on a link that has since been re-patched twice is technically valid and practically fiction, and the only mechanism that catches it is an operator seeing the date. The field costs four octets and it is the difference between a calibration regime and a one-off.
14. RTL 7 — Accuracy Telemetry
Six numbers, and the useful ones report the budget rather than the error — because Section 5 established that the error's largest term is not observable.
// -----------------------------------------------------------------------
// accuracy_telemetry -- reports the assembled error budget and which
// of its terms are known, so an operator can tell a measured error
// from an unmeasured one.
//
// It cannot report the achieved accuracy. Nothing can -- 16.1 section
// 19. What it can report is the budget's composition and whether the
// terms that are removable have been removed.
// -----------------------------------------------------------------------
module accuracy_telemetry
import synacc_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] granularity_ps, // section 7
input logic [15:0] servo_residual_ps, // 16.4 section 14
input logic [15:0] elastic_buffer_ps,
input logic [15:0] tc_chain_ps, // section 7's chain term
input ns_t cal_imbalance_ns, // section 13, if valid
input logic cal_valid,
input logic cal_stale,
input logic [7:0] uncorrected_hops,
input logic [15:0] phy_char_error_ps,
input logic eval,
output logic [31:0] random_budget_ps,
output logic [31:0] systematic_budget_ps,
output logic [31:0] total_budget_ps,
output logic [31:0] removable_remaining_ps,
output logic all_removable_removed,
output logic [7:0] dominant_term // an index, for a dashboard
);
function automatic int unsigned isqrt(input int unsigned v);
int unsigned r, b;
begin
r = 0; b = 1 << 15;
while (b != 0) begin
if ((r + b) * (r + b) <= v) r = r + b;
b = b >> 1;
end
isqrt = r;
end
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
random_budget_ps <= '0; systematic_budget_ps <= '0;
total_budget_ps <= '0; removable_remaining_ps <= '0;
all_removable_removed <= 1'b0; dominant_term <= '0;
end else if (eval) begin
automatic int unsigned r, s, rem;
automatic int unsigned g, sv, eb, tc;
g = 32'(granularity_ps); sv = 32'(servo_residual_ps);
eb = 32'(elastic_buffer_ps); tc = 32'(tc_chain_ps);
// Random terms combine in quadrature.
r = isqrt(g*g + sv*sv + eb*eb + tc*tc);
// Systematic terms sum linearly -- section 2, and 16.4 section
// 13's scaling argument.
s = 32'(phy_char_error_ps);
// An uncalibrated or stale link contributes half its imbalance.
if (!cal_valid || cal_stale)
s = s + 32'(cal_imbalance_ns) * 500; // ns/2 -> ps
// Every uncorrected store-and-forward hop contributes 6070 ns.
s = s + 32'(uncorrected_hops) * 6_070_000;
random_budget_ps <= r;
systematic_budget_ps <= s;
total_budget_ps <= r + s;
// What is still removable: uncorrected hops and an absent or
// stale calibration. The rest is structural.
rem = 32'(uncorrected_hops) * 6_070_000;
if (!cal_valid || cal_stale) rem = rem + 32'(cal_imbalance_ns) * 500;
removable_remaining_ps <= rem;
all_removable_removed <= (rem == 0);
// Which term to point a dashboard at.
dominant_term <= (uncorrected_hops != 0) ? 8'd1 // hops
: (!cal_valid || cal_stale) ? 8'd2 // calibration
: (sv > eb && sv > g) ? 8'd3 // servo
: (eb > g) ? 8'd4 // elastic buffer
: 8'd5; // granularity
end
end
endmoduleClassification: a budget assembler with a dominant-term selector. It reports what is knowable and refuses to report what is not.
What it teaches: that all_removable_removed is the only actionable accuracy indicator a device can produce, and it is a statement about the deployment rather than about the error. Uncorrected hops and a missing calibration are removable; granularity, the elastic buffer and the servo's residual are not. A device reporting all_removable_removed high has nothing left to fix that does not involve new hardware.
And it teaches that dominant_term is worth more than the total. A budget of 46.7 ns says little; "the dominant term is an uncorrected hop, worth 6070 ns" says exactly what to do — and the ordering in the selector is deliberately by removability rather than by size, because a 6070 ns term that can be fixed today ranks above a 16.2 ns one that cannot.
Deliberately simplified: the random terms are summed in quadrature as though independent. Chapter 16.4 §14's servo residual already contains the granularity that Section 7 also counts, so the sum double-counts slightly — which is conservative and therefore acceptable for a floor, and a production budget would separate the terms properly.
Production implication: the module deliberately has no output for "the achieved accuracy", and that absence is the design decision. Chapter 16.1 §19 established that a device cannot measure its own clock's error; Section 5 established that its largest remaining term is invisible to every statistic. A telemetry block that produced an accuracy number would be producing a number it could not defend — and an operator would believe it.
15. RTL 8 — Conformance for a Calibrated Link
The monitor checks that the calibration regime is sound. It cannot check that the calibration is right.
// -----------------------------------------------------------------------
// accuracy_conformance_monitor -- one bit.
//
// It asserts that every removable term has been removed and that the
// calibration was applied correctly. It says nothing about the
// achieved accuracy -- section 19's rejected property is the version
// that tries to.
// -----------------------------------------------------------------------
module accuracy_conformance_monitor
import synacc_pkg::*;
(
input logic clk,
input logic rst_n,
input logic whole_imbalance_applied, // should be half -- section 13
input logic stale_calibration_applied,
input logic calibrated_on_unstable_path,
input logic calibrated_without_reference,
input logic asymmetric_fec_configured,
input logic uncorrected_hop_in_path,
input logic cal_missing,
input logic cal_uncertainty_unrecorded,
output logic conformant,
output logic [15:0] fault_vector,
output logic [31:0] c_violations
);
logic v_whole, v_stale, v_unstable, v_noref;
logic v_fec, v_hop, v_missing, v_unc;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
v_whole <= 1'b0; v_stale <= 1'b0; v_unstable <= 1'b0;
v_noref <= 1'b0; v_fec <= 1'b0; v_hop <= 1'b0;
v_missing <= 1'b0; v_unc <= 1'b0; c_violations <= '0;
end else begin
// A factor-of-two error that looks like a correct calibration of
// a different cable -- section 13. Sticky: one occurrence is the
// finding.
if (whole_imbalance_applied) begin v_whole <= 1'b1; c_violations <= c_violations + 1; end
if (stale_calibration_applied) begin v_stale <= 1'b1; c_violations <= c_violations + 1; end
if (calibrated_on_unstable_path) begin v_unstable <= 1'b1; c_violations <= c_violations + 1; end
if (calibrated_without_reference) begin v_noref <= 1'b1; c_violations <= c_violations + 1; end
// Standing configuration properties: removable terms that have
// not been removed. These are wrong from deployment, not from a
// measurement -- 14.1 section 17's argument.
v_fec <= asymmetric_fec_configured;
v_hop <= uncorrected_hop_in_path;
v_missing <= cal_missing;
v_unc <= cal_uncertainty_unrecorded;
end
end
assign conformant = !(v_whole || v_stale || v_unstable || v_noref ||
v_fec || v_hop || v_missing || v_unc);
assign fault_vector = {8'b0, v_unc, v_missing, v_hop, v_fec,
v_noref, v_unstable, v_stale, v_whole};
endmoduleClassification: a sticky aggregator with four procedural violations and four standing deployment terms.
What it teaches: that calibrated_without_reference is the procedural fault that produces the most confident wrong answer. Section 11's gate exists because Chapter 16.2 §8's table proves no protocol activity separates the two one-way delays — so a calibration run without a known-symmetric reference measures the asymmetry against itself and produces zero. The stored constant is then zero, which is indistinguishable from an uncalibrated link except that an operator believes it has been calibrated.
And it teaches that cal_uncertainty_unrecorded is a fault rather than a nicety. Section 11's residual_spread_ns over 256 samples gives the mean's uncertainty; a calibration whose uncertainty exceeds the bias it measured has measured nothing. Storing a constant without its uncertainty removes the only evidence that would have said so, and the number is then defended by nobody and trusted by everybody.
Deliberately simplified: uncorrected_hop_in_path is an input and discovering it requires knowing the path, which a slave does not. In practice it is inferred: Chapter 16.2 §15's mean_path_ns far exceeding the cable's known length means queueing, and queueing that is not being corrected means an uncorrected hop. The inference is sound and the direct observation is not available.
Production implication: conformant here means the deployment is sound — every removable term removed, every calibration valid, current and correctly halved. It does not mean the clock is right, because Chapter 16.1 §19's instrument problem and Section 5's invisibility both still hold. What it does mean is stronger than it sounds: a non-conformant deployment has something to fix, and a conformant one has reached the floor Section 16 assembles.
16. The Error Budget, Assembled
Every term Module 16 has produced, in one table, combined the way its kind requires.
| Term | Kind | Magnitude | Source |
|---|---|---|---|
| timestamp granularity, 156.25 MHz | random | 1.85 ns | Section 8 |
| transparent-clock chain, 1 hop | random | 1.31 ns | Section 8 |
| Chapter 4.4's elastic buffer | random | ~10 ns | Chapter 16.3 §4 |
| Chapter 16.4's servo residual | random | 16.2 ns | Chapter 16.4 §4 |
| random, in quadrature | — | 19.17 ns | — |
| PHY characterisation error | systematic | 2.5 ns | Chapter 16.3 §4 |
| asymmetry, uncalibrated 10 m | systematic | 25 ns | Section 6 |
| asymmetry, calibrated to ±1 m | systematic | 2.5 ns | Section 11 |
| TOTAL, uncalibrated | — | ≈46.7 ns | — |
| TOTAL, calibrated | — | ≈24.2 ns | — |
Row four is the largest random term and it is Chapter 16.4's optimum, not a shortfall — 16.2 ns is the best a 3 Hz loop achieves with 10 ns of measurement noise and a 0.1 ppm oscillator. Improving it means more samples or a better oscillator, and both move along Chapter 16.4 §4's curve rather than off it.
Row seven is the largest term overall and it is removable by a procedure, which is the chapter's practical conclusion: the difference between 46.7 ns and 24.2 ns is an afternoon and eight octets.
And the composition is worth reading as a ranking of where effort goes:
| Effort | Buys |
|---|---|
| calibrate the links | 25 ns → 2.5 ns — the largest single win |
| fix asymmetric FEC | up to 150 ns — free |
| a better oscillator | the servo residual, 16.2 → less |
| more Sync messages | the same term, along Chapter 16.4 §4's curve |
| a faster capture clock | 1.85 → 0.90 ns — the smallest term |
| a better PHY characterisation | 2.5 ns, and it scales as N |
The last row's N scaling is why it ranks above its size — Chapter 16.4 §13: systematic terms add linearly, so 2.5 ns per hop is 25 ns at ten hops while the granularity term is 4.53 ns there.
And the fifth row is the one most often attacked first and it is the least valuable.
==
17. What Accuracy Can and Cannot Be Claimed
| Claim | Status |
|---|---|
| the round-trip delay is exact | guaranteed — Chapter 16.2 §6, no premise |
| the offset error is exactly half the imbalance | guaranteed — Section 4, algebraically |
| the offset's quantisation noise equals one capture's | guaranteed — Section 8 |
| a bias leaves every central statistic unchanged | guaranteed — Section 5 |
| every removable term has been removed | checkable — Section 15 |
| the achieved accuracy | not measurable by the device |
| the calibration is correct | only to its recorded uncertainty |
| the deployment meets its requirement | a budget, not a measurement |
Rows two and four are the chapter's two theorems and they are both exact rather than approximate, which is unusual for error analysis and is what makes them useful. "The error is exactly half the imbalance" is a specification a testbench can check to the nanosecond. A bias changes no central statistic is algebra, not a tendency.
Row six is Chapter 16.1 §19's instrument problem, unchanged by four chapters of work. And Section 5 adds to it: not only can the device not measure its error, it cannot even detect the presence of the largest component.
Which leaves row eight as the honest form of every accuracy claim about a PTP deployment. A budget is assembled from terms whose magnitudes are known — granularity from a clock period, the servo residual from Chapter 16.4 §4's formula, the asymmetry from a calibration with a stated uncertainty — and the claim is that the total is below the requirement. It is an engineering argument, not a measurement, and a deployment that cannot produce the argument has not established the claim by running the protocol.
18. The Cost, Accounted
| Component | Cost | Note |
|---|---|---|
| Section 3's asymmetry model | testbench only | it needs inputs silicon lacks |
| Section 5's bias detector | testbench only | and saying so is the point |
| Section 7's granularity propagator | a few constants, one isqrt | evaluated once |
| Section 9's chain model | the same | once |
| Section 11's calibration engine | an accumulator and two gates | run at commissioning |
| Section 13's calibration store | 24 × 14 octets = 336 octets | the constant, the mode, the date |
| Section 14's telemetry | ≈30 flops | — |
| total in silicon | ≈400 octets | — |
| what it buys | 46.7 ns → 24.2 ns | a factor of 1.9 |
Two of the modules are explicitly testbench-only, and that is the chapter's structural point rather than an omission. Section 3 needs d_ms and d_sm, which no device holds. Section 5 needs two sample streams differing only by a bias, which requires knowing the bias. Both are experiments that establish facts the silicon then acts on.
And Module 16's four chapters, in proportion:
| Chapter | Silicon cost | What it removed |
|---|---|---|
| Chapter 16.2 | ≈220 octets | the unknown path delay |
| Chapter 16.3 | ≈2500 XOR2 + 400 flops | the delivery latency of t1 |
| Chapter 16.4 | ≈300 octets + a multiply | 6070 ns per hop |
| this chapter | ≈400 octets | 25 ns of asymmetry — by procedure |
The whole module is under a kilobyte of state plus one datapath block, and its total effect is Chapter 16.1 §18's table: from ~10 ms of software-timestamp jitter to ~24 ns of assembled budget. A factor of four hundred thousand, of which the last factor of two is a commissioning procedure.
19. Properties Worth Asserting, and One Worth Refusing
The properties divide by term: the asymmetry, the granularity, the chain, the calibration, the store, and the budget.
Group 1 — the asymmetry.
// P1. The measured PATH is exact at any imbalance. This is 16.2's
// equation (3) and it carries no premise.
property p_path_is_exact;
@(posedge clk) disable iff (!rst_n)
sample_valid |=> (measured_path == ((d_ms + d_sm) >>> 1));
endproperty
// P2. The offset error is EXACTLY half the imbalance -- to the
// nanosecond, for any offset, path length or traffic.
property p_error_is_exactly_half;
@(posedge clk) disable iff (!rst_n)
sample_valid |=> (offset_error == ((d_sm - d_ms) >>> 1));
endproperty
// P3. And it does not depend on the true offset.
property p_error_independent_of_offset;
@(posedge clk) disable iff (!rst_n)
($stable(d_ms) && $stable(d_sm)) |=> $stable(offset_error);
endproperty
// P4. Nor on the path's total length -- only on the difference.
property p_error_independent_of_length;
@(posedge clk) disable iff (!rst_n)
($stable(d_sm - d_ms)) |=> $stable(offset_error);
endproperty
// P5. A symmetric path gives an exact offset.
property p_symmetric_is_exact;
@(posedge clk) disable iff (!rst_n)
(sample_valid && (d_ms == d_sm)) |=> (offset_error == 0);
endpropertyGroup 2 — bias against noise.
// P6. A bias changes the mean.
property p_bias_moves_the_mean;
@(posedge clk) disable iff (!rst_n)
window_tick |-> (biased_mean == (clean_mean + bias));
endproperty
// P7. And changes NO spread. This is the chapter's hinge.
property p_bias_leaves_spread;
@(posedge clk) disable iff (!rst_n)
window_tick |-> (biased_spread == clean_spread);
endproperty
// P8. Nor the variance.
property p_bias_leaves_variance;
@(posedge clk) disable iff (!rst_n)
window_tick |-> (biased_var_x == clean_var_x);
endproperty
// P9. So every spread-based indicator reads identically.
property p_indicators_are_blind;
@(posedge clk) disable iff (!rst_n)
window_tick |-> (!spread_differs && !variance_differs);
endpropertyGroup 3 — granularity.
// P10. The offset's quantisation sigma equals ONE capture's --
// sqrt(4)/2 = 1, exactly.
property p_offset_sigma_equals_capture;
@(posedge clk) disable iff (!rst_n)
eval |=> (offset_sigma_ps == per_capture_ps);
endproperty
// P11. And so does the path's -- same structure, opposite sign.
property p_path_sigma_equals_capture;
@(posedge clk) disable iff (!rst_n)
eval |=> (path_sigma_ps == per_capture_ps);
endproperty
// P12. Sigma is period/sqrt(12) -- a constant of the uniform
// distribution, not something measured.
property p_sigma_is_uniform;
@(posedge clk) disable iff (!rst_n)
(SIGMA_PS == ((PERIOD_PS * 1000) / 3464));
endproperty
// P13. The chain term grows as sqrt(N) -- random terms in quadrature.
property p_chain_grows_as_sqrt;
@(posedge clk) disable iff (!rst_n)
(eval && (n_transparent == 4*k)) |=> (chain_sigma_ps == 2 * chain_at(k));
endproperty
// P14. Zero transparent clocks contribute zero.
property p_zero_hops_zero_chain;
@(posedge clk) disable iff (!rst_n)
(eval && (n_transparent == 0)) |=> (chain_sigma_ps == 0);
endpropertyGroup 4 — the chain.
// P15. Boundary clocks compound as N when correlated and sqrt(N)
// when not. The switch is the module's whole content.
property p_correlated_adds_linearly;
@(posedge clk) disable iff (!rst_n)
(eval && stages_correlated) |=>
(boundary_chain_ps == (stage_residual_ps * n_boundary));
endproperty
property p_independent_adds_in_quadrature;
@(posedge clk) disable iff (!rst_n)
(eval && !stages_correlated) |=>
(boundary_chain_ps == (stage_residual_ps * isqrt(n_boundary)));
endproperty
// P16. A transparent chain is always smaller than a boundary chain of
// the same depth -- the architectural result.
property p_transparent_beats_boundary;
@(posedge clk) disable iff (!rst_n)
(eval && (n_transparent == n_boundary) && (n_boundary > 0))
|=> (transparent_chain_ps < boundary_chain_ps);
endpropertyGroup 5 — the calibration.
// P17. A calibration without a known-symmetric reference is refused.
// There is no way to bootstrap one from inside the protocol.
property p_needs_a_reference;
@(posedge clk) disable iff (!rst_n)
(start && !reference_is_symmetric) |=> (failed && !busy);
endproperty
// P18. A calibration on a moving path is aborted -- a moving path has
// a moving asymmetry and the constant would not be one.
property p_needs_a_stable_path;
@(posedge clk) disable iff (!rst_n)
(busy && sample_valid && (path_spread > ns_t'(STABLE_NS)))
|=> (failed && !busy);
endproperty
// P19. The produced imbalance is TWICE the mean displacement --
// section 4 inverted.
property p_imbalance_is_twice_the_mean;
@(posedge clk) disable iff (!rst_n)
done |-> (imbalance_ns == ((acc / SAMPLES) <<< 1));
endproperty
// P20. And its uncertainty is always recorded alongside it.
property p_uncertainty_recorded;
@(posedge clk) disable iff (!rst_n)
done |-> (residual_spread_ns != 'x);
endpropertyGroup 6 — the store and the budget.
// P21. The APPLIED correction is HALF the stored imbalance. Applying
// the whole is a factor-of-two error that looks like a correct
// calibration of a different cable.
property p_applies_half;
@(posedge clk) disable iff (!rst_n)
cal_valid |-> (cal_egress_ns == (tbl[q_port].imbalance_ns >>> 1));
endproperty
// P22. A calibration measured in a different link mode is refused,
// not applied. 16.3 section 4's stale-correction trap.
property p_stale_is_refused;
@(posedge clk) disable iff (!rst_n)
(tbl[q_port].valid && (tbl[q_port].link_mode != q_link_mode))
|=> (cal_stale && !cal_valid);
endproperty
// P23. A missing calibration is reported, not silently zero.
property p_missing_is_reported;
@(posedge clk) disable iff (!rst_n)
(!tbl[q_port].valid) |=> ($changed(c_missing) && !cal_valid);
endproperty
// P24. Random terms combine in quadrature.
property p_random_in_quadrature;
@(posedge clk) disable iff (!rst_n)
eval |=> (random_budget_ps == isqrt(g*g + sv*sv + eb*eb + tc*tc));
endproperty
// P25. Systematic terms combine linearly. Adding them in quadrature
// under-reports a deep path by sqrt(N).
property p_systematic_linearly;
@(posedge clk) disable iff (!rst_n)
eval |=> (systematic_budget_ps ==
(phy_char_error_ps + uncal_term + hop_term));
endproperty
// P26. all_removable_removed is exactly "no uncorrected hops and a
// valid, current calibration".
property p_removable_definition;
@(posedge clk) disable iff (!rst_n)
all_removable_removed <-> ((uncorrected_hops == 0) &&
cal_valid && !cal_stale);
endproperty
// P27. The dominant term is ranked by REMOVABILITY, not by size --
// a 6070 ns term that can be fixed today outranks a 16.2 ns one that
// cannot.
property p_dominant_ranks_removable_first;
@(posedge clk) disable iff (!rst_n)
(eval && (uncorrected_hops != 0)) |=> (dominant_term == 8'd1);
endproperty
// P28. Standing property: FEC is symmetric.
property p_fec_symmetric;
@(posedge clk) disable iff (!rst_n)
!asymmetric_fec_configured;
endproperty
// P29. Standing property: no uncorrected hop in the path.
property p_no_uncorrected_hop;
@(posedge clk) disable iff (!rst_n)
!uncorrected_hop_in_path;
endproperty
// P30. A calibration is never applied without its uncertainty.
property p_no_naked_constants;
@(posedge clk) disable iff (!rst_n)
!cal_uncertainty_unrecorded;
endproperty
// P31. conformant is exactly the conjunction it claims.
property p_conformant_definition;
@(posedge clk) disable iff (!rst_n)
conformant <-> (fault_vector == 16'h0000);
endpropertyP2 and P7 are the chapter's two exact results and both are checkable in a testbench that controls the imbalance. P21 catches the factor-of-two error, P22 catches the stale constant, and P26 defines the only accuracy indicator a device can honestly produce. None of them says the clock is accurate to any figure — which is the property this chapter refuses, and its defect is one the series has not met.
20. Verification Scenarios
Seventy-one scenarios. The important ones are pairs of runs that differ by a bias and agree on every measurement.
The asymmetry
| # | Scenario | Expected |
|---|---|---|
| 1 | d_ms = d_sm = 500 ns | offset exact |
| 2 | d_ms = 500, d_sm = 550 | error +25 ns — exactly half |
| 3 | Same, with a true offset of +40 µs | error still exactly 25 ns |
| 4 | Same, with a 10 km symmetric path added | error unchanged |
| 5 | mean_path in every case above | exact |
| 6 | 1 m of fibre mismatch | 2.5 ns |
| 7 | 100 m of mismatch | 250 ns |
| 8 | Odd imbalance, 51 ns | 25 ns — a half-ns rounding bias |
| 9 | Round-to-nearest instead | 25.5 ns |
| 10 | c_half_violations over 10⁶ samples | zero |
Bias against noise
| # | Scenario | Expected |
|---|---|---|
| 11 | Clean and biased streams, means | differ by the bias |
| 12 | Same, spreads | identical |
| 13 | Same, variances | identical |
| 14 | Same, medians | differ by the bias |
| 15 | Same, max − min | identical |
| 16 | Same, any central moment | identical |
| 17 | Chapter 16.2 §15's path_spread_ns | identical |
| 18 | Chapter 16.4 §14's achievable_ns | identical |
| 19 | performing_to_inputs | high in both |
| 20 | Bias grows from 25 ns to 125 ns | no spread moves |
| 21 | Averaging 10⁶ samples | variance → 0, mean still displaced |
Sources
| # | Scenario | Expected |
|---|---|---|
| 22 | FEC on one direction only | ~150 ns of offset error |
| 23 | Same, diagnosed as cabling | the wrong investigation |
| 24 | PHY TX/RX pipeline difference, 40 ns | 20 ns |
| 25 | Bidirectional fibre, 1310/1550 nm, 10 km | ~5 ns — asymmetric by construction |
| 26 | Two-strand fibre, matched lengths | ~0 |
| 27 | Uncorrected store-and-forward, 1 Gb/s | 6070 ns |
| 28 | Same, with a transparent clock | 1.31 ns |
| 29 | Ranked by size | FEC, PHY, fibre, wavelength |
| 30 | Ranked by cost to fix | FEC is free |
Granularity
| # | Scenario | Expected |
|---|---|---|
| 31 | One capture at 156.25 MHz | σ = 1.85 ns |
| 32 | The offset, four captures, ÷2 | σ = 1.85 ns — unchanged |
| 33 | A design budgeting 4σ | over by 4× |
| 34 | A design budgeting σ/2 | under by 2× |
| 35 | The path delay | σ = 1.85 ns — same structure |
| 36 | 125 MHz | 2.31 ns |
| 37 | 644.53 MHz | 0.45 ns |
| 38 | One transparent clock | chain term 1.31 ns, total 2.26 ns |
| 39 | Ten transparent clocks | chain 4.13 ns, total 4.53 ns |
| 40 | Same, against 60 700 ns removed | the trade, in one comparison |
| 41 | Mixed 125 MHz access and 644 MHz core | the slow links dominate |
The chain
| # | Scenario | Expected |
|---|---|---|
| 42 | 1 transparent hop | 2.26 ns |
| 43 | 1 boundary clock | 16.2 ns |
| 44 | 10 transparent hops | 4.53 ns |
| 45 | 10 boundary clocks, independent | 51.2 ns |
| 46 | 10 boundary clocks, correlated | 162 ns |
| 47 | A rack of identical switches | correlated — same PHY, oscillator, firmware |
| 48 | Assuming independence there | under-reports by √N — 3.2× at ten hops |
| 49 | Transparent for depth, boundary for scale | the fabric rule |
| 50 | Boundary at a pod edge | message load terminated, accuracy paid once |
Calibration
| # | Scenario | Expected |
|---|---|---|
| 51 | Calibration without a symmetric reference | refused |
| 52 | Same, allowed to run | measures zero — confidently wrong |
| 53 | Calibration on a path with 200 ns of spread | aborted, c_aborted_unstable |
| 54 | Loopback calibration | true offset zero by construction |
| 55 | 256 samples, 200 ns spread | mean uncertain by 12.5 ns |
| 56 | Same, on a 25 ns bias | half the quantity being measured |
| 57 | Uncertainty not recorded | cal_uncertainty_unrecorded |
| 58 | Stored imbalance applied whole | over-corrects by exactly the bias |
| 59 | Same | looks like a correct calibration of the opposite mismatch |
| 60 | Applied as half | correct |
| 61 | Link renegotiates 10 G → 1 G | cal_stale, refused |
| 62 | Stale applied anyway | hundreds of ns in an arbitrary direction |
| 63 | Calibration missing | reported, not silently zero |
| 64 | Date recorded | the only check on a three-year-old constant |
The budget
| # | Scenario | Expected |
|---|---|---|
| 65 | Random terms in quadrature | 19.17 ns |
| 66 | Systematic, uncalibrated | 27.5 ns |
| 67 | Systematic, calibrated | 5.0 ns |
| 68 | Total, uncalibrated | ≈46.7 ns |
| 69 | Total, calibrated | ≈24.2 ns |
| 70 | Systematic summed in quadrature instead | under-reports by √N |
| 71 | dominant_term with one uncorrected hop | points at the hop, not at the largest random term |
The directed test random stimulus will not produce
A bias is not a stimulus distribution — it is a property of the testbench's delay model, and no amount of random traffic varies a cable's length. And the finding is that two runs differing by a bias agree on every statistic the design computes, which requires running both and comparing their instruments rather than their errors. No coverage metric asks for a comparison between two runs' dashboards.
Setup: the full Module 16 stack — Chapter 16.3's timestamp unit at 156.25 MHz, Chapter 16.2's exchange at 16 Sync/s, Chapter 16.4's servo at f_c = 3 Hz, one transparent clock in the path. Measurement noise σ = 10 ns. A testbench that holds the true offset, which Chapter 16.1 §19 established is the only place it exists.
Stimulus, five runs of 600 seconds.
Run A — symmetric: d_ms = d_sm = 500 ns.
Run B — 10 m mismatch: d_sm = 550 ns.
Run C — 100 m mismatch: d_sm = 1000 ns.
Run D — asymmetric FEC: d_sm = 800 ns, from FEC on one direction.
Run E — Run B with the calibration stored and applied.
Oracle:
| # | Observable | A — sym | B — 10 m | C — 100 m | D — FEC | E — calibrated |
|---|---|---|---|---|---|---|
| 1 | true offset error | ≈0 | 25 ns | 250 ns | 150 ns | ≈0 |
| 2 | mean_path_ns | exact | exact | exact | exact | exact |
| 3 | path_spread_ns | ≈20 ns | ≈20 ns | ≈20 ns | ≈20 ns | ≈20 ns |
| 4 | spread_ns (servo) | ≈20 ns | ≈20 ns | ≈20 ns | ≈20 ns | ≈20 ns |
| 5 | achievable_ns | ≈16 ns | ≈16 ns | ≈16 ns | ≈16 ns | ≈16 ns |
| 6 | achieved_ns | ≈16 ns | ≈16 ns | ≈16 ns | ≈16 ns | ≈16 ns |
| 7 | performing_to_inputs | high | high | high | high | high |
| 8 | locked | high | high | high | high | high |
| 9 | Chapter 16.2 conformant | high | high | high | high | high |
| 10 | Chapter 16.4 conformant | high | high | high | high | high |
| 11 | any statistic differing between A and C | — | — | none | — | — |
| 12 | c_half_violations | 0 | 0 | 0 | 0 | 0 |
| 13 | error against (d_sm−d_ms)/2 | exact | exact | exact | exact | — |
| 14 | cal_valid | low | low | low | low | high |
| 15 | all_removable_removed | low | low | low | low | high |
| 16 | dominant_term | calibration | calibration | calibration | calibration | servo |
| 17 | asymmetric_fec_configured | low | low | low | high | low |
| 18 | rerun C with 10⁶ samples averaged | — | — | variance → 0, error still 250 ns | — | — |
| 19 | rerun E applying the whole imbalance | — | — | — | — | −25 ns — over-corrected |
| 20 | rerun E after a renegotiation | — | — | — | — | cal_stale, refused |
Rows 1 and 11 together are the finding. Run C is 250 nanoseconds wrong and not one statistic the design computes differs from Run A's. Rows 3 to 10 are identical across all five runs; the error in row 1 spans four orders of magnitude.
Rows 13 and 15 are what the design can say. The error matches (d_sm − d_ms)/2 exactly, which a testbench can check — and all_removable_removed is low in A through D and high in E, which is the only honest accuracy indicator available. Row 16's dominant_term points at the calibration in every uncalibrated run, which is the actionable output.
And row 19 is Section 13's factor-of-two trap demonstrated: applying the whole imbalance takes a +25 ns error to −25 ns, which would look like a correct calibration of a cable mismatched the other way.
21. Debugging an Accuracy Shortfall
Five questions, and the first two are the only ones most deployments need.
Step 1 — has every removable term been removed? all_removable_removed and dominant_term. An uncorrected hop is 6070 ns and a missing calibration is 25 ns, and both are fixable today — so they rank above anything structural regardless of size. dominant_term is deliberately ordered by removability.
Step 2 — is the configuration symmetric? asymmetric_fec_configured, and the two ends' link modes compared. FEC enabled on one direction only is 150 ns of offset error, it is free to fix, and it presents as a cabling problem — Section 6's rank one. It is checked by comparing two configurations and needs no instrument.
Step 3 — is the calibration sound? cal_valid, cal_stale, and the recorded uncertainty. A stale constant is worse than none — hundreds of nanoseconds in an arbitrary direction on a link that renegotiated. And a constant whose uncertainty exceeds the bias it measured has measured nothing, which only the recorded uncertainty reveals.
Step 4 — what is the budget's composition? random_budget_ps against systematic_budget_ps. A deployment dominated by random terms is at Chapter 16.4 §4's floor and needs a better oscillator or more samples. One dominated by systematic terms has something to calibrate or characterise, and Chapter 16.4 §13's N scaling means it gets worse with depth.
Step 5 — is there still a discrepancy? This is the step no instrument takes. Section 5: the remaining bias is invisible to every statistic. The only test is a second, independent measurement — a portable transfer standard, or a loopback — and it is a commissioning activity rather than a monitoring one.
And the finding that ends an investigation: all_removable_removed high, conformant high across Chapter 16.2, Chapter 16.3, Chapter 16.4 and this chapter, dominant_term pointing at the servo, and a budget below the requirement. That is a deployment at its floor — and the floor is Section 16's 24.2 ns, which is what a well-built PTP system delivers.
22. Common Misconceptions
1 — "Averaging more samples improves accuracy."
The wrong model: more data is more precision.
What it costs: Section 20's row 18 — a million samples drive the variance to zero and leave a 250 ns error exactly where it was. Averaging reduces random error as 1/√N and has no effect whatever on a bias.
The corrected model: classify the error first. A bias displaces every sample equally, so it survives the mean and is absent from every moment about the mean. The only thing averaging helps here is Section 11's calibration — where the bias is the signal and the noise is the nuisance, which is the one place in Module 16 that averaging is the right tool.
2 — "Our spreads are tight, so we are accurate."
The wrong model: a small spread is a small error.
What it costs: a deployment 250 ns wrong with every dashboard green. Section 20's rows 3 to 10: path_spread_ns, spread_ns, achievable_ns, achieved_ns, performing_to_inputs, locked and both conformant bits are identical across runs whose errors span four orders of magnitude.
The corrected model: a spread measures random error, and the dominant term is systematic. A tight spread says the noise is small, which was never the question — and it keeps saying so as the bias grows. The only detector is a second, independent measurement.
3 — "A longer path is a worse path."
The wrong model: accuracy degrades with distance.
What it costs: effort shortening links that were never the problem. Section 4: the error depends only on the difference between the two directions and not at all on their sum. A 10 km symmetric fibre pair contributes zero; a 10 m mismatch contributes 25 ns.
The corrected model: specify matched lengths. Two fibres cut from the same reel are symmetric; two cut to convenient lengths differ by metres. And a bidirectional single-strand link is asymmetric by construction — the two wavelengths propagate at different speeds, about 1 ns per kilometre — which reverses the usual assumption that one fibre is simpler than two.
4 — "Four timestamps means four times the quantisation noise."
The wrong model: errors accumulate with the number of measurements.
What it costs: a budget over-stated by four, and a capture clock specified four times faster than necessary. The four terms combine as √4 = 2 and Chapter 16.2 §6's formula divides by 2, so they cancel exactly: the offset's quantisation σ equals one capture's.
The corrected model: 1.85 ns at 156.25 MHz, for the offset and for the path delay alike. And the same arithmetic shows where it does grow — each transparent clock adds two more captures, so ten hops take it from 1.85 ns to 4.53 ns, while removing 60 700 ns of uncorrected asymmetry.
5 — "Boundary clocks and transparent clocks are interchangeable."
The wrong model: both are PTP-aware switches.
What it costs: Section 10's table — at ten hops, 4.53 ns against 162 ns. A transparent clock forwards the exchange and adds measurement noise as √N. A boundary clock terminates it and re-originates it, so its own servo residual becomes the next segment's reference error — and identical switches in a rack are correlated, so those residuals add linearly.
The corrected model: transparent for depth, boundary for scale. Depth is accuracy; scale is Chapter 16.2 §18's 131 328 messages per second, which a boundary clock terminates and a transparent clock does not. Large fabrics use both: transparent within a pod, boundary at its edge.
6 — "The link is calibrated."
The wrong model: a stored constant is a solved problem.
What it costs: four distinct failures with one appearance. A constant applied whole instead of halved over-corrects by exactly the bias — Section 20's row 19, +25 ns becoming −25 ns. A constant measured in a different link mode is hundreds of nanoseconds wrong after a renegotiation. A constant measured without a symmetric reference is zero, confidently. And one whose uncertainty exceeds the bias measured nothing.
The corrected model: a calibration is a constant plus its uncertainty, plus the mode it was measured in, plus the date. Fourteen octets per port, and the store must refuse a stale one rather than apply it — a missing correction leaves a known error, and a stale one substitutes an unknown one.
23. Interview Reasoning
Q1 — Derive the asymmetry error and state its three properties.
From Chapter 16.2 §6: θ_measured = θ_true + (d_ms − d_sm)/2, so the error is exactly −(d_sm − d_ms)/2. Three properties follow. It is exact — algebraically half, with no approximation. It is independent of everything but the difference — the true offset, the path's total length, the traffic and the clock quality all cancel. And it is constant while the imbalance is, which makes it calibratable — and is the whole basis of a commissioning procedure.
Q2 — Why does no statistic reveal it?
Because a constant displacement moves the mean and leaves every moment about the mean unchanged. Subtracting a constant from every sample subtracts it from the mean and leaves every deviation from the mean identical — so the variance, the standard deviation, the max minus min, every central moment and the Allan deviation are all unmoved. And every instrument Module 16 built measures one of those: path_spread_ns, spread_ns, achievable_ns and the judgements on them. Which is why a deployment can be 250 ns wrong with every dashboard green, and stay green as the bias grows.
Q3 — How much quantisation noise does the four-timestamp exchange cost?
Exactly one capture's — the four terms and the division by two cancel. Four independent quantisations combine as √4 = 2 and Chapter 16.2 §6's formula divides by 2. At 156.25 MHz that is 1.85 ns for the offset and 1.85 ns for the path delay. So the protocol's shape is quantisation-neutral. Where it grows is the chain: each transparent clock adds two captures, giving σ√(2N)/2 — 4.13 ns at ten hops, for a total of 4.53 ns, against 60 700 ns of asymmetry removed.
Q4 — Transparent or boundary clocks?
Transparent for depth, boundary for scale, and the gap is a factor of 36 at ten hops. A transparent clock forwards the exchange and contributes measurement noise as √N — 4.53 ns at ten hops. A boundary clock terminates the exchange and becomes a master, so its own servo residual — 16.2 ns — is the next segment's reference error. Ten of them are 51.2 ns if independent and 162 ns if correlated — and a rack of identical switches sharing a PHY family, an oscillator part and firmware is correlated. What boundary clocks buy is termination of Chapter 16.2 §18's message load.
Q5 — Describe the calibration procedure and what makes it valid.
Seven steps: verify the configuration is symmetric; record the vendor's PHY figures per mode; loop back each port; measure the cable lengths; store the constant; verify against a transfer standard; and record the date and the link mode. Validity rests on two gates. The reference must be known symmetric — a loopback traverses the same fibre both ways so its true offset is zero by construction, and no protocol activity can supply one (Chapter 16.2 §8). And the path must be stable, because a moving path has a moving asymmetry and the constant would not be one. The result is stored halved, with its uncertainty, indexed by mode.
Q6 — Why can't you check the accuracy budget against a measured spread?
Because the test detects random error and the dominant term is systematic. Section 5's algebra: a bias leaves the spread, the variance and every central moment unchanged, so the check passes identically on a perfect link and on one 25 ns wrong — and keeps passing as the bias grows to 125. A check blind to its subject's dominant failure is worse than none, because it is reported as coverage. What to assert instead: the error's exact form against a controlled imbalance — which catches sign, shift and factor-of-two bugs at any magnitude — that every removable term has been removed, and agreement with a second, independent measurement, which is a commissioning step because the second route must be physical.
24. Understanding Check
25. What's Next
Module 16 is complete, and its result is a number and a procedure.
The number: ≈24.2 nanoseconds, assembled from terms whose magnitudes are each known — Chapter 16.1's capture granularity, Chapter 16.2's four-timestamp arithmetic, Chapter 16.3's characterised PHY delay, Chapter 16.4's servo optimum and transparent clocks, and this chapter's calibrated asymmetry.
The procedure: the last factor of two is a commissioning activity and not a technology. A deployment that skipped it has hardware it is not using.
And the module's deeper result is about kinds of error rather than sizes. Random errors are found by every instrument and removed by better clocks and longer filters. Systematic errors are found by nothing internal, removed by nothing internal, and scale as N rather than √N — so they dominate any deep path and they are invisible while they do it.
Module 17 — Time-Sensitive Networking — asks a different question with the same dependency.
Chapter 17.1 — What Determinism Requires establishes why standard switched Ethernet cannot bound latency: Chapter 14.1's queueing, Chapter 14.3's 58.6%, Chapter 12.6's store-and-forward and Chapter 13.4 §11's strict priority all contribute, and none of them is bounded. It derives a frame's worst-case latency across N hops and shows which term has no bound at all.
Then it states what must be added: a schedule, a clock, and a guard band — and the clock is this module's. A time-aware shaper opens and closes gates at instants agreed across every device in the path, which requires exactly the synchronisation Module 16 has spent five chapters building.
One thread carries directly across, and it is the guard band. A gate must close early enough that no frame is still transmitting when the next window opens, and how early depends on how well the devices agree about the time. Module 16's 24.2 ns is the input to that calculation — so the accuracy this chapter assembled becomes, in the next module, a quantity of wasted bandwidth.
Continue learning
Related tutorials
- Related topic
Why Sub-Microsecond Sync Is a Hardware Problem
A 100 ppm crystal drifts 8.64 seconds a day, a software timestamp jitters by more than it measures, and the fix is a register 10 nanoseconds from the wire.
- Related topic
The PTP Message Exchange
Why synchronisation needs four messages rather than one, what each equation assumes, and the half-the-imbalance error that survives every measurement.
- Related topic
Hardware Timestamping at the MAC/PHY Boundary
A Sync must carry the time of its own transmission. One-step rewrites the field in flight and patches the CRC with a linear map; two-step sends a second message.
- Related topic
The Servo — Offset, Delay and Correction
A PI loop whose bandwidth falls out of the noise-against-drift trade, and a residence-time measurement that turns 6.07 microseconds of error into 1.3 nanoseconds.
Standards & specifications
- Governing standard
- IEEE Std 802.3 (Ethernet)(opens IEEE in a new tab)
Defines the Ethernet MAC, the media-independent interfaces and the physical-layer sublayers, including framing, access control, auto-negotiation and per-rate PHY specifications. VLAN tagging, priority and time-sensitive shaping are defined by IEEE 802.1, not by 802.3.
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 Ethernet curriculum.
