Skip to content
VLSI Mentor

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 = 1so 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.

TermKindMagnitude, one hopReduced by
path asymmetry, uncalibratedsystematic25 ns — 10 m of fibrecalibration only
path asymmetry, calibrated to ±1 msystematic2.5 nsa better calibration
timestamp granularity, 156.25 MHzrandom1.85 nsa faster capture clock
transparent-clock residual, 1 hoprandom1.31 nsthe same
PHY characterisation errorsystematic2.5 nsbetter characterisation
Chapter 4.4's elastic bufferrandom~10 nsnothing at this layer
Chapter 16.4's servo residualrandom16.2 nsa 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:

UncalibratedCalibrated
random terms, in quadrature19.17 ns19.17 ns
systematic terms, summed27.5 ns5.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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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

endmodule

Classification: 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t2 - t1 = d_ms + θ                          (1)
t4 - t3 = d_sm - θ                          (2)

The protocol computes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
θ_measured = ((t2 - t1) - (t4 - t3)) / 2
           = ((d_ms + θ) - (d_sm - θ)) / 2
           = (d_ms - d_sm + 2θ) / 2
           = θ + (d_ms - d_sm) / 2

So:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
θ_measured - θ_true = (d_ms - d_sm) / 2 = -(d_sm - d_ms) / 2

Three 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.

StatisticWith no biasWith a bias b
meanθθ + b
medianθθ + b
varianceσ²σ² — unchanged
standard deviationσσ — unchanged
max − min — unchanged
any central momentunchanged
Allan deviationunchanged

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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

endmodule

Classification: 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.

==

A path asymmetry displaces every offset sample by exactly the same amount, half the imbalance. Subtracting a constant from every sample subtracts it from the mean and leaves every deviation from the mean unchanged, so the variance, the standard deviation, the maximum minus the minimum, every central moment and the Allan deviation are all identical with and without the bias. Every instrument Module 16 built measures one of those quantities: Chapter 16.2's path spread, Chapter 16.4's servo spread and achievable error, and the performing-to-inputs judgement built on them. So a deployment can be 250 nanoseconds wrong with every dashboard reading normal, and the dashboards stay normal as the bias grows. The only detector is a second independent measurement of the same quantity, which must come from outside the protocol.A bias of bevery sample displacedThe mean movesby exactly bThe variance doesnotnor any central momentEvery instrumentmeasures a spreadAll dashboardsgreenat 25 ns or 250A secondmeasurementby an independent routeThe calibrationa commissioning step12
Figure 1 — a bias moves the mean and touches nothing else, which is why every instrument in Module 16 is blind to it.

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.

SourceImbalanceOffset errorWith the protocol
fibre length mismatch, per metre5 ns2.5 nsunchanged — calibratable
10 m mismatch50 ns25 nsunchanged
bidirectional fibre, 1310/1550 nm over 10 km~10 ns5 nsunchanged
PHY TX vs RX pipeline difference~40 ns20 nsunchanged — per mode
FEC enabled on one direction only~300 ns150 nsa configuration error
uncorrected store-and-forward, 1 Gb/s12 140 ns6070 nsREMOVED — 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:

RankSourceTypicalFix
1asymmetric FEC or mode configuration150 nsfix the configuration
2PHY TX/RX difference20 nsthe vendor's figure, per mode
3fibre length mismatch2.5 ns/mmatch the lengths, or calibrate
4bidirectional wavelength5 ns per 10 kmcalibrate

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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

endmodule

Classification: 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 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.

==

The offset is computed from four timestamps, each quantised uniformly over one capture clock period with a standard deviation of the period divided by the square root of twelve. The four independent terms combine as the square root of four, which is two, and Chapter 16.2's formula divides the result by two, so the two factors cancel exactly and the offset's quantisation noise equals one capture's. At 156.25 megahertz that is 1.85 nanoseconds for the offset and, by the same structure, 1.85 for the path delay. A design budgeting four sigma has over-estimated by four; one budgeting sigma over two because of the division has under-estimated by two. Where the term does grow is the chain: each transparent clock adds two more captures to the Sync's path, giving sigma times the square root of two N over two, which is 4.13 nanoseconds at ten hops for a total of 4.53.One captureperiod / sqrt(12)Four of themsqrt(4) = 2The formula dividesby 216.2's equation (4)1.85 nsexactly one capture'sEach transparentclockadds two capturessigma x sqrt(2N) /24.13 ns at ten hops4.53 ns totalfor 60 700 ns removed12
Figure 2 — four timestamps, four quantisations, and a division by two that cancels them exactly.

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/√12Chapter 16.1 §14.

