Skip to content
VLSI Mentor

Ethernet · Module 17

Time-Aware Shaping (802.1Qbv)

A schedule turns an unbounded interference term into a configuration parameter, and then spends up to 19.5% of the cycle on a guard band to stay safe.

Chapter 17.1 §16 named the three things a bounded path needs — a schedule, a clock and a guard band — and built none of them. This chapter builds the first and the third; Module 16 already built the second.

The schedule's job is one substitution. Chapter 17.1 §8's interference term is bounded by how much higher-priority traffic arrives, which nothing limits. A gate schedule shuts every other class's gate during a window, so a frame transmitted in its own window waits for nothing — and a frame that misses its window waits at most one cycle, which is a number an operator chose.

beforewith a schedule
interference, inside the windowunboundedzero
interference, having missed a windowunboundedcycle − window
what sets itthe trafficthe configuration

And the price is the guard band, which Chapter 17.1 §17 derived as MTU/R + 2 × sync_error and this chapter spends.

At 1 Gb/s that is 12.192 µs, and it is subtracted from every window in every cycle. So a design that shortens its cycle to improve the latency bound pays the guard band more often — and the two move in opposite directions:

CycleWindowWorst waitGuard band, per cycleUsable fraction
2000 µs200 µs1800 µs0.61%9.39%
1000 µs100 µs900 µs1.22%8.78%
250 µs50 µs200 µs4.88%15.12%
125 µs25 µs100 µs9.75%10.25%
62.5 µs12.5 µs50 µs19.51%0.49%

Read the last row's last two columns together. A 12.5 µs window at 1 Gb/s is 12.192 µs of guard band and 0.308 µs of usable transmissionthe guard band has eaten the window, and the schedule delivers 0.49% of the link.

Which is the whole argument for Chapter 17.3, and this chapter's last section is where that argument becomes a number.

1. Scope — What This Chapter Owns

This chapter owns the schedule: the gate-control list, the cycle and its windows, the guard band's implementation, the transmission-eligibility check, the schedule's own contribution to latency, and the installation of a new schedule across a path.

It does not own the requirement. Chapter 17.1 established that two of a hop's seven latency terms have no bound and that five sum to 37.12 µs per hop at 1 Gb/s. This chapter bounds the sixth and seventh.

It does not own the clock. Chapter 16.5 §16 assembled a 24.2 ns budget; Sections 7 and 14 consume it — once in the guard band and once in the installation window.

And it does not own the guard band's dominant term. Chapter 17.1 §18 showed the MTU contributing 12.144 µs of a 12.192 µs band at 1 Gb/s. Section 18 prices what removing it would buy and Chapter 17.3 removes it.

2. What a Schedule Is

Four objects, and the whole mechanism is their arithmetic.

ObjectWhat it is
the cyclea repeating interval, the same on every device
the gate-control lista sequence of (gate vector, interval) entries summing to the cycle
a gateone bit per traffic class: open or shut
the guard bandthe interval before a gate shuts during which nothing new is admitted

A gate-control list is executed cyclically. At each entry the eight gates take the entry's vector, and the entry's interval says how long before the next. The intervals sum to the cycle, so the list repeats exactly.

And "the same on every device" is the requirement that makes it work. A frame transmitted during class 6's window at hop 1 must arrive at hop 2 during class 6's window there — otherwise it waits for the next one, and the bound becomes N × cycle rather than cycle.

Which gives the three things Section 1 said this chapter consumes, in the order they are needed:

A shared cycle, so every device's list repeats with the same period.

A shared phase, so entry k begins at the same instant everywhere — and this is where Chapter 16.5's 24.2 ns enters.

And a guard band, because Chapter 12.6 §8 established that a frame in flight cannot be aborted, so a gate that shuts while a frame is transmitting has admitted something that will overrun into the next window.

The gates themselves are trivial — eight bits and a comparator. Everything difficult in this chapter is one of those three.

3. RTL 1 — The Gate-Control List

The list is a small memory and a sequencer, and the one subtlety is that it must be replaceable while it is running.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// tas_pkg -- shared types for 802.1Qbv time-aware shaping.
// -----------------------------------------------------------------------
package tas_pkg;

  localparam int NUM_CLASSES = 8;        // 13.2's PCP, 13.4's queues
  localparam int GCL_DEPTH   = 64;
  localparam int NS_W        = 32;

  typedef logic [NUM_CLASSES-1:0] gate_vec_t;
  typedef logic [NS_W-1:0]        ns_t;

  // One gate-control entry: which gates are open, and for how long.
  typedef struct packed {
    gate_vec_t gates;
    ns_t       interval_ns;
  } gcl_entry_t;

  // A schedule is a list plus the cycle it repeats on and the instant
  // it becomes effective. The third field is section 13's subject.
  typedef struct packed {
    logic  valid;
    logic [$clog2(GCL_DEPTH+1)-1:0] length;
    ns_t   cycle_ns;
    logic [47:0] base_sec;      // PTP time -- 16.2's timescale
    ns_t   base_ns;
  } schedule_hdr_t;

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// gate_control_list -- holds two schedules and executes one.
//
// Two, because a schedule must be REPLACED atomically at an instant
// agreed across the path -- section 13. A single-buffered list cannot
// be changed without a window in which it is neither the old schedule
// nor the new one.
// -----------------------------------------------------------------------
module gate_control_list
  import tas_pkg::*;
