USB · Module 11
SOF Packets
The packet addressed to nobody, carrying the timebase every periodic guarantee depends on — and why a device must keep counting frames it was never told about.
Three packet types so far, all addressed to somebody. This one is addressed to nobody, expects no reply, and is the most important packet on the bus for any device with a deadline.
1. A Broadcast With No Recipient
An SOF is a token — Chapter 11.1 §3's fourth, PID 0xA5, low nibble 0101. Same shape as IN, OUT and SETUP: a PID, eleven bits, a CRC5.
What differs is that the eleven bits are a frame number, and no device is named.
| Addressed tokens | SOF | |
|---|---|---|
| Names a device | yes, 7 bits | no |
| Names an endpoint | yes, 4 bits | no |
| The 11 bits mean | ADDR + ENDP | frame number |
| Expects a response | yes | none, ever |
| Who acts on it | one device | every device |
The host transmits one every frame, unconditionally, forever — 1 ms at full and low speed, 125 µs at high speed. It is the most frequent packet on an idle bus, and on a bus with nothing attached but a hub, it is the only packet.
2. Eleven Bits, and What They Cost
The frame number is 11 bits, so it wraps at 2048. At full speed that is 2.048 seconds; at high speed a frame is still 1 ms — the microframes within it are 125 µs and the frame number increments once per millisecond, with the same number repeated across the eight microframes of a frame.
Three consequences follow, and the third is the one that produces bugs:
- A device can time anything shorter than 2.048 s by frame number alone. Longer intervals need a local extension — the frame number aliases, and nothing in the packet says how many times it has wrapped.
- The wrap is not an event. Frame 2047 is followed by frame 0, and that is a perfectly ordinary increment. A device that treats it as a discontinuity will report a fault once every 2.048 seconds forever.
- Seven of those eleven bits look exactly like a device address. Chapter 11.1 §6 measured the decoder that mistakes them — and its symptom was a glitch every 128 frames, which is the low seven bits wrapping.
3. What Depends On It
Every periodic guarantee in Module 10 is expressed in frames, and the SOF is where the frame is named.
Interrupt endpoints. Chapter 10.4 §3's bInterval is a count of frames at full speed and of microframes at high speed. A device that wants to know whether its interval has elapsed is counting SOFs.
Isochronous endpoints. Chapter 10.5 §4's slot is the frame. The MAX_AGE in that chapter's freshness model is denominated in slot boundaries, and a slot boundary is an SOF.
Suspend detection. Chapter 8.6 defined suspend as an absence of bus activity — and on an otherwise idle bus the activity that is absent is the SOF. A device detects suspend by noticing that SOFs stopped.
Which gives the missing-SOF problem its shape. A device that stops hearing SOFs must distinguish:
- a few lost to corruption — the bus is fine, keep going;
- all of them, permanently — the bus is suspended, and Chapter 8.6's power obligations begin.
Those need opposite responses, and the only difference between them is how many in a row. §5's MISS_LIMIT is exactly that threshold.
4. The Two Rules
Everything in this chapter's RTL reduces to two sentences that sound similar and are not.
1 — When an SOF arrives, take its number. Do not predict it.
2 — When an SOF does not arrive, advance the number anyway.
Rule 1 is about authority. The host's number is the truth. A device that computes what the next number should be and uses that has replaced a shared fact with a local guess, and the two diverge permanently the first time they disagree.
Rule 2 is about continuity. A frame boundary that passed unheard still happened. A device that only advances when told will be behind by exactly the number of SOFs it missed — and every periodic deadline it is tracking moves with it.
5. The Frame Tracker, as RTL
// ─────────────────────────────────────────────────────────────────────────
// usb_frame_tracker
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL. It models section
// 4's two rules and section 3's distinction between a few lost SOFs and a
// bus that has gone quiet.
//
// WHAT IT MODELS. The device's view of the current frame number, whether
// that view is trustworthy, and how long it has been since it was confirmed.
//
// WHAT IT DOES NOT MODEL. The SOF packet's reception and CRC5 (Chapters
// 11.1 and 11.6 -- `sof_valid` already means "an SOF arrived intact"); the
// generation of `frame_tick`, which is a local timebase this block consumes
// and does not own; microframes (a high-speed device needs a 3-bit
// sub-counter alongside this, driven the same way); the suspend entry that
// `frame_lost` would trigger (Chapter 8.6); and every consumer of the frame
// number -- the interrupt interval counter, the isochronous slot boundary
// (Chapter 10.5), the schedule.
//
// ── ON `frame_tick` ─────────────────────────────────────────────────────
// Rule 2 needs a local notion of "a frame boundary just passed" that does
// NOT depend on hearing an SOF, because the whole point is to keep counting
// when SOFs are absent. That is what `frame_tick` is. It comes from a local
// timer calibrated while SOFs WERE arriving, and its accuracy bounds how
// long a device can free-run before its count is worthless -- which is a
// real design parameter this block deliberately does not hide.
// ─────────────────────────────────────────────────────────────────────────
module usb_frame_tracker #(
parameter int unsigned TICKS_PER_FRAME = 8, // local timer, for context
// How many consecutive unheard frames before the number is declared
// untrustworthy. Section 3: this threshold is the ONLY thing separating
// "a few packets were corrupted" from "the bus has gone quiet".
parameter int unsigned MISS_LIMIT = 3
)(
input logic clk,
input logic rst_n,
input logic bus_reset, // Chapter 8.3
input logic sof_valid, // an SOF arrived, CRC5 good
input logic [10:0] sof_frame, // its frame number -- THE AUTHORITY
input logic frame_tick, // a local frame boundary -- see header
output logic [10:0] frame_number,
output logic frame_valid, // we have heard at least one SOF
output logic sof_missed, // this frame's SOF did not arrive, or
// the one that did was discontinuous
output logic frame_lost // MISS_LIMIT consecutive -- section 3
);
localparam int unsigned MISS_W = $clog2(MISS_LIMIT + 1);
logic [10:0] frame_q;
logic valid_q;
logic [MISS_W-1:0] miss_q;
logic missed_q;
assign frame_number = frame_q;
assign frame_valid = valid_q;
assign sof_missed = missed_q;
// Derived, not stored (Chapter 8.4): frame_lost is a statement about
// miss_q and cannot drift from it.
assign frame_lost = valid_q && (miss_q >= MISS_LIMIT[MISS_W-1:0]);
// What the NEXT frame number will be. Used for two different things, and
// conflating them is section 7's S1: it is the value to advance TO when no
// SOF arrives, and the value to COMPARE AGAINST when one does. It is never
// the value to load from.
logic [10:0] predicted;
assign predicted = frame_q + 11'd1; // wraps at 2048 by width -- section 2:
// the wrap is an ordinary increment,
// not an event
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_q <= '0; valid_q <= 1'b0; miss_q <= '0; missed_q <= 1'b0;
end else if (bus_reset) begin
// Chapter 8.3: the host is rebuilding its view of the device, and the
// device's view of the frame number is not carried across.
frame_q <= '0; valid_q <= 1'b0; miss_q <= '0; missed_q <= 1'b0;
end else begin
missed_q <= 1'b0; // a pulse, not a level
if (sof_valid) begin
// ── RULE 1: TAKE THE NUMBER. Not `predicted`. ────────────────────
frame_q <= sof_frame;
valid_q <= 1'b1;
// A number that is not the expected one means frames passed that we
// learned nothing about -- neither an SOF nor a local tick. Section
// 7's S3, and the case a bench will not produce by accident.
if (valid_q && (sof_frame != predicted)) missed_q <= 1'b1;
miss_q <= '0; // confirmed: the count restarts
end else if (frame_tick) begin
// ── RULE 2: THE FRAME HAPPENED ANYWAY. ───────────────────────────
frame_q <= predicted;
if (valid_q) begin
missed_q <= 1'b1;
// Saturating, because past MISS_LIMIT the distinction has already
// been made and a wider counter buys nothing.
if (miss_q < MISS_LIMIT[MISS_W-1:0]) miss_q <= miss_q + 1'b1;
end
end
end
end
endmoduleWhat it models. The device's view of the shared frame number, and its confidence in it.
Engineering reason. Because every periodic guarantee the device made is denominated in frames, and the number that names them arrives in a packet that can be lost.
Inputs. A bus reset, an arriving SOF with its number, and a local frame boundary.
State retained. 11 bits of frame number, a validity bit, a saturating miss counter (2 bits for MISS_LIMIT = 3), and the missed pulse. 15 flip-flops.
Outputs. The frame number and its validity, a per-frame miss pulse, and a derived the number is no longer trustworthy.
Hardware implied. An 11-bit incrementer, an 11-bit comparator, a 2-bit saturating counter, and a handful of flags.
Reset behaviour. Everything clears on hard reset and on bus reset — including frame_valid, because a device that has heard no SOF since the reset does not know the frame number and must not pretend to.
Assumptions. That sof_valid implies the CRC5 passed and the PID was SOF; that frame_tick approximates a real frame boundary well enough for MISS_LIMIT frames — the local timer's accuracy is a design parameter, not an implementation detail; and that sof_valid and frame_tick do not both assert for the same frame.
Omissions. The packet, the local timer, microframes, the suspend entry and every consumer — in the header.
What DV should verify. That the frame number never diverges from the bus's, during outages included; that a missed SOF is reported and a received one is not; that a discontinuous SOF is reported; that frame_lost asserts after exactly MISS_LIMIT consecutive misses and not before; that the wrap from 2047 to 0 is not reported as a discontinuity; and that frame_valid is low until the first SOF.
Free-running through an outage
8 cycles6. Mutation Test
Four mutations over 713 frames, of which 561 SOFs were heard and 142 missed, plus two deliberate blackout periods in which the device heard neither an SOF nor a local tick.
The checks are all against the bus's frame number, which the bench tracks independently of anything the design does.
| frame number diverged | miss unreported | ghost miss | frame_lost late | |
|---|---|---|---|---|
| golden | 0 | 0 | 0 | 0 |
| S1 predict instead of load | 703 | 0 | 0 | 0 |
| S2 stop counting during a gap | 142 | 0 | 0 | 0 |
| S3 no discontinuity detection | 0 | 3 | 0 | 0 |
| S4 misses not accumulated | 0 | 0 | 0 | 42 |
S1 — predict instead of loading
if (sof_valid) frame_q <= predicted; // MUTANT S1: ignores the wireMeasured: wrong on 703 of 713 frames.
§4's rule 1 removed. The device computes the next number instead of taking the one it was sent, so the SOF packet's payload is never used for anything. The first prediction is already wrong — the device starts at 0 and predicts 1 while the bus says 100 — and nothing ever corrects it.
And the clue is in what the mutant does not break. Miss detection, frame_lost and validity are all perfect. The device is confidently, consistently, precisely wrong, and every self-consistency check it could run on itself would pass.
S2 — stop counting during a gap
Measured: wrong on exactly 142 frames — precisely the number of SOFs that were missed. Not one more.
§4's rule 2 removed. The exactness of that number is the finding: the device is wrong for the duration of each outage and correct the instant one ends, because the next SOF reloads it.
Which makes this the harder of the two to detect in the field. A device with S1 is permanently wrong and obviously so. A device with S2 is right whenever anyone looks, and wrong only while the bus is losing packets — which is exactly when its isochronous slot boundaries and interrupt intervals are being computed from a stale number.
S3 — do not detect a discontinuous SOF
Measured: 3 unreported misses — and it escaped the bench completely until the stimulus was changed. §8.
An SOF arrives carrying a number that is not the one after the device's. That can only happen if frames passed that the device learned nothing about — neither an SOF nor a local tick — which means its local timebase was also down.
The consequence is that the device silently loses its place. It reloads the correct number, reports nothing, and every periodic obligation it was counting toward has quietly slipped. Chapter 10.5's stream does not know its slot moved.
S4 — do not accumulate consecutive misses
Measured: frame_lost failed to assert on 42 frames where it should have.
Individual misses are still reported; what is lost is the distinction §3 exists to make. A device that cannot count consecutive misses cannot tell a few corrupted packets from a bus that has gone quiet — and those require opposite responses, one of them involving Chapter 8.6's power obligations.
7. The Assertions
// ─────────────────────────────────────────────────────────────────────────
// Classification: TEACHING ASSERTIONS about the frame tracker.
// F-properties concern the NUMBER. M-properties concern the reporting.
// Note that F1 is stated against an EXTERNAL reference (the bus's frame
// number), which is not a signal of the design -- it must be bound in from
// the bench or a monitor. Chapter 11.3 section 8 is the argument for why a
// property sourced from outside the design is worth the inconvenience.
// ─────────────────────────────────────────────────────────────────────────
// F1 -- THE NUMBER NEVER DIVERGES. The only property that matters, and the
// only one that catches BOTH section 6's S1 and S2 -- because it is written
// against what the number IS, not against how it is updated.
property p_number_tracks_bus;
@(posedge clk) disable iff (!rst_n)
frame_valid |-> (frame_number == bus_frame_number);
endproperty
assert property (p_number_tracks_bus); // via bind
// F2 -- A RECEIVED SOF IS LOADED, NOT COMPUTED. Section 4's rule 1, stated
// separately from F1 for the same reason Chapter 10.4's C1 was: F1 tells you
// something is wrong, this tells you WHICH RULE broke.
property p_sof_is_authoritative;
@(posedge clk) disable iff (!rst_n)
(sof_valid && !bus_reset) |=> (frame_number == $past(sof_frame));
endproperty
assert property (p_sof_is_authoritative);
// F3 -- AN UNHEARD FRAME STILL ADVANCES. Section 4's rule 2.
property p_tick_advances;
@(posedge clk) disable iff (!rst_n)
(frame_tick && !sof_valid && !bus_reset && frame_valid)
|=> (frame_number == $past(frame_number) + 11'd1);
endproperty
assert property (p_tick_advances);
// F4 -- AND THE NUMBER MOVES FOR NO OTHER REASON. Without this, F2 and F3
// are satisfied by a counter that also advances at random.
property p_no_spontaneous_advance;
@(posedge clk) disable iff (!rst_n)
(frame_number != $past(frame_number))
|-> $past(sof_valid || frame_tick || bus_reset);
endproperty
assert property (p_no_spontaneous_advance);
// M1 -- EVERY UNHEARD FRAME IS REPORTED. Section 6's S2 and S3.
property p_miss_reported;
@(posedge clk) disable iff (!rst_n)
(frame_valid && frame_tick && !sof_valid && !bus_reset) |=> sof_missed;
endproperty
assert property (p_miss_reported);
// M2 -- AND A DISCONTINUOUS SOF IS TOO. Section 6's S3 specifically. The
// case a bench does not produce by accident -- section 8.
property p_discontinuity_reported;
@(posedge clk) disable iff (!rst_n)
(frame_valid && sof_valid && !bus_reset
&& (sof_frame != frame_number + 11'd1)) |=> sof_missed;
endproperty
assert property (p_discontinuity_reported);
// M3 -- AND NOTHING ELSE IS. A continuous SOF is not a miss -- INCLUDING
// the wrap from 2047 to 0, which this property gets right for free because
// `frame_number + 1` wraps in 11-bit arithmetic. Section 2: the wrap is an
// ordinary increment. Writing the comparison with a wider type would break
// exactly one frame in 2048.
property p_no_ghost_miss;
@(posedge clk) disable iff (!rst_n)
(frame_valid && sof_valid && !bus_reset
&& (sof_frame == frame_number + 11'd1)) |=> !sof_missed;
endproperty
assert property (p_no_ghost_miss);
// L1 -- LOST IS EXACTLY MISS_LIMIT CONSECUTIVE. Section 6's S4. Both halves
// in one equivalence, because "not too late" and "not too early" are
// separate defects and a one-sided property catches only one.
property p_lost_is_exact;
@(posedge clk) disable iff (!rst_n)
frame_lost == (frame_valid && (consecutive_misses >= MISS_LIMIT));
endproperty
assert property (p_lost_is_exact); // via bindF1 is the property, and it is the awkward one. It references bus_frame_number, which is not a signal of the design — it has to be bound in from a bus monitor. That inconvenience is the reason it works: it is the only statement in the set that is sourced from outside the device's own view, and Chapter 11.3 §8 measured what happens when every check shares the design's assumptions.
M3 is a property that is correct by arithmetic rather than by care. frame_number + 11'd1 wraps at 2048 because of its width, so frame 2047 followed by frame 0 satisfies it automatically. Written with a wider intermediate type it would fail exactly one frame in 2048 — every 2.048 seconds, forever, which is §2's second consequence arriving as a property bug rather than a design bug.
8. Verification
This chapter's commit point is the device's frame number was the bus's frame number, at every instant, including the ones nobody was watching.
Stimulus. Twenty clean frames; one missed SOF; MISS_LIMIT consecutive misses; a 40-frame outage followed by recovery; two blackout periods in which neither signal arrives; the wrap from 2047 through 0; a bus reset; and 600 randomised frames with SOFs lost one time in five.
Observation. The frame number against the bus's, every frame — including during outages, which is where S2 lives and where a check that only samples after recovery sees nothing.
Reference model. A single counter representing the bus's frame number, advanced once per frame independently of what the device heard. It is not a model of the design — it is a model of the environment, which is what makes it able to disagree.
Coverage — crosses:
sof_valid×frame_tick— all four, and the0 × 0cell is the one §8 is about- consecutive misses from 0 to
MISS_LIMIT + 1 - a received SOF that is continuous × discontinuous
- the 2047 → 0 wrap, received and free-run
- bus reset at each miss count
Negative cases with defined outcomes: the number never diverges from the bus's; it never advances without a cause; a continuous SOF is never reported as a miss — the wrap included; and frame_lost never asserts before MISS_LIMIT.
9. Debugging: the Stream That Glitches Only on a Busy Bus
An isochronous audio device is clean on a quiet bus. On a busy one it produces brief artefacts. The artefacts correlate with bus load but not with the device's own data rate, and the device reports no underruns.
What does no underruns rule out? Chapter 10.5's producer path. The device had data ready every time it was asked — so the problem is not that it was empty.
What is left? That it was asked at the wrong time, or that it believed the wrong time had come. Both are frame-number problems.
Why would bus load matter? Because load causes packet corruption, and corruption costs SOFs. The device is not losing data; it is losing the packets that tell it which frame it is.
Which mutation does this look like? §6's S2 — the device stops counting during the gap, so its slot boundaries slip by exactly the length of the outage, then snap back. Brief artefacts, correlated with load, self-correcting, which is precisely the described symptom.
How do you confirm it? Instrument the device's frame number and compare it with the SOF numbers on the wire during an outage. Not after — after is when a device with S2 is correct again, which is why this bug survives a lab session spent looking at steady state.
What if the frame number is right throughout? Then the tracker is fine and the fault is downstream — in whatever converts a frame number into a slot boundary. That is a different block and a different chapter, and the point of checking first is that these two look identical from outside.
The signature to keep: artefacts that scale with bus load rather than with the device's own traffic point at the timebase, not the data path — and a timebase defect that heals itself has to be caught while it is happening.
10. Common Misconceptions
11. Reason It Through
A device must assert a signal exactly 5 seconds after it is configured. The engineer proposes counting SOFs: 5000 frames at 1 ms each.
Does the arithmetic work? Yes — 5000 frames is 5 seconds at full speed, and the device will see 5000 SOFs.
What is the first problem? §2: the frame number wraps at 2048, so it cannot be used as an absolute clock over 5 seconds. But the engineer said count SOFs, not read frame numbers — a local counter incremented on each SOF has no wrap problem at all.
So what is actually wrong with it? The SOFs the device does not hear. §6 measured 142 missed out of 713 on a lossy bus. A counter incremented only on received SOFs runs slow by exactly the loss rate, and the error accumulates — over 5000 frames a 1% loss rate is 50 ms of drift.
What is the fix? Count frames, not SOFs — increment on sof_valid || frame_tick, which is §5's block already doing the work. The count then advances once per frame whether or not the packet arrived.
Is that exact? No, and it is worth being clear about the residue. During an outage the count depends on the local timer, whose accuracy is a real parameter. §5's header says so deliberately: frame_tick is not free, and how long a device can free-run before its count is worthless is a design decision somebody has to make.
What if 5 seconds must be accurate regardless? Then the SOF is the wrong source entirely and the device needs its own timebase — with the SOF used to discipline it rather than to drive it. Which is the general shape: a remote reference is good for agreement and a local oscillator is good for continuity, and a system needing both uses each for what it is good at.
And the transferable point: counting the messages that announce an event is not the same as counting the event. The difference is exactly the loss rate — invisible on a clean bus, unbounded on a bad one, and always in the same direction.
12. Understanding Check
13. Summary
The SOF is a token addressed to nobody, broadcast every frame, expecting no reply. Its eleven bits are a frame number rather than an address and an endpoint, and only the PID says so.
What it establishes is not a clock but an agreement — a number both ends share. Every periodic guarantee in Module 10 is denominated in frames, Chapter 10.5's slot is a frame, and Chapter 8.6's suspend is detected by the SOF's absence.
Eleven bits wrap at 2048 — 2.048 seconds — and the wrap is an ordinary increment, not an event. A device that special-cases it reports a fault every 2.048 seconds forever.
The whole design reduces to two rules that sound alike and fail oppositely:
- Take the number you are sent; do not predict it. §6 measured prediction wrong on 703 of 713 frames — permanently, with every self-consistency check passing.
- Advance anyway when nothing arrives. §6 measured its absence wrong on exactly 142 of 713 — precisely the SOFs that were missed, self-healing at the end of every outage, which makes it the one that reaches customers.
And distinguishing a few lost packets from a quiet bus is a counter, because those need opposite responses and nothing else separates them.
§8 is Module 11's accumulated verification finding. Four mutations across three chapters escaped their benches, and every one was a state the bench could not reach rather than a check it got wrong: a token carrying this device's address, a toggle-clear applied when the toggle was not already zero, a device's own reason to acknowledge, and a frame in which neither signal arrives.
Each was a combination the bench's own structure made impossible. Ask not what you have covered, but what your stimulus generator cannot produce — and whether the real world is equally constrained.
14. What Comes Next
Four packet types, four shapes, four sets of rules. Chapter 11.5 is where they turn out to be one thing.
Every packet in this module — token, data, handshake, SOF — is the same structure with different fields: a PID whose top nibble is the complement of its bottom, a body, and a check. That complement is not decoration: it makes any single-bit error in the PID byte impossible to miss, which is what lets Chapter 11.3's NAK and STALL sit one nibble-bit apart without danger.
And the four PID groups this module kept referring to — 00, 01, 10, 11 — turn out to partition all sixteen PIDs into exactly four groups of four, which is a fact provable from the values rather than asserted.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
Token Packets
The packet that names a destination before anything is sent to it — four reasons to ignore one, and the SOF whose eleven bits are not an address at all.
- Related topic
Data Packets
Two PIDs carrying identical data and one alternating bit: why it advances on acknowledgement rather than arrival, and the two bench defects that hid two of four mutations.
- Related topic
Handshake Packets
Four one-byte packets and a silence: the difference between not now and not ever, and why a reference model reported zero divergence on a design that was wrong.
- Related topic
USB Packet Structure
Four packet types turn out to be one structure — and the PID check field, measured, catches 100% of single-bit errors and 85.7% of two-bit ones.
Standards & specifications
- Governing standard
- USB-IF (Universal Serial Bus Specification)(opens USB Implementers Forum (USB-IF) in a new tab)
Defines the USB bus — its electrical signalling, connectors, packet and transaction model, device framework and the descriptors a device must expose — together with the device-class specifications layered on it. It does not define host-controller register interfaces (xHCI and EHCI are separate documents) nor any operating system's driver architecture.
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 USB curriculum.