The offset uses four of them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
θ = ((t2 - t1) - (t4 - t3)) / 2

Four independent terms, each with variance σ², summed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 clockPeriodσ per captureσ of the offset
125 MHz8.00 ns2.31 ns2.31 ns
156.25 MHz6.40 ns1.85 ns1.85 ns
322.27 MHz3.10 ns0.90 ns0.90 ns
644.53 MHz1.55 ns0.45 ns0.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 clocksChain termTotal σ at 156.25 MHz
001.85 ns
11.31 ns2.26 ns
32.26 ns2.92 ns
52.92 ns3.46 ns
104.13 ns4.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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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

endmodule

Classification: 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 firmwareso 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.

==

Two ways to put a PTP-aware switch in a path. A transparent clock forwards the exchange unchanged, measuring only how long it held each message and writing that into the correction field, so it contributes measurement noise that grows as the square root of the hop count — 4.53 nanoseconds at ten hops. 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 own servo residual of about 16.2 nanoseconds becomes the reference error that every downstream device inherits. Ten boundary clocks give 51.2 nanoseconds if their residuals are independent and 162 if they are correlated — and a rack of identical switches sharing a PHY family, an oscillator part number and firmware is correlated. The rule that follows is transparent clocks for depth and boundary clocks for scale, since only a boundary clock terminates the master's message load.Transparentforwards the exchangeMeasurement noisegrows as sqrt(N)4.53 ns at ten hopsdepth is cheapBoundaryterminates andre-originatesIts residual is thereference16.2 ns per stage162 ns at ten hopscorrelated stagesTransparent fordepthboundary for scale12
Figure 3 — a transparent clock forwards the exchange; a boundary clock terminates it, and its own residual becomes the next segment's reference error.

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.

HopsTransparent — σ at 156.25 MHzBoundary, independentBoundary, correlated
12.26 ns16.2 ns16.2 ns
22.61 ns22.9 ns32.4 ns
32.92 ns28.1 ns48.6 ns
53.46 ns36.2 ns81.0 ns
104.53 ns51.2 ns162 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 loadChapter 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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

endmodule

Classification: 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.

StepWhat it measuresReference
1 — verify the configurationFEC and mode symmetric on both endsthe configuration itself
2 — record the PHY figuresTX and RX pipeline, per modethe vendor's characterisation
3 — loopback each portthe port's own TX/RX asymmetrya physical loopback — offset is zero
4 — measure or record cable lengthsthe fibre or copper mismatchan OTDR, or the installation record
5 — store the per-link constantthe sum of 2, 3 and 4
6 — verify against a transfer standardthe whole patha calibrated portable clock
7 — record the date and the link modeso 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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

endmodule

Classification: 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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

endmodule

Classification: 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.

The monitor checks that the calibration regime is sound. It cannot check that the calibration is right.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// 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};

endmodule

Classification: 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.

TermKindMagnitudeSource
timestamp granularity, 156.25 MHzrandom1.85 nsSection 8
transparent-clock chain, 1 hoprandom1.31 nsSection 8
Chapter 4.4's elastic bufferrandom~10 nsChapter 16.3 §4
Chapter 16.4's servo residualrandom16.2 nsChapter 16.4 §4
random, in quadrature19.17 ns
PHY characterisation errorsystematic2.5 nsChapter 16.3 §4
asymmetry, uncalibrated 10 msystematic25 nsSection 6
asymmetry, calibrated to ±1 msystematic2.5 nsSection 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:

EffortBuys
calibrate the links25 ns → 2.5 ns — the largest single win
fix asymmetric FECup to 150 ns — free
a better oscillatorthe servo residual, 16.2 → less
more Sync messagesthe same term, along Chapter 16.4 §4's curve
a faster capture clock1.85 → 0.90 ns — the smallest term
a better PHY characterisation2.5 ns, and it scales as N