(
  input  logic       clk,
  input  logic       rst_n,

  // Configuration writes land in the SHADOW bank, never the live one.
  input  logic       cfg_we,
  input  logic [$clog2(GCL_DEPTH)-1:0] cfg_idx,
  input  gcl_entry_t cfg_entry,
  input  logic       cfg_hdr_we,
  input  schedule_hdr_t cfg_hdr,

  // The swap, from section 13's installer.
  input  logic       swap_now,

  // Execution.
  input  logic       advance,            // the interval expired
  output gate_vec_t  gates,
  output ns_t        current_interval_ns,
  output logic [$clog2(GCL_DEPTH)-1:0] current_index,
  output schedule_hdr_t live_hdr,
  output logic       wrapped,            // the list returned to entry 0

  output logic [31:0] c_swaps,
  output logic [31:0] c_cycles,
  output logic       cfg_sum_mismatch    // intervals do not sum to the cycle
);

  gcl_entry_t    bank_a [GCL_DEPTH];
  gcl_entry_t    bank_b [GCL_DEPTH];
  schedule_hdr_t hdr_a, hdr_b;
  logic          live_is_a;

  logic [$clog2(GCL_DEPTH)-1:0] idx;

  // The intervals must sum to the cycle, or the list drifts against
  // every other device's. This is checkable at configuration time and
  // is the most common schedule error.
  ns_t sum_shadow;
  always_comb begin
    int i;
    sum_shadow = '0;
    for (i = 0; i < GCL_DEPTH; i++)
      if (i < int'(live_is_a ? hdr_b.length : hdr_a.length))
        sum_shadow = sum_shadow +
                     (live_is_a ? bank_b[i].interval_ns : bank_a[i].interval_ns);
  end
  assign cfg_sum_mismatch =
    (live_is_a ? hdr_b.valid : hdr_a.valid) &&
    (sum_shadow != (live_is_a ? hdr_b.cycle_ns : hdr_a.cycle_ns));

  always_ff @(posedge clk or negedge rst_n) begin
    int i;
    if (!rst_n) begin
      for (i = 0; i < GCL_DEPTH; i++) begin
        bank_a[i] <= '0;
        bank_b[i] <= '0;
      end
      hdr_a <= '0; hdr_b <= '0;
      live_is_a <= 1'b1; idx <= '0;
      c_swaps <= '0; c_cycles <= '0; wrapped <= 1'b0;
    end else begin
      wrapped <= 1'b0;

      // Writes always target the shadow bank.
      if (cfg_we) begin
        if (live_is_a) bank_b[cfg_idx] <= cfg_entry;
        else           bank_a[cfg_idx] <= cfg_entry;
      end
      if (cfg_hdr_we) begin
        if (live_is_a) hdr_b <= cfg_hdr;
        else           hdr_a <= cfg_hdr;
      end

      // The swap is a single flop toggle, so the transition is atomic
      // AT THIS DEVICE. Section 14 is about the fact that it is not
      // atomic across devices.
      if (swap_now) begin
        live_is_a <= !live_is_a;
        idx       <= '0;
        c_swaps   <= c_swaps + 1;
      end else if (advance) begin
        if (idx == ((live_is_a ? hdr_a.length : hdr_b.length) - 1)) begin
          idx      <= '0;
          wrapped  <= 1'b1;
          c_cycles <= c_cycles + 1;
        end else begin
          idx <= idx + 1'b1;
        end
      end
    end
  end

  assign gates = live_is_a ? bank_a[idx].gates : bank_b[idx].gates;
  assign current_interval_ns =
    live_is_a ? bank_a[idx].interval_ns : bank_b[idx].interval_ns;
  assign current_index = idx;
  assign live_hdr      = live_is_a ? hdr_a : hdr_b;

endmodule

Classification: a double-buffered configuration memory with a cyclic sequencer. Two banks, one toggle.

What it teaches: that double buffering is not an optimisation here, it is the only way a schedule can be replaced at all. A single-banked list being rewritten while it executes runs a mixture of the old and new schedules — entries 0 to k new, k+1 onward old — and the mixture's intervals do not sum to any cycle. The gates then drift against every other device's until the write completes, which for a 64-entry list over a management bus is milliseconds.

And it teaches that cfg_sum_mismatch is a configuration check with a specific and severe failure. The intervals must sum to the cycle exactly. If they sum to less, this device's list wraps early and its phase advances against the path's; if more, it wraps late. Either way the drift accumulates every cycle, so a 1 µs error on a 1000 µs cycle puts the device a full window out of phase after 100 cycles — 0.1 seconds.

Deliberately simplified: the sum is computed combinationally over all 64 entries, which is a 64-input adder tree evaluated continuously. A production design computes it once when the shadow bank's header is written and latches the result — the check is a configuration-time property, not a runtime one, and Chapter 14.1 §17's argument applies: it is wrong from the moment it is written, not from the first frame.

Production implication: c_cycles and wrapped are what let an operator verify that two devices are running the same schedule at the same phase. Comparing c_cycles across a path, sampled at a common PTP instant, shows a device whose list is drifting — and a device whose count differs by N after a known interval has an interval-sum error of N × cycle over that interval. Without the counter the drift is invisible until a frame misses a window.

4. The Cycle, the Windows and the Gates

A worked schedule makes the objects concrete, and the arithmetic in it is the arithmetic of the whole chapter.

Take a 1000 µs cycle at 1 Gb/s with three classes: 6 for control, 5 for audio, and 0 for everything else.

EntryGates openIntervalWhat happens
0class 6 only100 µscontrol transmits; nothing else may start
1class 5 only150 µsaudio transmits
2class 0 only750 µsbulk traffic gets the rest
total1000 µsmust equal the cycle exactly

Three things follow immediately and each one is a section of this chapter.

A class-6 frame that is ready at the start of entry 0 waits for nothing — every other gate is shut, so Chapter 17.1 §8's interference term is zero.

A class-6 frame that becomes ready one microsecond after entry 0 ends waits 900 µs for the next cycle's entry 0. That is the schedule's own latency contribution, and it is cycle − window.

And entry 0's last 12.192 µs cannot admit a new frame, because a maximum frame started then would still be transmitting when the gate shuts. So the usable part of a 100 µs window is 87.8 µs, and Section 8 is that subtraction.

The gates themselves are the easy part:

ClassGate bitOpen during
6gates[6]entry 0
5gates[5]entry 1
0gates[0]entry 2
1–4, 7never — configured, and starved by construction

The last row is worth noticing because it is a real configuration and a real hazard. A class whose gate is never open is a class whose traffic is never transmitted, and Chapter 13.4 §11's strict-priority starvation returns in a new form — except that here it is deliberate, permanent, and produced by a configuration that looks complete.

==

A 1000 microsecond cycle divided into three gate-control entries. Entry zero opens class 6 alone for 100 microseconds, entry one opens class 5 alone for 150, and entry two opens class 0 for the remaining 750; the intervals must sum to the cycle exactly or the device's phase drifts against every other device's. Each window's final 12.192 microseconds at 1 gigabit is a guard band during which no new frame may be admitted, because a maximum frame started then would still be transmitting when the gate shuts and would overrun into the next window. So a 100 microsecond window delivers 87.8 microseconds of usable transmission. A frame that becomes ready one nanosecond after its window ends waits 900 microseconds for the next cycle, which is the schedule's own latency contribution and equals the cycle minus the window.Cycle: 1000 usrepeats exactlyEntry 0: class 6100 usEntry 1: class 5150 usEntry 2: class 0750 usGuard band: 12.192usat the end of each window87.8 us usableof a 100 us windowMissed it: 900 uscycle minus window12
Figure 1 — one cycle of a three-entry schedule, and the guard band at the end of every window.

5. RTL 2 — The Cycle Timer

The list from Section 3 needs something to advance it, and that something is where the schedule is anchored to real time.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// cycle_timer -- advances the gate-control list and keeps its phase
// locked to the PTP timescale.
//
// The list's phase is NOT free-running. It is derived from the
// schedule's base time and the cycle, so every device in the path
// computes the same entry boundary from the same clock -- 16.5's
// clock, with 16.5's 24.2 ns of disagreement.
// -----------------------------------------------------------------------
module cycle_timer
  import tas_pkg::*;
(
  input  logic          clk,
  input  logic          rst_n,

  // 16.1 section 3's free-running clock, disciplined by 16.4's servo.
  input  logic [47:0]   ptp_sec,
  input  ns_t           ptp_ns,
  input  logic          ptp_locked,      // 16.4 section 9's S_LOCKED

  input  schedule_hdr_t live_hdr,
  input  ns_t           current_interval_ns,

  output logic          advance,
  output ns_t           time_in_entry_ns,
  output ns_t           time_to_entry_end_ns,
  output logic          phase_valid,
  output logic [31:0]   c_advances,
  output logic [31:0]   c_phase_lost,
  output logic signed [31:0] phase_error_ns
);

  ns_t entry_start_ns;
  ns_t elapsed;

  // Time since the schedule's base, modulo the cycle. This is the
  // position within the cycle, and every device computes it from the
  // same PTP instant -- which is why they agree.
  logic [63:0] since_base;
  ns_t         cycle_pos;

  always_comb begin
    since_base = ({16'd0, ptp_sec} - {16'd0, live_hdr.base_sec}) * 64'd1_000_000_000
               + {32'd0, ptp_ns} - {32'd0, live_hdr.base_ns};
    cycle_pos  = (live_hdr.cycle_ns == 0) ? '0
               : ns_t'(since_base % {32'd0, live_hdr.cycle_ns});
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      entry_start_ns <= '0; elapsed <= '0; advance <= 1'b0;
      phase_valid <= 1'b0; c_advances <= '0; c_phase_lost <= '0;
      phase_error_ns <= '0;
    end else begin
      advance <= 1'b0;

      // A schedule without a locked clock is a schedule whose phase is
      // meaningless. 16.4 section 9's S_LOCKED gates it -- and the
      // correct behaviour when it is lost is section 17's subject.
      if (!ptp_locked || !live_hdr.valid) begin
        if (phase_valid) c_phase_lost <= c_phase_lost + 1;
        phase_valid <= 1'b0;
      end else begin
        phase_valid <= 1'b1;

        elapsed <= cycle_pos - entry_start_ns;

        if ((cycle_pos - entry_start_ns) >= current_interval_ns) begin
          advance        <= 1'b1;
          entry_start_ns <= entry_start_ns + current_interval_ns;
          c_advances     <= c_advances + 1;
        end

        // How far this device's entry boundary sits from the ideal.
        // On a correct schedule it is bounded by one clock period plus
        // 16.5's synchronisation error.
        phase_error_ns <= $signed({1'b0, cycle_pos}) -
                          $signed({1'b0, entry_start_ns});
      end
    end
  end

  assign time_in_entry_ns     = elapsed;
  assign time_to_entry_end_ns = (current_interval_ns > elapsed)
                              ? (current_interval_ns - elapsed) : '0;

endmodule

Classification: a modulo counter anchored to an absolute timescale. No control, and its correctness is entirely in the modulo.

What it teaches: that the schedule's phase is computed from an absolute time rather than counted from a local reset, and that is what makes two devices agree. A free-running cycle counter started at power-on would drift against every other device's by the oscillator's toleranceChapter 16.1 §4's 100 ppm is 100 µs per second, so two devices would be a whole window apart within a second. Deriving the position as (now − base) mod cycle makes the phase a function of the shared clock, so the only disagreement is the clock's own: 24.2 ns.

And it teaches that ptp_locked must gate the schedule, and that gating it is not obviously the right behaviour. A device whose servo has lost lock has a clock drifting at its oscillator's rate, so its gates open at times nobody else agrees with — and opening them anyway means transmitting into another class's window. Section 17's table has the alternatives and none of them is good.

Deliberately simplified: since_base uses a 64-bit modulo, which synthesises into a divider. A production design exploits the fact that the cycle is constant between swaps and maintains the position incrementally, resynchronising to the absolute value once per cycle — which is one divide per cycle rather than one per clock.

Production implication: phase_error_ns is the measurement that says whether this device's gates are where the schedule says they should be. On a correct implementation it is bounded by one clock period plus Chapter 16.5's 24.2 ns; a value larger than that means the intervals do not sum to the cycle — Section 3's cfg_sum_mismatch, seen at run time rather than at configuration time — and the error accumulates, so the measurement grows without limit.

6. Where the Cycle's Start Comes From

Section 5 anchored the phase to base_sec/base_ns. This section is why that field exists and what happens when it is chosen badly.

The base time is an instant in Chapter 16.2's timescale at which entry 0 of the list begins. Every device computes its position as (now − base) mod cycle, so every device that has the same base and the same cycle is at the same entry — to within the clock's accuracy.

Three properties of the base matter and all three are configuration decisions.

It must be the same on every device. Two devices with bases differing by 500 µs on a 1000 µs cycle are half a cycle out of phase, and every frame transmitted in a window at one arrives outside the window at the other. Nothing detects this except the frames missing their deadlines.

It should be in the past. A base in the future means the schedule is not yet effective, and the standard's behaviour is to compute the position modulo the cycle anyway — so a base 1 hour in the future on a 1000 µs cycle gives a position that is arithmetically correct and semantically meaningless. A base in the past is unambiguous.

And it must be divisible into the cycle sensibly if several schedules interlock. A path whose hops run a 1000 µs cycle and one hop running 1024 µs has a relative phase that advances 24 µs per cycle and wraps in 41 cycles — so the path works for 41 milliseconds at a time.

Which gives a commissioning requirement that looks trivial and is not:

ParameterMust beFailure if not
cycleidentical on every hoprelative phase drifts; works intermittently
base timeidentical on every hopa constant phase offset; frames always miss
intervalssum to the cycleSection 3's drift, accumulating
the clocklocked, on every hopSection 5's phase_valid low

Row two's failure is the one that is easiest to create and hardest to diagnose, because it produces a network in which everything is configured, every device reports a valid schedule, and every scheduled frame arrives one window late at the next hop. The latency is then N × cycle rather than cycle, which for five hops at 1000 µs is 5 ms against a 1 ms deadline.

7. RTL 3 — The Guard Band

Chapter 17.1 §17 derived it. This is the implementation, and the implementation has a choice in it that the derivation did not.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// guard_band_calc -- computes how long before a gate shuts the port
// must stop admitting frames.
//
// Two variants, and the choice is a real one:
//   STATIC : reserve for a maximum frame always -- simple, wasteful.
//   LENGTH : admit a frame if THIS frame fits -- 12.6's length is
//            known before transmission starts, so the check is exact.
// -----------------------------------------------------------------------
module guard_band_calc
  import tas_pkg::*;
#(
  parameter int LINE_RATE_MBPS = 1000,
  parameter int MTU_OCTETS     = 1518,
  // 16.5 section 16's assembled budget. Doubled: two devices each
  // wrong by that much, in opposite directions.
  parameter int SYNC_ERROR_NS  = 24,
  parameter bit LENGTH_AWARE   = 1'b1
)(
  input  logic  clk,
  input  logic  rst_n,

  input  ns_t   time_to_gate_close_ns,
  input  logic  frame_pending,
  input  logic [13:0] frame_octets,

  output ns_t   static_guard_ns,
  output ns_t   this_frame_ns,
  output logic  admit,
  output ns_t   wasted_this_window_ns,
  output logic [31:0] c_admitted,
  output logic [31:0] c_held,
  output logic [15:0] guard_pct_x100        // of the window
);

  // MTU/R + 2 x sync_error -- 17.1 section 17's formula, in hardware.
  localparam int MTU_NS   = (MTU_OCTETS * 8000) / LINE_RATE_MBPS;
  localparam int GUARD_NS = MTU_NS + 2*SYNC_ERROR_NS;

  ns_t need;

  always_comb begin
    // A length-aware guard admits a frame if THIS frame fits, which is
    // exact because the length is known before transmission begins --
    // 12.6's store-and-forward has the whole frame; even cut-through
    // has the length field.
    this_frame_ns = ns_t'((32'(frame_octets) * 8000) / LINE_RATE_MBPS)
                  + ns_t'(2*SYNC_ERROR_NS);
    need = LENGTH_AWARE ? this_frame_ns : ns_t'(GUARD_NS);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      admit <= 1'b0; c_admitted <= '0; c_held <= '0;
      wasted_this_window_ns <= '0; guard_pct_x100 <= '0;
    end else begin
      admit <= 1'b0;

      if (frame_pending) begin
        if (time_to_gate_close_ns >= need) begin
          admit      <= 1'b1;
          c_admitted <= c_admitted + 1;
        end else begin
          // The frame does not fit. It waits for the next window --
          // which is section 10's latency contribution, arriving one
          // frame at a time.
          c_held                <= c_held + 1;
          wasted_this_window_ns <= time_to_gate_close_ns;
        end
      end
    end
  end

  assign static_guard_ns = ns_t'(GUARD_NS);

endmodule

Classification: a comparator with two policies. The policies differ by a factor that depends entirely on the traffic's frame-size distribution.

What it teaches: that a length-aware guard band is exact and a static one is a worst case, and the difference is large on small frames. A 64-octet frame needs 0.56 µs at 1 Gb/s; the static guard reserves 12.192 µs. So a window's last 11.6 µs is usable by small frames and not by large ones — and a static guard refuses them all.

And it teaches that the length is always available in time, which is why the exact policy is implementable. Chapter 12.6's store-and-forward has the entire frame before it transmits anything; even cut-through has the length from Chapter 5.5's length field or from the descriptor. There is no case in which a transmitter must decide whether to admit a frame without knowing how long it is.

Deliberately simplified: SYNC_ERROR_NS is a parameter and Chapter 16.5 §14 produces a budget with a composition. A production design takes the live budget — which changes when a link renegotiates or a calibration goes stale — and a guard band computed from a commissioning-time constant is Chapter 16.3 §4's stale-correction problem in a new place.

Production implication: c_held is the count of frames that were ready and did not fit, and it is the measurement that says whether the window is sized correctly. A window in which c_held is a large fraction of c_admitted is a window whose guard band is consuming it — Section 8's last row — and the remedy is a longer window, a shorter frame, or Chapter 17.3's preemption.

8. The Guard Band, Recomputed With the Schedule

Chapter 17.1 §17 computed the band. This computes what it does to a schedule, which is a different and worse number.

The band is subtracted from every window in every cycle, so its cost is a fraction of time rather than a one-off.

Line rateGuard bandOf a 100 µs windowOf a 1000 µs cycle
1 Gb/s12.192 µs12.19%1.22%
10 Gb/s1.263 µs1.26%0.13%
25 Gb/s0.534 µs0.53%0.05%
100 Gb/s0.170 µs0.17%0.02%

And the fraction that matters is the first column against the window, because that is the bandwidth the scheduled class loses.

Then the cycle's length enters, and it enters twice. A shorter cycle reduces the worst-case wait — Section 10 — and increases how often the band is paid:

CycleWindowWorst waitGuard band per cycleUsable fraction of the link
2000 µs200 µs1800 µs0.61%9.39%
1000 µs100 µs900 µs1.22%8.78%
500 µs75 µs425 µs2.44%12.56%
250 µs50 µs200 µs4.88%15.12%
125 µs25 µs100 µs9.75%10.25%
62.5 µs12.5 µs50 µs19.51%0.49%

The usable column has a maximum in it, which is the finding. It rises as the cycle shortens — more windows per second, each losing a fixed banduntil the band approaches the window's length, and then it collapses. At 1 Gb/s the optimum is around a 250 µs cycle and the collapse is at about 12 µs of window.

And the last row is the one to internalise: a 12.5 µs window at 1 Gb/s is 12.192 µs of guard band and 0.308 µs of transmission. The schedule is correct, the bound is met, and 0.49% of the link is delivered.

Which is Chapter 17.3's entire justification, and Section 18 states it as an arithmetic rather than as a motivation.

==

Shortening the schedule's cycle moves two quantities in opposite directions. The worst-case wait is the cycle minus the window and falls linearly, so a 2000 microsecond cycle waits 1800 and a 62.5 microsecond cycle waits 50. The guard band's share is the band divided by the cycle and rises linearly, from 0.61 percent at 2000 microseconds to 19.51 percent at 62.5. Their combination gives a usable fraction of the link with a maximum: 9.39 percent at 2000, 8.78 at 1000, 12.56 at 500, a peak of 15.12 percent at a 250 microsecond cycle, 10.25 at 125, and a collapse to 0.49 percent at 62.5 where a 12.5 microsecond window consists of 12.192 microseconds of guard band and 0.308 of transmission. The sizing rule is therefore the longest cycle that meets the deadline, not the shortest achievable.2000 ususable 9.39%1000 ususable 8.78%250 ususable 15.12%125 ususable 10.25%62.5 ususable 0.49%Wait falls1800 us to 50Band rises0.61% to 19.51%12
Figure 2 — the usable fraction has a maximum, and below it the guard band eats the window.

9. RTL 4 — The Transmission-Eligibility Check

Three conditions, evaluated every cycle, and their conjunction is what a scheduled port actually does.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// tx_eligibility_check -- decides which class may start a frame now.
//
// It sits between 13.4 section 11's scheduler and the transmit path,
// in exactly the position 14.4 section 9's PFC gate occupies -- and it
// is a second mask on the same eligibility vector.
// -----------------------------------------------------------------------
module tx_eligibility_check
  import tas_pkg::*;
(
  input  logic       clk,
  input  logic       rst_n,

  input  gate_vec_t  gates,              // section 3's list
  input  logic       phase_valid,        // section 5
  input  gate_vec_t  q_nonempty,         // 13.4's eight queues
  input  gate_vec_t  pfc_paused,         // 14.4 section 8's timers
  input  logic       guard_admits,       // section 7
  input  logic       tx_in_frame,

  output gate_vec_t  eligible,
  output logic       all_blocked,
  output logic       blocked_by_gate,
  output logic       blocked_by_guard,
  output logic [31:0] c_gate_blocked [NUM_CLASSES],
  output logic [31:0] c_guard_blocked,
  output logic [31:0] c_no_phase
);

  always_comb begin
    int c;
    for (c = 0; c < NUM_CLASSES; c++) begin
      // Four conditions, all required. The gate is new; the other
      // three were already there.
      eligible[c] = gates[c]              // the schedule permits it
                 && q_nonempty[c]         // there is something to send
                 && !pfc_paused[c]        // 14.4's neighbour agrees
                 && guard_admits          // section 7: it fits
                 && phase_valid;          // section 5: we know the time
    end
  end

  assign blocked_by_gate  = (q_nonempty & ~gates) != '0;
  assign blocked_by_guard = (q_nonempty & gates) != '0 && !guard_admits;
  assign all_blocked      = (q_nonempty != '0) && (eligible == '0);

  always_ff @(posedge clk or negedge rst_n) begin
    int c;
    if (!rst_n) begin
      for (c = 0; c < NUM_CLASSES; c++) c_gate_blocked[c] <= '0;
      c_guard_blocked <= '0; c_no_phase <= '0;
    end else begin
      for (c = 0; c < NUM_CLASSES; c++)
        if (q_nonempty[c] && !gates[c]) c_gate_blocked[c] <= c_gate_blocked[c] + 1;
      if (blocked_by_guard) c_guard_blocked <= c_guard_blocked + 1;
      if (!phase_valid && (q_nonempty != '0)) c_no_phase <= c_no_phase + 1;
    end
  end

endmodule

Classification: a four-way conjunction on an eligibility vector. Combinational, and it is the third mask on the same signal.

What it teaches: that a scheduled port now has three independent gating mechanisms and they compose by conjunction. Chapter 13.4 §11's scheduler chooses among eligible classes; Chapter 14.4 §9's PFC gate removes paused ones; and this removes classes whose window is shut. All three subtract and none adds — so a class transmits only when all three permit it, and a frame held by any of them is held.

And it teaches that the three counters must be separate because they name different parties. c_gate_blocked[c] is our own schedule; pfc_paused is the neighbour's decision; c_guard_blocked is the frame's own length against the window's remainder. A single "blocked" counter sends every investigation to the same place, and two of the three are at different ends of the cable — which is Chapter 14.3 §15's attribution argument, arriving for the fourth time.

Deliberately simplified: the gate vector is applied to the class and not to the queue, which assumes Chapter 13.4 §9's mapping is the identity. With eight priorities compressed into four queues — that chapter's §10 — a gate must be derived from the queue's constituent classes, and the conservative derivation is that a queue transmits only when every class mapped to it has its gate open.

Production implication: c_no_phase counts cycles in which frames were waiting and the clock was not locked, and it is the counter that connects Module 16's health to Module 17's function. A device whose servo lost lock — Chapter 16.4 §9's S_HOLDOVERhas a schedule whose phase is drifting, and Section 17's table gives the three possible responses. Without this counter, a synchronisation outage presents as a latency problem.

==

A scheduled port's transmission eligibility is the conjunction of four conditions on the same eight-bit vector. Chapter 13.4's scheduler chooses among classes whose queues are non-empty. Chapter 14.4's priority flow control gate removes classes the neighbour has paused. This chapter's gate removes classes whose schedule window is shut. And the guard band removes frames that would not finish before the window closes. All four subtract and none adds, so a class transmits only when every one of them permits it. The three blocking causes must be counted separately because they name three different parties: our own schedule, the neighbour's decision, and this frame's own length against the window's remainder — and a single blocked counter sends every investigation to the same place while two thirds of them belong elsewhere.Queue non-empty13.4's schedulerNot PFC-paused14.4: the neighbourGate openthis chapter: ourscheduleIt fitsthe frame's own lengthEligiblethe conjunctionc_gated[c]names the neighbourc_gate_blocked[c]names our schedule12
Figure 4 — three masks subtract from one eligibility vector, and each names a different party.

10. The Schedule's Own Contribution to Latency

Chapter 17.1 §8's interference term is gone. What replaces it is a new term that is bounded, and the bound is worth deriving rather than quoting.

A frame becomes ready at some instant. Two cases:

It is inside its class's window and it fits. Then it transmits immediately and the schedule contributes zero.

It is outside its window, or inside but too close to the end. Then it waits until the next occurrence of its window, and the worst case is the whole cycle minus the window — a frame that became ready one nanosecond after its window ended.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
schedule_wait_worst = cycle − window

And that is the entire new term. It is bounded, it is a configuration parameter, and it does not depend on the traffic at all.

Which lets Chapter 17.1's worst case be completed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
worst_case = N × bounded_terms  +  schedule_wait

At 1 Gb/s with five hops, Chapter 17.1 §4's bounded subtotal is 37.12 µs per hop — 185.6 µs.

CycleWindowSchedule waitFive-hop worst caseAgainst a 1 ms deadline
2000 µs200 µs1800 µs1985.6 µsmissed
1000 µs100 µs900 µs1085.6 µsmissed
500 µs75 µs425 µs610.6 µsmet
250 µs50 µs200 µs385.6 µsmet, with margin
125 µs25 µs100 µs285.6 µsmet
62.5 µs12.5 µs50 µs235.6 µsmet

Row two is Chapter 17.1 §20's Run D exactly — 1085.6 µs against a 1000 µs deadline — and row four is that section's row 14, the cycle change that fixed it.

And the schedule wait appears once, not once per hop, which is the subtlety worth stating. A frame that catches its window at hop 1 catches it at every hop, provided the hops share a cycle and a phase — Section 6 — so the wait is paid at the first hop and nowhere else. A path whose hops have different bases pays it at every hop, and five hops at a 1000 µs cycle is 5 ms.

Which is why Section 6's base-time requirement is not a detail. The difference between one schedule wait and N of them is the difference between meeting the deadline and missing it by a factor of five, and both configurations look identical in every status output.

11. RTL 5 — Chapter 17.1 §20's Run D, Fixed

Section 10's table has the answer in it. This module is what lets a design find it without a spreadsheet.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// schedule_latency_model -- computes a path's worst case from the
// schedule's parameters, and searches for a cycle that meets a
// deadline.
//
// The search is worth having in hardware because both terms move: a
// shorter cycle reduces the wait and raises the guard-band overhead --
// section 8 -- so the answer is a constrained optimum rather than a
// limit.
// -----------------------------------------------------------------------
module schedule_latency_model
  import tas_pkg::*;
#(
  parameter int LINE_RATE_MBPS = 1000,
  parameter int GUARD_NS       = 12192      // section 7, at 1 Gb/s
)(
  input  logic  clk,
  input  logic  rst_n,

  input  logic  eval,
  input  logic [7:0] n_hops,
  input  ns_t   bounded_per_hop_ns,         // 17.1 section 4: 37 120
  input  ns_t   cycle_ns,
  input  ns_t   window_ns,
  input  logic  hops_share_phase,           // section 6
  input  ns_t   deadline_ns,

  output ns_t   schedule_wait_ns,
  output ns_t   worst_case_ns,
  output logic  meets_deadline,
  output ns_t   margin_ns,
  output logic [15:0] guard_overhead_x100,
  output logic [15:0] usable_fraction_x100,
  output ns_t   suggested_cycle_ns,
  output logic  no_cycle_works
);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      schedule_wait_ns <= '0; worst_case_ns <= '0;
      meets_deadline <= 1'b0; margin_ns <= '0;
      guard_overhead_x100 <= '0; usable_fraction_x100 <= '0;
      suggested_cycle_ns <= '0; no_cycle_works <= 1'b0;
    end else if (eval) begin
      automatic ns_t wait_one, bounded, wc, try_cycle, try_win;
      automatic bit found;

      wait_one = (cycle_ns > window_ns) ? (cycle_ns - window_ns) : '0;
      // Section 10: the wait is paid ONCE if the hops share a phase,
      // and once per hop if they do not.
      schedule_wait_ns <= hops_share_phase
                        ? wait_one
                        : (wait_one * ns_t'(n_hops));

      bounded = bounded_per_hop_ns * ns_t'(n_hops);
      wc      = bounded + (hops_share_phase ? wait_one
                                            : (wait_one * ns_t'(n_hops)));
      worst_case_ns  <= wc;
      meets_deadline <= (wc <= deadline_ns);
      margin_ns      <= (deadline_ns > wc) ? (deadline_ns - wc) : '0;

      guard_overhead_x100  <= (cycle_ns == 0) ? 16'd0
                            : 16'((32'(GUARD_NS) * 10000) / 32'(cycle_ns));
      // What the scheduled class actually gets: the window less the
      // guard band, as a fraction of the cycle. Section 8's collapse.
      usable_fraction_x100 <= ((cycle_ns == 0) || (window_ns <= ns_t'(GUARD_NS)))
                            ? 16'd0
                            : 16'(((32'(window_ns) - 32'(GUARD_NS)) * 10000) /
                                  32'(cycle_ns));

      // Search: halve the cycle until the deadline is met, keeping the
      // window at a tenth. Stop when the guard band consumes it.
      found = 1'b0; try_cycle = cycle_ns;
      repeat (8) begin
        try_win = try_cycle / 10;
        if (!found && (try_win > ns_t'(GUARD_NS)) &&
            ((bounded + (try_cycle - try_win)) <= deadline_ns)) begin
          found              = 1'b1;
          suggested_cycle_ns <= try_cycle;
        end
        try_cycle = try_cycle >> 1;
      end
      no_cycle_works <= !found;
    end
  end

endmodule

Classification: a closed-form evaluator with a bounded search. Eight iterations, evaluated on demand.

What it teaches: that hops_share_phase multiplies the answer by the hop count, and it is a configuration property rather than a measurable one. Section 6: hops with different base times pay the schedule wait at every hop. At a 1000 µs cycle over five hops that is 4500 µs of wait against 900 — and the two configurations are indistinguishable in every per-device status output. The model has to be told, which is why the input exists and why commissioning must verify it.

And it teaches that the search terminates on try_win > GUARD_NS rather than on the deadline, which is Section 8's collapse as a stopping condition. A cycle short enough that its window is smaller than the guard band delivers nothing, so it is not a solution however well it meets the deadline — and no_cycle_works distinguishes "the deadline is unachievable" from "we did not look hard enough".

Deliberately simplified: the window is assumed to be a tenth of the cycle throughout the search. A real schedule allocates windows by class bandwidth requirement, so the search is over a two-dimensional space — and the honest version reports a frontier rather than a single suggestion.

Production implication: usable_fraction_x100 is the number that stops a design shortening the cycle indefinitely. At 1 Gb/s it peaks around a 250 µs cycle at 15.12% and collapses to 0.49% at 62.5 µs — so a design chasing latency by halving the cycle passes through the optimum and then loses the link. The counter makes the peak visible, and Chapter 17.3 moves it.

12. What a Cycle Change Costs

Section 10 showed a shorter cycle meeting a deadline that a longer one missed. This is the other side of that ledger.

Two quantities move in opposite directions as the cycle shortens:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
schedule_wait  =  cycle − window          falls linearly
guard_overhead =  guard_band / cycle      rises linearly

And the product of those two behaviours is a usable fraction with a maximum:

CycleWaitGuard overheadUsable
2000 µs1800 µs0.61%9.39%
1000 µs900 µs1.22%8.78%
500 µs425 µs2.44%12.56%
250 µs200 µs4.88%15.12%
125 µs100 µs9.75%10.25%
62.5 µs50 µs19.51%0.49%

Halving the cycle halves the latency contribution and doubles the guard-band overhead — which is a clean trade until the window approaches the band, at which point the usable fraction falls off a cliff rather than declining.

And there is a third cost that the table does not show and that a real deployment feels: a shorter cycle means more schedule entries per second to execute, more list wraps, and — Section 14 — more frequent opportunities for an installation to go wrong.

CycleCycles per secondGCL entries executed per second, 3-entry list
2000 µs5001500
1000 µs10003000
250 µs400012 000
62.5 µs16 00048 000

None of those is difficult for hardware — 48 000 entry advances per second is a 21 µs period against a 2 ns clock — and all of them multiply the management burden, because a schedule change disrupts one cycle and a shorter cycle means more of them per unit time.

Which gives the sizing rule: choose the longest cycle that meets the deadline, not the shortest that is achievable. Section 11's search does exactly that — it halves until the deadline is met and stops — and a design that halved further bought margin it did not need with bandwidth it did.

13. RTL 6 — Installing a Schedule Atomically

The hardest part of the chapter, and it is hard for a reason that has nothing to do with the schedule.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// schedule_installer -- swaps the live and shadow gate-control lists
// at an instant agreed across every device in the path.
//
// The swap is atomic AT THIS DEVICE -- section 3's single flop. It is
// not atomic ACROSS devices, because "the same instant" is bounded by
// 16.5's 24.2 ns and nothing closes that window.
// -----------------------------------------------------------------------
module schedule_installer
  import tas_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [47:0] ptp_sec,
  input  ns_t         ptp_ns,
  input  logic        ptp_locked,

  // The new schedule's effective instant, distributed to every device
  // by management. It must be the SAME value everywhere.
  input  logic        arm,
  input  logic [47:0] effective_sec,
  input  ns_t         effective_ns,
  input  logic        shadow_valid,
  input  logic        shadow_sum_ok,          // section 3's check

  // 16.5 section 14's live budget, not a commissioning constant.
  input  logic [15:0] sync_budget_ns,

  output logic        swap_now,
  output logic        armed,
  output logic        install_refused,
  output logic [7:0]  refuse_reason,
  output ns_t         disagreement_window_ns,
  output logic [31:0] c_installs,
  output logic [31:0] c_refused,
  output logic [31:0] c_late_arm              // armed after the instant
);

  logic [63:0] now_abs, eff_abs;
  always_comb begin
    now_abs = {16'd0, ptp_sec} * 64'd1_000_000_000 + {32'd0, ptp_ns};
    eff_abs = {16'd0, effective_sec} * 64'd1_000_000_000 + {32'd0, effective_ns};
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      armed <= 1'b0; swap_now <= 1'b0;
      install_refused <= 1'b0; refuse_reason <= '0;
      c_installs <= '0; c_refused <= '0; c_late_arm <= '0;
    end else begin
      swap_now        <= 1'b0;
      install_refused <= 1'b0;

      if (arm && !armed) begin
        // Four preconditions. Each one, violated, produces a path in
        // which the devices are running different schedules and no
        // device is at fault.
        if (!ptp_locked) begin
          install_refused <= 1'b1; refuse_reason <= 8'd1;
          c_refused <= c_refused + 1;
        end else if (!shadow_valid) begin
          install_refused <= 1'b1; refuse_reason <= 8'd2;
          c_refused <= c_refused + 1;
        end else if (!shadow_sum_ok) begin
          install_refused <= 1'b1; refuse_reason <= 8'd3;
          c_refused <= c_refused + 1;
        end else if (eff_abs <= now_abs) begin
          // The instant has already passed. Swapping now would put
          // this device on the new schedule while its neighbours are
          // still waiting -- a permanent phase split, not a transient.
          install_refused <= 1'b1; refuse_reason <= 8'd4;
          c_late_arm      <= c_late_arm + 1;
          c_refused       <= c_refused + 1;
        end else begin
          armed <= 1'b1;
        end
      end

      if (armed && (now_abs >= eff_abs)) begin
        swap_now   <= 1'b1;
        armed      <= 1'b0;
        c_installs <= c_installs + 1;
      end
    end
  end

  // The window during which two devices may be on different
  // schedules: each is wrong about `now` by up to the budget, in
  // opposite directions. Section 14.
  assign disagreement_window_ns = ns_t'(2 * 32'(sync_budget_ns));

endmodule

Classification: a deferred trigger with four preconditions. One comparison, and the comparison is against an absolute time.

What it teaches: that the swap is scheduled rather than commanded, and that is the only way a distributed configuration change can be coordinated at all. A management write that says swap now arrives at different devices at different times — a management bus is not synchronous and a software loop over ten switches takes milliseconds. A write that says swap at time T arrives whenever it arrives and takes effect together, to within the clock's accuracy.

And it teaches that refuse_reason = 4 — an instant already past — is a refusal rather than a best-effort swap, and the distinction matters enormously. A device that swaps late is on the new schedule while its neighbours are still on the old one, permanently — not for a window, but until the next install. Refusing leaves it on the old schedule with every neighbour, which is a consistent state, and c_late_arm says the management system's distribution was too slow.

Deliberately simplified: sync_budget_ns is taken as a scalar where Chapter 16.5 §14 produces a budget with random and systematic parts. The disagreement window should use the systematic part doubled plus a few sigma of the random — because two devices' random errors are independent and their systematic ones may not be. The scalar is conservative and the composition is better.

Production implication: disagreement_window_ns is an output rather than an internal value because it is the number a commissioning engineer must compare against the cycle. At Chapter 16.5's 24.2 ns it is 48.4 ns, which against a 62.5 µs cycle is 0.077% — negligible. On an uncalibrated deployment at 1 µs it is 2 µs — 3.2% of the cycle — and on a network synchronised by NTP at 1 ms it is 2 ms, which is thirty-two whole cycles. Section 14 is what happens then.

==

A schedule change is distributed as an effective instant in PTP time rather than as a command. Each device writes the new gate-control list into its shadow bank, arms, and swaps when its own clock reaches the instant — a single flop toggle, atomic at that device. Across devices it is not atomic: each clock is wrong by up to Chapter 16.5's assembled budget and the two errors can oppose, so there is a window of twice that budget during which one device has swapped and another has not. At a calibrated 24.2 nanoseconds the window is 48.4 nanoseconds, which is 0.077 percent of a 62.5 microsecond cycle. Uncalibrated at one microsecond it is 2 microseconds and 3.2 percent. Under NTP at one millisecond it is 2 milliseconds, or thirty-two whole cycles, which is not a window at all. Nothing in the scheduler shortens it; only a better clock does.Swap at time Tnot 'swap now'Each device armsshadow bank writtenAtomic hereone flop toggleNot atomic across2 x sync error24.2 ns: 48.4 ns0.077% of a cycle1 us: 2 us3.2% of a cycleNTP: 2 msthirty-two cycles12
Figure 3 — the swap is atomic at each device and not across them, and the window's width comes from Module 16.

14. The Installation Window Nobody Can Close

Section 13's swap is atomic at one device. Across a path it is not, and the window's length is imported from Chapter 16.5 rather than chosen here.

Two adjacent devices each believe the effective instant has arrived when their clock says so. Each clock is wrong by up to the synchronisation budget, and the two errors can be in opposite directions — so there is an interval of up to twice the budget during which one device has swapped and the other has not.

SynchronisationDisagreement windowOf a 1000 µs cycleOf a 62.5 µs cycle
Chapter 16.5's 24.2 ns48.4 ns0.005%0.077%
uncalibrated, 1 µs2 µs0.2%3.2%
NTP, 1 ms2 ms200%3200% — thirty-two cycles

And what happens during the window depends entirely on how different the two schedules are.

If the new schedule differs only in an interval's length, a frame transmitted by the early device during what the late device still considers another class's window arrives into a shut gate and waits. One frame, one extra cycle of latency.

If the new schedule reassigns a class's window to a different part of the cycle, every frame in flight during the disagreement arrives at the wrong time, and the effect lasts for the window's duration.

And if the two devices' cycles differ — a schedule change that alters the cycle length — the disagreement is not a window at all. The devices' phases diverge until both have swapped, and if the management distribution failed on one device, they never reconverge.

Which gives the operational rule that follows from the arithmetic:

ChangeDisruptionSafe?
an interval's length, same cycleframes in the windowyes, with a good clock
a window's positionone window's worth of framesyes
the cycle length itselfphases diverge until both swaponly if every device swaps
adding a hop mid-installthe new hop is on no scheduleno — install before connecting

Row three is why a cycle change is a maintenance operation rather than a tuning knob, which matters because Section 12's whole argument was about choosing the cycle. The choice is made once; changing it is an outage of at least one cycle on every device, and an unbounded one if the install fails anywhere.

And the deepest point is that the window cannot be closed by anything in this chapter. It is 2 × sync_error and the sync error is Chapter 16.5's assembled budget — which that chapter showed has an irreducible floor. A better schedule installer does not help; a better clock does, and it is the only thing that does.

15. RTL 7 — Schedule Telemetry

Six numbers, and the useful ones say whether this device's schedule agrees with the path's rather than whether it is executing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// schedule_telemetry -- what an operator needs to decide whether a
// scheduled port is working AND whether it agrees with its neighbours.
//
// The second question is the one that matters: a device can execute a
// perfectly valid schedule that nobody else is running.
// -----------------------------------------------------------------------
module schedule_telemetry
  import tas_pkg::*;
(
  input  logic  clk,
  input  logic  rst_n,

  input  logic  wrapped,
  input  logic [31:0] c_cycles,
  input  logic signed [31:0] phase_error_ns,
  input  logic [31:0] c_guard_blocked,
  input  logic [31:0] c_admitted,
  input  logic [31:0] c_gate_blocked [NUM_CLASSES],
  input  logic  phase_valid,
  input  ns_t   cycle_ns,
  input  ns_t   window_ns,
  input  ns_t   guard_ns,
  input  logic  window_tick,

  output logic [15:0] held_pct_x100,
  output logic [15:0] usable_fraction_x100,
  output logic signed [31:0] worst_phase_error_ns,
  output logic  guard_dominates_window,
  output logic  schedule_is_executing,
  output logic [2:0] most_blocked_class,
  output logic [31:0] c_windows
);

  logic [31:0] adm_base, held_base, cyc_base;

  always_ff @(posedge clk or negedge rst_n) begin
    int c;
    if (!rst_n) begin
      held_pct_x100 <= '0; usable_fraction_x100 <= '0;
      worst_phase_error_ns <= '0; guard_dominates_window <= 1'b0;
      schedule_is_executing <= 1'b0; most_blocked_class <= '0;
      c_windows <= '0; adm_base <= '0; held_base <= '0; cyc_base <= '0;
    end else begin
      if (phase_error_ns > worst_phase_error_ns)
        worst_phase_error_ns <= phase_error_ns;

      if (window_tick) begin
        automatic logic [31:0] adm, held;
        automatic int best; automatic logic [31:0] bestv;
        adm  = c_admitted      - adm_base;
        held = c_guard_blocked - held_base;

        // What fraction of ready frames did not fit in their window.
        // A large value means section 8's collapse is happening.
        held_pct_x100 <= ((adm + held) == 0) ? 16'd0
                       : 16'((held * 10000) / (adm + held));

        // Section 8's usable fraction, computed from the live values.
        usable_fraction_x100 <= ((cycle_ns == 0) || (window_ns <= guard_ns))
                              ? 16'd0
                              : 16'(((32'(window_ns) - 32'(guard_ns)) * 10000)
                                    / 32'(cycle_ns));

        guard_dominates_window <= (window_ns <= (guard_ns + (guard_ns >> 2)));

        // Executing means the list is advancing AND the phase is
        // valid. A stalled list and a lost clock look identical from
        // outside and have different remedies.
        schedule_is_executing <= phase_valid &&
                                 ((c_cycles - cyc_base) != 32'd0);

        best = 0; bestv = '0;
        for (c = 0; c < NUM_CLASSES; c++)
          if (c_gate_blocked[c] > bestv) begin bestv = c_gate_blocked[c]; best = c; end
        most_blocked_class <= best[2:0];

        adm_base  <= c_admitted;
        held_base <= c_guard_blocked;
        cyc_base  <= c_cycles;
        c_windows <= c_windows + 1;
      end
    end
  end

endmodule

Classification: a windowed rate calculator with two derived judgements. No control.

What it teaches: that guard_dominates_window is the warning Section 8's table needs as a signal. A window within 25% of the guard band's length is a window delivering almost nothing — the 62.5 µs row's 0.49% — and the condition is checkable from two configured values with no traffic at all. A design that shortened its cycle to meet a deadline and tripped this has met the deadline and lost the link, which no latency measurement would reveal.

And it teaches that schedule_is_executing must conjoin two conditions that look like one. A list that has stopped advancing and a clock that has lost lock both produce a port that transmits nothing scheduled, and they have entirely different remedies: the first is a configuration or logic fault here, the second is Chapter 16.4 §9's servo somewhere else. phase_valid separates them.

Deliberately simplified: most_blocked_class is a plain argmax over eight counters evaluated in one cycle. A production design would report the full vector — the argmax hides the case where two classes are equally starved, and a class blocked 100% of the time because its gate is never open is Section 4's last row, which the argmax will report and not explain.

Production implication: worst_phase_error_ns is the cross-device measurement in disguise. On a correct schedule it is bounded by one clock period plus Chapter 16.5's 24.2 ns; a value that grows without limit is Section 3's interval-sum error accumulating, and a value that is large and constant is a base-time mismatch — Section 6's row two, the failure that produces a path where every scheduled frame arrives one window late.

16. RTL 8 — Conformance for a Scheduled Port

The monitor checks that this port's schedule is well formed and correctly executed. It cannot check that the path agrees.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// schedule_conformance_monitor -- one bit.
//
// It asserts the local properties: the list sums, the gates are
// honoured, the guard band is respected, and the swap was atomic here.
// Section 19's rejected property is the one that claims the PATH is
// consistent, which no device can check.
// -----------------------------------------------------------------------
module schedule_conformance_monitor
  import tas_pkg::*;
(
  input  logic  clk,
  input  logic  rst_n,

  input  logic  transmitted_with_gate_shut,
  input  logic  overran_the_window,
  input  logic  swapped_mid_cycle,          // not at the effective instant
  input  logic  admitted_without_guard,
  input  logic  cfg_sum_mismatch,
  input  logic  cfg_class_never_open,       // section 4's last row
  input  logic  cfg_window_below_guard,     // section 8's collapse
  input  logic  cfg_no_ptp_lock,

  output logic  conformant,
  output logic [15:0] fault_vector,
  output logic [31:0] c_violations
);

  logic v_gate, v_over, v_swap, v_guard;
  logic v_sum, v_never, v_win, v_lock;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      v_gate <= 1'b0; v_over <= 1'b0; v_swap <= 1'b0; v_guard <= 1'b0;
      v_sum <= 1'b0; v_never <= 1'b0; v_win <= 1'b0; v_lock <= 1'b0;
      c_violations <= '0;
    end else begin
      // Runtime violations. The second is the one the guard band
      // exists to prevent, so it firing means the guard is wrong.
      if (transmitted_with_gate_shut) begin v_gate  <= 1'b1; c_violations <= c_violations + 1; end
      if (overran_the_window)         begin v_over  <= 1'b1; c_violations <= c_violations + 1; end
      if (swapped_mid_cycle)          begin v_swap  <= 1'b1; c_violations <= c_violations + 1; end
      if (admitted_without_guard)     begin v_guard <= 1'b1; c_violations <= c_violations + 1; end

      // Standing configuration properties -- wrong from the moment
      // the schedule is written, not from the first frame.
      v_sum   <= cfg_sum_mismatch;
      v_never <= cfg_class_never_open;
      v_win   <= cfg_window_below_guard;
      v_lock  <= cfg_no_ptp_lock;
    end
  end

  assign conformant = !(v_gate || v_over || v_swap || v_guard ||
                        v_sum || v_never || v_win || v_lock);
  assign fault_vector = {8'b0, v_lock, v_win, v_never, v_sum,
                         v_guard, v_swap, v_over, v_gate};

endmodule

Classification: a sticky aggregator with four runtime violations and four standing configuration terms.

What it teaches: that overran_the_window firing means the guard band is wrong rather than that a frame misbehaved. The band exists precisely so that nothing is still transmitting when a gate shuts — so an overrun is a guard band computed from a stale MTU, a stale synchronisation budget, or a length-aware check that used the wrong length. The frame did what it was permitted to do.

And it teaches that cfg_class_never_open is a configuration fault rather than a policy choice. A class whose gate never opens in any entry is a class permanently starved, and the schedule looks complete because every interval is accounted for. The check is a bitwise OR over the list's gate vectors, and a zero in the result is a class nobody will ever hear from.

Deliberately simplified: swapped_mid_cycle is an input, and producing it means comparing the swap instant against the cycle boundary. The standard permits a swap at any point — it is not required to align with a cycle — but a swap mid-cycle leaves the list partway through a schedule whose intervals do not sum from that point, so a production design either aligns the swap to a wrap or accepts one short cycle and says so.

Production implication: conformant here means this port executed this schedule correctly. It does not mean the path is consistent — Section 14's disagreement window, Section 6's base-time requirement and a neighbour running a different cycle are all invisible from here. A fully conformant port on a path whose hops have different base times delivers N × cycle of latency, and Section 19's rejected property is precisely the attempt to claim otherwise.

17. What a Schedule Can and Cannot Promise

ClaimStatus
interference is zero inside the windowguaranteed — every other gate is shut
a missed window costs cycle − windowguaranteed — a configuration parameter
nothing overruns a windowguaranteed, if the guard band is right
the swap is atomic at this deviceguaranteed — one flop
the swap is atomic across the pathno — 2 × sync_error
the path shares a cycle and a phasenot checkable from here
the wait is paid once rather than N timesonly if the bases match
the window delivers usable bandwidthonly if it exceeds the guard band

Rows five through seven are the ones no device can verify, and all three have the same shape: they are properties of the path's configuration rather than of any device's behaviour.

Row seven is the most consequential. A path whose hops share a base pays the schedule wait once; one whose hops do not pays it at every hop — and at a 1000 µs cycle over five hops that is 4500 µs against 900, a factor of five, with every device individually conformant and every status output identical.

And row eight is the one a design creates for itself. Section 8: at 1 Gb/s a window below about 12.2 µs is entirely guard band. The schedule is correct, the bound is met, and the class gets 0.49% of the link — which is a configuration that passes every check in Section 16 and delivers nothing.

Which gives the honest position: a schedule converts an unbounded term into a configured one, and every remaining risk is in the configuration rather than in the mechanism. The gates are eight bits and a comparator; the cycle, the base, the window sizing and the install are where deployments fail.

18. The Cost of a Schedule, Accounted

ComponentCostNote
gate-control list, 64 × (8 + 32) bits, two banks640 octetsdouble buffering is mandatory — Section 3
schedule headers, two28 octets
cycle timer — a modulo and a comparator~100 flopsone divide per cycle in practice
guard-band calculatora multiply and a comparator
eligibility checkan 8-bit AND treea third mask on the same vector
installera 64-bit comparator
telemetry and conformance≈80 flops
total state, one port≈700 octetstrivial
the guard band12.19% of a window at 1 Gb/s0.17% at 100 Gb/s
the usable fraction, at its optimum15.12% of the linkat a 250 µs cycle, 1 Gb/s

Seven hundred octets and an AND tree, and the cost that matters is entirely bandwidth — which is Chapter 17.1 §18's finding confirmed: Module 17's mechanisms cost throughput rather than gates.

And the guard band's composition decides where the next chapter goes:

Line rateMTU termSync termBandMTU's share
1 Gb/s12.144 µs0.048 µs12.192 µs99.6%
10 Gb/s1.214 µs0.048 µs1.263 µs96.2%
25 Gb/s0.486 µs0.048 µs0.534 µs90.9%
100 Gb/s0.121 µs0.048 µs0.170 µs71.5%

At every rate below 100 Gb/s the MTU term is over 90% of the band, so removing it is worth almost the whole band. Chapter 17.3 removes it by interrupting the frame in flight, replacing a maximum frame with a minimum fragment.

And the arithmetic of that replacement is the chapter's closing number. A 64-octet minimum fragment at 1 Gb/s is 0.512 µs, so a preemption-capable guard band is 0.512 + 0.048 = 0.560 µs against 12.192a factor of 21.8.

Guard bandOf a 100 µs windowUsable at a 62.5 µs cycle
without preemption12.192 µs12.19%0.49%
with preemption0.560 µs0.56%19.10%

The last column is the one to carry into the next chapter: a 12.5 µs window at 1 Gb/s goes from delivering 0.49% of the link to delivering 19.10%. Which is not an optimisation — it is the difference between a schedule that works at short cycles and one that does not.

19. Properties Worth Asserting, and One Worth Refusing

The properties divide by object: the list, the timer, the guard band, the eligibility check, the installer, and the configuration.

Group 1 — the gate-control list.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1. The shadow bank is written and the live bank is not. A write to
// the live bank would mix two schedules -- section 3.
property p_writes_target_shadow;
  @(posedge clk) disable iff (!rst_n)
  (cfg_we && live_is_a) |=> $stable(bank_a);
endproperty

// P2. The swap is a single-cycle toggle: atomic at this device.
property p_swap_is_atomic_here;
  @(posedge clk) disable iff (!rst_n)
  swap_now |=> (live_is_a == !$past(live_is_a));
endproperty

// P3. A swap restarts the list at entry 0.
property p_swap_restarts_the_list;
  @(posedge clk) disable iff (!rst_n)
  swap_now |=> (current_index == '0);
endproperty

// P4. The list wraps exactly at its configured length.
property p_wraps_at_length;
  @(posedge clk) disable iff (!rst_n)
  (advance && (current_index == live_hdr.length - 1)) |=> (current_index == '0);
endproperty

// P5. The intervals sum to the cycle. A standing property, wrong from
// configuration time -- 14.1 section 17's argument.
property p_intervals_sum_to_cycle;
  @(posedge clk) disable iff (!rst_n)
  !cfg_sum_mismatch;
endproperty

Group 2 — the cycle timer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P6. The phase is derived from ABSOLUTE time, not counted locally.
// A free-running counter drifts at the oscillator's tolerance.
property p_phase_is_absolute;
  @(posedge clk) disable iff (!rst_n)
  phase_valid |-> (cycle_pos == ((now_abs - base_abs) % live_hdr.cycle_ns));
endproperty

// P7. No phase without a locked clock. 16.4 section 9's S_LOCKED.
property p_no_phase_without_lock;
  @(posedge clk) disable iff (!rst_n)
  !ptp_locked |=> !phase_valid;
endproperty

// P8. An entry advances exactly when its interval has elapsed.
property p_advance_on_interval;
  @(posedge clk) disable iff (!rst_n)
  (phase_valid && (elapsed >= current_interval_ns)) |=> advance;
endproperty

// P9. The phase error is bounded on a correct schedule: one clock
// period plus 16.5's budget.
property p_phase_error_is_bounded;
  @(posedge clk) disable iff (!rst_n)
  (phase_valid && !cfg_sum_mismatch)
    |-> (absv(phase_error_ns) <= (CLK_PERIOD_NS + SYNC_BUDGET_NS));
endproperty

// P10. Losing the phase is counted, so a synchronisation outage does
// not present as a latency problem.
property p_phase_loss_counted;
  @(posedge clk) disable iff (!rst_n)
  $fell(phase_valid) |=> $changed(c_phase_lost);
endproperty

Group 3 — the guard band.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P11. THE property the band exists for: nothing is admitted that
// cannot finish before the gate shuts.
property p_nothing_overruns;
  @(posedge clk) disable iff (!rst_n)
  admit |-> (time_to_gate_close_ns >= this_frame_ns);
endproperty

// P12. The static band is MTU/R + 2 x sync -- 17.1 section 17.
property p_static_band_formula;
  @(posedge clk) disable iff (!rst_n)
  (static_guard_ns == ns_t'(MTU_NS + 2*SYNC_ERROR_NS));
endproperty

// P13. A length-aware band admits a short frame the static one would
// refuse -- the whole reason for the variant.
property p_length_aware_admits_more;
  @(posedge clk) disable iff (!rst_n)
  (LENGTH_AWARE && frame_pending && (frame_octets < MTU_OCTETS) &&
   (time_to_gate_close_ns >= this_frame_ns) &&
   (time_to_gate_close_ns < static_guard_ns)) |=> admit;
endproperty

// P14. A held frame is counted, so a window whose band is eating it
// is visible -- section 8's collapse.
property p_held_is_counted;
  @(posedge clk) disable iff (!rst_n)
  (frame_pending && !admit) |=> $changed(c_held);
endproperty

Group 4 — eligibility.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P15. A class whose gate is shut never transmits. The schedule's
// central guarantee.
property p_shut_gate_never_transmits;
  @(posedge clk) disable iff (!rst_n)
  !gates[c] |-> !eligible[c];
endproperty

// P16. The three masks compose by conjunction and each one can only
// subtract -- 13.4's scheduler, 14.4's PFC gate, and this.
property p_masks_only_subtract;
  @(posedge clk) disable iff (!rst_n)
  eligible[c] |-> (gates[c] && q_nonempty[c] && !pfc_paused[c] && guard_admits);
endproperty

// P17. Without a valid phase nothing scheduled is eligible.
property p_no_phase_no_eligibility;
  @(posedge clk) disable iff (!rst_n)
  !phase_valid |-> (eligible == '0);
endproperty

// P18. Each blocking cause increments its own counter -- 14.3
// section 15's attribution argument, applied to three parties.
property p_attribution_is_separate;
  @(posedge clk) disable iff (!rst_n)
  (q_nonempty[c] && !gates[c]) |=> $changed(c_gate_blocked[c]);
endproperty

Group 5 — the installer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P19. A swap happens at the effective instant and not before.
property p_swap_at_the_instant;
  @(posedge clk) disable iff (!rst_n)
  swap_now |-> (now_abs >= eff_abs);
endproperty

// P20. An instant already past is REFUSED, not swapped late. A late
// swap is a permanent phase split, not a transient.
property p_late_arm_is_refused;
  @(posedge clk) disable iff (!rst_n)
  (arm && (eff_abs <= now_abs)) |=> (install_refused && !armed);
endproperty

// P21. Every precondition, violated, refuses with its own reason.
property p_refusal_has_a_reason;
  @(posedge clk) disable iff (!rst_n)
  install_refused |-> (refuse_reason != 8'd0);
endproperty

// P22. An armed install eventually swaps or is disarmed -- it never
// waits for ever.
property p_armed_resolves;
  @(posedge clk) disable iff (!rst_n)
  $rose(armed) |-> ##[1:$] !armed;
endproperty

// P23. The disagreement window is twice the live budget, not a
// commissioning constant -- section 13.
property p_window_uses_live_budget;
  @(posedge clk) disable iff (!rst_n)
  (disagreement_window_ns == ns_t'(2 * 32'(sync_budget_ns)));
endproperty

Group 6 — configuration and telemetry.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P24. Standing property: every class's gate opens somewhere in the
// list. A class that never opens is permanently starved.
property p_no_class_never_opens;
  @(posedge clk) disable iff (!rst_n)
  !cfg_class_never_open;
endproperty

// P25. Standing property: every window exceeds the guard band.
// Otherwise the window delivers nothing -- section 8's last row.
property p_window_exceeds_guard;
  @(posedge clk) disable iff (!rst_n)
  !cfg_window_below_guard;
endproperty

// P26. The usable fraction is computed from the live window and band,
// so a stale budget cannot hide a collapse.
property p_usable_uses_live_values;
  @(posedge clk) disable iff (!rst_n)
  window_tick |=> (usable_fraction_x100 ==
                   (((window_ns - guard_ns) * 10000) / cycle_ns));
endproperty

// P27. guard_dominates_window fires before the collapse, not after.
property p_collapse_warned_early;
  @(posedge clk) disable iff (!rst_n)
  (window_ns <= (guard_ns + (guard_ns >> 2))) |=> guard_dominates_window;
endproperty

// P28. schedule_is_executing requires BOTH a advancing list and a
// valid phase -- the two failures look identical from outside.
property p_executing_needs_both;
  @(posedge clk) disable iff (!rst_n)
  schedule_is_executing |-> (phase_valid && ($past(c_cycles) != c_cycles));
endproperty

// P29. Standing property: the clock is locked.
property p_clock_locked;
  @(posedge clk) disable iff (!rst_n)
  !cfg_no_ptp_lock;
endproperty

// P30. A gate-shut transmission is the schedule's one real violation
// and is always counted.
property p_violation_counted;
  @(posedge clk) disable iff (!rst_n)
  transmitted_with_gate_shut |=> $changed(c_violations);
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

P11 is the guard band's whole purpose, P15 is the schedule's central guarantee, P20 keeps a failed install from becoming a permanent split, and P25 catches the configuration that meets its bound and delivers nothing. Every one of them is about this port. None claims anything about the path — which is the property this chapter refuses.

20. Verification Scenarios

Seventy-two scenarios. Several have expected outcomes in which the schedule is correct, the bound is met, and the link delivers half a percent.

The gate-control list

#ScenarioExpected
1Write the shadow bank while runninglive bank unchanged
2Single-banked list, rewritten livea mixture of two schedules
3Sameintervals sum to no cycle; gates drift
4Swapone flop toggle, index → 0
5Intervals sum to 999 µs on a 1000 µs cyclecfg_sum_mismatch
6Same, undetecteddrift of 1 µs per cycle — a full window in 0.1 s
764-entry list, two banks640 octets
8List wrapsc_cycles + 1, wrapped
9Compare c_cycles across hops at a PTP instanta drifting device is visible

The cycle timer

#ScenarioExpected
10Phase from (now − base) mod cycleevery device agrees to 24.2 ns
11Free-running local counter insteaddrifts 100 µs/s at 100 ppm
12Samea full window apart within a second
13PTP lock lostphase_valid low, c_phase_lost
14Base times differ by 500 µs, 1000 µs cyclehalf a cycle out of phase
15Sameevery scheduled frame arrives outside its window
16Base in the futuremodulo is arithmetically correct, semantically meaningless
17Cycles 1000 µs and 1024 µs on two hopsphase wraps in 41 cycles — works 41 ms at a time
18Correct schedule, phase_error_nsbounded by a clock period + 24.2 ns
19Interval-sum errorphase_error_ns grows without limit

The guard band

#ScenarioExpected
201 Gb/s, MTU 1518, sync 24.2 ns12.192 µs
2110 Gb/s1.263 µs
22100 Gb/s0.170 µs
23MTU's share at 1 Gb/s99.6%
24MTU's share at 100 Gb/s71.5%
25Static band, 64-octet frame at window endrefused
26Length-aware band, same frameadmitted — it needs 0.56 µs
27Length-aware, 1518-octet framerefused at 12 µs remaining
28Sync budget stale after a renegotiationthe band is wrongChapter 16.3 §4's trap
29Frame heldc_held

Cycle and window sizing

#ScenarioExpected
302000 µs cycle, 200 µs windowwait 1800 µs, usable 9.39%
311000 µs / 100 µswait 900 µs, usable 8.78%
32500 µs / 75 µswait 425 µs, usable 12.56%
33250 µs / 50 µswait 200 µs, usable 15.12% — the optimum
34125 µs / 25 µswait 100 µs, usable 10.25%
3562.5 µs / 12.5 µswait 50 µs, usable 0.49%
36Same12.192 µs of band, 0.308 µs of transmission
37Sameguard_dominates_window
38Halving the cyclewait halves, overhead doubles
3962.5 µs cycle, 3-entry list48 000 entry advances/s

Latency

#ScenarioExpected
40Frame ready at window startschedule contributes zero
41Frame ready 1 ns after the window endswaits cycle − window
42Five hops, bounded terms, 1 Gb/s185.6 µs
431000 µs cycle, shared phase1085.6 µs — Chapter 17.1 §20's Run D
44250 µs cycle, shared phase385.6 µs — that section's row 14
451000 µs cycle, bases differwait paid at every hop: 4500 µs
46Same, five hops total4685.6 µs — a factor of five
47Both configurations' status outputsidentical
48no_cycle_worksthe deadline is unachievable, not under-searched

Installation

#ScenarioExpected
49Management says "swap now" over a busdevices swap milliseconds apart
50Management says "swap at T"together, to 24.2 ns
51Effective instant already pastrefused, refuse_reason = 4
52Same, swapped anywaya permanent phase split
53PTP not lockedrefused, reason 1
54Shadow sum wrongrefused, reason 3
55Disagreement window, 24.2 ns sync48.4 ns — 0.077% of a 62.5 µs cycle
56Same, 1 µs sync2 µs — 3.2%
57Same, NTP at 1 ms2 ms — thirty-two cycles
58Interval length changed, same cycleframes in the window; safe
59Window position changedone window's frames
60Cycle length changedphases diverge until both swap
61Same, install fails on one devicethey never reconverge
62A hop added mid-installon no schedule at all

Eligibility and conformance

#ScenarioExpected
63Gate shut, queue non-emptynot eligible; c_gate_blocked[c]
64Gate open, PFC pausednot eligibleChapter 14.4 §9
65Gate open, frame too longnot eligible; c_guard_blocked
66Phase invalid, frames waitingc_no_phase
67Three masks, one counterevery investigation to the same place
68Class whose gate never openscfg_class_never_open
69Same, schedule otherwise completelooks configured; permanently starved
70A frame overruns a windowoverran_the_window — the band is wrong
71Eight priorities into four queuesa gate must be the AND of its classes'
72Conformant port, path bases mismatchedconformant high, latency 5×

The directed test random stimulus will not produce

A base-time mismatch is not a traffic condition and not a fault — it is a configuration in which every device is individually correct. Random stimulus varies frames; it does not vary one hop's base_ns by 500 µs. And the finding is that the two configurations are indistinguishable in every per-device output while differing by a factor of five in latency, which requires running both and comparing the end-to-end result rather than any instrument.

Setup: a five-hop path at 1 Gb/s, 100 m per hop, Chapter 17.1 §4's bounded terms at 37.12 µs per hop. A 1000 µs cycle with a 100 µs window for class 6, three-entry list, PTP locked at Chapter 16.5's 24.2 ns. A measured class-6 flow with a 1 ms deadline, one frame per cycle, offered at a uniformly random phase.

Stimulus, four runs of 10⁶ frames. Run A — shared base: every hop's base_ns identical. Run B — mismatched base: hop 3's base offset by 500 µs. Run C — shared base, 250 µs cycle, 50 µs window. Run D — shared base, 62.5 µs cycle, 12.5 µs window.

Oracle:

#ObservableA — shared, 1000 µsB — mismatchedC — 250 µsD — 62.5 µs
1cfg_sum_mismatch, every hoplowlowlowlow
2phase_valid, every hophighhighhighhigh
3phase_error_ns, every hop≈0≈0≈0≈0
4conformant, every hophighhighhighlow — window < guard
5c_cycles, every hopequalequalequalequal
6worst end-to-end latency≈1085 µs≈4686 µs≈386 µs≈236 µs
7deadline metnonoyesyes
8any per-device output distinguishing A from Bnone
9schedule wait, measured≈900 µs≈4500 µs≈200 µs≈50 µs
10usable_fraction_x100878878151249
11guard_dominates_windowlowlowlowhigh
12c_held / (c_held + c_admitted)≈0%≈0%≈0%≈96%
13class-6 throughput delivered8.78%8.78%15.12%0.49%
14rerun D with 1518-octet framesevery frame held
15rerun D with 64-octet framesadmitted — length-aware band

Rows 6 and 8 together are the first finding. Run B is 4.3× worse than Run A and not one per-device output differs — every hop's sum check passes, every phase is valid, every phase error is zero, every conformance bit is high, and every cycle count matches. The failure is entirely in a configured constant that each device holds correctly and that differs between them.

Rows 10 to 13 are the second. Run D meets the deadline with the most margin of any run and delivers 0.49% of the link — 96% of ready frames held by the guard band, guard_dominates_window high, and row 4's conformance bit correctly low because Section 16's standing check caught it. A design that chased latency by halving the cycle twice arrived here.

And rows 14 and 15 are the length-aware band earning its keep: at a 12.5 µs window, maximum frames never fit and minimum frames always do — so the class's usable bandwidth depends entirely on its frame-size distribution, which is a dependency the static band does not have and which Chapter 17.3 removes.

21. Debugging a Schedule

Five questions, and the first three need no traffic.

Step 1 — is the schedule well formed? cfg_sum_mismatch, cfg_class_never_open, cfg_window_below_guard. All three are standing properties answerable from the configuration alone, and all three produce a port that looks like it is working. The sum error drifts the phase; the never-open class starves silently; and a window below the guard band delivers nothing.

Step 2 — do the hops agree? The cycle_ns and base_ns of every hop, compared. This is the check Section 20's Run B fails and no device performs. A mismatch in cycle makes the path work intermittently; a mismatch in base makes the schedule wait be paid at every hop — a factor of N in latency with every status output identical.

Step 3 — is the clock adequate? phase_valid, c_phase_lost, and disagreement_window_ns against the cycle. A device in Chapter 16.4 §9's S_HOLDOVER has a phase that is drifting, and an install window of 2 ms against a 62.5 µs cycle is a deployment that cannot run this schedule.

Step 4 — is the window delivering? usable_fraction_x100, held_pct_x100 and guard_dominates_window. A window within 25% of the guard band's length delivers almost nothing — Section 20's Run D — and held_pct_x100 near 100 means the band is refusing everything that is offered.

Step 5 — which mask is blocking? c_gate_blocked[c], c_guard_blocked, and Chapter 14.4's c_gated[c]. Three mechanisms subtract from the same eligibility vector and they name three different parties — our schedule, the frame's own length, and the neighbour's pause. A single blocked counter sends every investigation to the same place and two thirds of them are wrong.

And the finding that ends an investigation: schedule well formed, hops agreeing, clock locked, usable fraction at its optimum, and blocking attributed to the gate during other classes' windows. That is a schedule doing exactly what it was configured to do — and any remaining shortfall is the guard band, which is Chapter 17.3's subject.

22. Common Misconceptions

1 — "A shorter cycle is always better for latency."

The wrong model: the schedule wait is the only thing the cycle affects.

What it costs: Section 20's Run D — the deadline met with the most margin of any run and 0.49% of the link delivered. Halving the cycle halves the wait and doubles the guard band's share, and once the window approaches the band the usable fraction collapses rather than declining.

The corrected model: at 1 Gb/s the usable fraction peaks at about a 250 µs cycle and 15.12%, and the collapse is at roughly a 12 µs window. Choose the longest cycle that meets the deadline — Section 11's search halves until it is met and stops — because margin bought below the optimum is paid for in bandwidth.

2 — "Every device is conformant, so the path is scheduled correctly."

The wrong model: per-device conformance composes.

What it costs: a factor of five in latency with no indication anywhere. Section 20's Run B: hop 3's base_ns offset by 500 µs, every hop's sum check passing, every phase error zero, every conformance bit high — and the schedule wait paid at every hop instead of once, 4686 µs against 1086.

The corrected model: conformant is a claim about this port executing this schedule. The path's consistency — the same cycle, the same base, the same effective instant — is a configuration property no device can verify, and it is the thing commissioning must check.

3 — "The guard band is a small overhead."

The wrong model: 12.192 µs is negligible.

What it costs: 12.19% of a 100 µs window at 1 Gb/s, and 97.5% of a 12.5 µs one. The band is subtracted from every window in every cycle, so its cost is a fraction of time rather than a one-off — and it is 99.6% MTU term at 1 Gb/s, which is why Chapter 17.3 exists.

The corrected model: the band scales with the line rate and the window does not scale with anything. At 100 Gb/s it is 0.17% of a 100 µs window; at 1 Gb/s with a short cycle it can exceed the window entirely. guard_dominates_window fires before the collapse, and it is computed from two configured numbers with no traffic at all.

4 — "A schedule change is a configuration write."

The wrong model: write the new list and it takes effect.

What it costs: a device on the new schedule while its neighbours are on the old one — for milliseconds if the management bus is the distribution mechanism, and permanently if one device's effective instant had already passed and it swapped anyway.

The corrected model: a schedule change is a scheduled operation. Every device is given the same effective instant in Chapter 16.2's timescale and arms; the swap fires when each device's clock reaches it. An instant already past must be refused, not honoured late — a late swap is a permanent phase split rather than a transient.

5 — "Good synchronisation matters to the applications, not to the schedule."

The wrong model: the clock is a service the network provides.

What it costs: two things at once. The guard band contains 2 × sync_error — negligible at 1 Gb/s and 28% of the band at 100and the install window is 2 × sync_error too. At NTP's millisecond that window is 2 ms, or thirty-two 62.5 µs cycles, which is not a window at all.

The corrected model: Chapter 16.5's 24.2 ns is an input to this chapter in two places. A schedule cannot be installed more precisely than the devices agree about the time, and it cannot be safer than the guard band that error contributes to.

6 — "A class with no gate entry just uses the default."

The wrong model: an unmentioned class behaves as it did before.

What it costs: permanent, silent starvation. A gate vector is eight bits and every entry sets all eight — so a class absent from every entry's vector has its gate shut for the entire cycle, for ever. The schedule looks complete: every interval is accounted for and the sum matches the cycle.

The corrected model: the check is a bitwise OR across the list's gate vectors, and a zero bit in the result is a class that will never transmit. cfg_class_never_open is that OR, and it costs eight bits of logic.

23. Interview Reasoning

Q1 — What does a gate schedule actually change?

It replaces Chapter 17.1 §8's unbounded interference term with a configured one. During a class's window every other gate is shut, so a frame transmitted in its own window waits for nothing — interference is zero. A frame that misses its window waits cycle − window, which is a number an operator chose rather than a property of the traffic. The bounded terms are unchanged: Chapter 17.1 §4's 37.12 µs per hop at 1 Gb/s is still there, and a five-hop worst case is 185.6 µs + (cycle − window).

Q2 — Derive the guard band and say what it costs.

MTU/R + 2 × sync_errorChapter 17.1 §17. A gate must stop admitting early enough that nothing is still transmitting when it shuts, because Chapter 12.6 §8 established a frame in flight cannot be aborted; and the two devices may disagree about the closing instant by twice the synchronisation budget. At 1 Gb/s that is 12.144 + 0.048 = 12.192 µs, which is 12.19% of a 100 µs window and 97.5% of a 12.5 µs one. The MTU term is 99.6% of it at 1 Gb/s and 71.5% at 100 — which is why preemption is the next chapter.

Q3 — Why does shortening the cycle stop helping?

Because the two costs move in opposite directions. The wait is cycle − window and falls linearly; the guard band's share is band / cycle and rises linearly. Their combination gives a usable fraction with a maximum — at 1 Gb/s about 15.12% at a 250 µs cycle — and below that the window approaches the band and the fraction collapses rather than declining: 0.49% at 62.5 µs. So the rule is the longest cycle that meets the deadline, not the shortest achievable.

Q4 — How is a schedule installed across a path?

By scheduling the swap rather than commanding it. Every device is given the same effective instant in PTP time, writes the new list into a shadow bank, and arms; the swap fires when each device's own clock reaches the instant. Double buffering is mandatory — a single-banked list rewritten live runs a mixture whose intervals sum to no cycle. And an instant already past must be refused, because a late swap leaves that device on the new schedule alone, permanently, rather than for a window.

Q5 — What is the installation window and why can't it be closed?

2 × sync_error — each device's clock is wrong by up to the budget and the two errors can oppose. At Chapter 16.5's 24.2 ns that is 48.4 ns, which against a 62.5 µs cycle is 0.077%; at 1 µs it is 2 µs and 3.2%; at NTP's millisecond it is 2 ms — thirty-two whole cycles. It cannot be closed by anything in this chapter because its length is Module 16's error budget, and a better installer does not shorten it. A better clock does, and only that.

Q6 — Why can't you assert that the path is running one schedule?

Because the transition is atomic at each device and not across them, and the falsity has a duration imported from another module. The property is false for 2 × sync_error around every effective instant, by construction, on a correct design — 48 ns on a good network and 2 ms on a bad one, with identical logic. The assertable form names the window: the path agrees outside an interval of disagreement_window_ns around the effective instantplus a second property that the window is small against the cycle, which is the condition that makes the disruption tolerable and which fails by a factor of 32 on an NTP-synchronised network.

24. Understanding Check

25. What's Next

This chapter built two of Chapter 17.1 §16's three additions and consumed the third.

The schedule bounds the interference term at cycle − window, which is a configuration parameter. The guard band prevents a frame overrunning into the next window. And the clock — Module 16's, at Chapter 16.5's 24.2 ns — appears twice: inside the guard band, and as the width of Section 14's installation window.

What is left is the guard band's dominant term, and it dominates everywhere below 100 Gb/s.

Line rateGuard bandMTU's share
1 Gb/s12.192 µs99.6%
10 Gb/s1.263 µs96.2%
25 Gb/s0.534 µs90.9%
100 Gb/s0.170 µs71.5%

Chapter 17.3 — Frame Preemption (802.1Qbu / 802.3br) removes it by interrupting the frame in flight. A maximum frame becomes a minimum fragment, so the band at 1 Gb/s goes from 12.192 µs to 0.560 µs — a factor of 21.8 — and Section 18's last table follows: a 12.5 µs window goes from delivering 0.49% of the link to 19.10%.

And the cost is a second MAC, a second framing, and a new kind of partial frame. A preempted frame arrives as fragments, each with its own CRC arrangement, and a receiver must distinguish a fragment from a corrupt frame — which Chapter 6.3's residue check was never designed to do. That chapter's checker gains a case it did not have.

One thread carries directly across. This chapter's guard band exists because Chapter 12.6 §8 established that a frame in flight cannot be aborted — a fact every chapter since has taken as given. Chapter 17.3 is what happens when that stops being true.

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.