The last row's N scaling is why it ranks above its sizeChapter 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.

==

Module 16's error budget assembled. The random terms — timestamp granularity at 1.85 nanoseconds, the transparent-clock chain at 1.31, the elastic buffer at about 10 and Chapter 16.4's servo residual at 16.2 — combine in quadrature to 19.17 nanoseconds. The systematic terms sum linearly: the PHY characterisation error at 2.5 nanoseconds plus the path asymmetry, which is 25 nanoseconds uncalibrated and 2.5 calibrated. The totals are about 46.7 nanoseconds uncalibrated and 24.2 calibrated, a factor of 1.9 bought by an afternoon's measurements and a constant that fits in eight octets. The largest random term is the servo residual, and that is Chapter 16.4's optimum rather than a shortfall; the largest term overall is the uncalibrated asymmetry, and it is the only large one that is removable by procedure.Random termsin quadrature19.17 nsservo dominates at 16.2Systematic termssummed linearly27.5 nsuncalibratedasymmetry is 25 of it5.0 ns calibratedPHY plus residual46.7 ns totaluncalibrated24.2 ns totalcalibrated12
Figure 4 — the assembled budget: random terms in quadrature, systematic terms summed, and the calibration worth a factor of two.

17. What Accuracy Can and Cannot Be Claimed

ClaimStatus
the round-trip delay is exactguaranteedChapter 16.2 §6, no premise
the offset error is exactly half the imbalanceguaranteed — Section 4, algebraically
the offset's quantisation noise equals one capture'sguaranteed — Section 8
a bias leaves every central statistic unchangedguaranteed — Section 5
every removable term has been removedcheckable — Section 15
the achieved accuracynot measurable by the device
the calibration is correctonly to its recorded uncertainty
the deployment meets its requirementa 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

ComponentCostNote
Section 3's asymmetry modeltestbench onlyit needs inputs silicon lacks
Section 5's bias detectortestbench onlyand saying so is the point
Section 7's granularity propagatora few constants, one isqrtevaluated once
Section 9's chain modelthe sameonce
Section 11's calibration enginean accumulator and two gatesrun at commissioning
Section 13's calibration store24 × 14 octets = 336 octetsthe constant, the mode, the date
Section 14's telemetry≈30 flops
total in silicon≈400 octets
what it buys46.7 ns → 24.2 nsa 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:

ChapterSilicon costWhat it removed
Chapter 16.2≈220 octetsthe unknown path delay
Chapter 16.3≈2500 XOR2 + 400 flopsthe delivery latency of t1
Chapter 16.4≈300 octets + a multiply6070 ns per hop
this chapter≈400 octets25 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endproperty

Group 2 — bias against noise.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endproperty

Group 3 — granularity.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endproperty

Group 4 — the chain.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endproperty

Group 5 — the calibration.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endproperty

Group 6 — the store and the budget.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endproperty

P2 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

#ScenarioExpected
1d_ms = d_sm = 500 nsoffset exact
2d_ms = 500, d_sm = 550error +25 ns — exactly half
3Same, with a true offset of +40 µserror still exactly 25 ns
4Same, with a 10 km symmetric path addederror unchanged
5mean_path in every case aboveexact
61 m of fibre mismatch2.5 ns
7100 m of mismatch250 ns
8Odd imbalance, 51 ns25 ns — a half-ns rounding bias
9Round-to-nearest instead25.5 ns
10c_half_violations over 10⁶ sampleszero

Bias against noise

#ScenarioExpected
11Clean and biased streams, meansdiffer by the bias
12Same, spreadsidentical
13Same, variancesidentical
14Same, mediansdiffer by the bias
15Same, max − minidentical
16Same, any central momentidentical
17Chapter 16.2 §15's path_spread_nsidentical
18Chapter 16.4 §14's achievable_nsidentical
19performing_to_inputshigh in both
20Bias grows from 25 ns to 125 nsno spread moves
21Averaging 10⁶ samplesvariance → 0, mean still displaced

Sources

#ScenarioExpected
22FEC on one direction only~150 ns of offset error
23Same, diagnosed as cablingthe wrong investigation
24PHY TX/RX pipeline difference, 40 ns20 ns
25Bidirectional fibre, 1310/1550 nm, 10 km~5 ns — asymmetric by construction
26Two-strand fibre, matched lengths~0
27Uncorrected store-and-forward, 1 Gb/s6070 ns
28Same, with a transparent clock1.31 ns
29Ranked by sizeFEC, PHY, fibre, wavelength
30Ranked by cost to fixFEC is free

Granularity

#ScenarioExpected
31One capture at 156.25 MHzσ = 1.85 ns
32The offset, four captures, ÷2σ = 1.85 ns — unchanged
33A design budgeting 4σover by 4×
34A design budgeting σ/2under by 2×
35The path delayσ = 1.85 ns — same structure
36125 MHz2.31 ns
37644.53 MHz0.45 ns
38One transparent clockchain term 1.31 ns, total 2.26 ns
39Ten transparent clockschain 4.13 ns, total 4.53 ns
40Same, against 60 700 ns removedthe trade, in one comparison
41Mixed 125 MHz access and 644 MHz corethe slow links dominate

The chain

#ScenarioExpected
421 transparent hop2.26 ns
431 boundary clock16.2 ns
4410 transparent hops4.53 ns
4510 boundary clocks, independent51.2 ns
4610 boundary clocks, correlated162 ns
47A rack of identical switchescorrelated — same PHY, oscillator, firmware
48Assuming independence thereunder-reports by √N — 3.2× at ten hops
49Transparent for depth, boundary for scalethe fabric rule
50Boundary at a pod edgemessage load terminated, accuracy paid once

Calibration

#ScenarioExpected
51Calibration without a symmetric referencerefused
52Same, allowed to runmeasures zero — confidently wrong
53Calibration on a path with 200 ns of spreadaborted, c_aborted_unstable
54Loopback calibrationtrue offset zero by construction
55256 samples, 200 ns spreadmean uncertain by 12.5 ns
56Same, on a 25 ns biashalf the quantity being measured
57Uncertainty not recordedcal_uncertainty_unrecorded
58Stored imbalance applied wholeover-corrects by exactly the bias
59Samelooks like a correct calibration of the opposite mismatch
60Applied as halfcorrect
61Link renegotiates 10 G → 1 Gcal_stale, refused
62Stale applied anywayhundreds of ns in an arbitrary direction
63Calibration missingreported, not silently zero
64Date recordedthe only check on a three-year-old constant

The budget

#ScenarioExpected
65Random terms in quadrature19.17 ns
66Systematic, uncalibrated27.5 ns
67Systematic, calibrated5.0 ns
68Total, uncalibrated≈46.7 ns
69Total, calibrated≈24.2 ns
70Systematic summed in quadrature insteadunder-reports by √N
71dominant_term with one uncorrected hoppoints 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:

#ObservableA — symB — 10 mC — 100 mD — FECE — calibrated
1true offset error≈025 ns250 ns150 ns≈0
2mean_path_nsexactexactexactexactexact
3path_spread_ns≈20 ns≈20 ns≈20 ns≈20 ns≈20 ns
4spread_ns (servo)≈20 ns≈20 ns≈20 ns≈20 ns≈20 ns
5achievable_ns≈16 ns≈16 ns≈16 ns≈16 ns≈16 ns
6achieved_ns≈16 ns≈16 ns≈16 ns≈16 ns≈16 ns
7performing_to_inputshighhighhighhighhigh
8lockedhighhighhighhighhigh
9Chapter 16.2 conformanthighhighhighhighhigh
10Chapter 16.4 conformanthighhighhighhighhigh
11any statistic differing between A and Cnone
12c_half_violations00000
13error against (d_sm−d_ms)/2exactexactexactexact
14cal_validlowlowlowlowhigh
15all_removable_removedlowlowlowlowhigh
16dominant_termcalibrationcalibrationcalibrationcalibrationservo
17asymmetric_fec_configuredlowlowlowhighlow
18rerun C with 10⁶ samples averagedvariance → 0, error still 250 ns
19rerun E applying the whole imbalance−25 ns — over-corrected
20rerun E after a renegotiationcal_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 √N4.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 knownChapter 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

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.