USB · Module 2
The Root Hub
Where the tree begins: a hub that is architecturally ordinary and structurally unique, living inside the host controller and managed through its registers rather than addressed on the bus. The place bus events first become software-visible, with a teaching register block covering synchronisation, sticky status and clear semantics.
Chapter 2.3 showed how the tree grows: a hub attaches, is configured, and offers ports that further devices or hubs can occupy. The recursion is clean, but it has to terminate upward. Something must provide the first ports — the ones a hub is plugged into — and that something cannot itself be plugged in anywhere.
That is the root hub, and it is worth a chapter of its own for a reason that is easy to miss. It is architecturally an ordinary hub and structurally unlike every other hub in the system, and the difference lands precisely on the hardware/software boundary this module has been building toward.
1. Tier One
The root hub is the hub the host controller provides. Its downstream ports are the connectors on the machine, or the internal ports a board routes to a chip. It occupies the first tier of the tree, and every device in the system is reachable from it — directly, or through hubs plugged into it.
Architecturally it does what Chapter 2.3 described: it holds per-port state, powers and enables ports, observes attachment and removal, resets ports when instructed, and forwards host-originated activity to what is below. If you understood the previous chapter you already understand the root hub's function.
What differs is everything about how that function is reached.
2. Managed by Registers, Not by Addressing
An external hub is managed the way every device is managed: the host sends it requests over the bus, and it answers. To power a port on an external hub, the host controller generates bus activity addressed to that hub, and the hub's controller acts on it.
The root hub cannot work that way, and the reason is a small logical knot worth following. Bus activity is produced by the host controller. The root hub is inside the host controller. For the host to manage the root hub by addressing it on the bus, the controller would have to emit a request onto the bus and then receive its own request back — which is not what a bus is for, and would require the root hub to have an address assigned by a procedure that itself runs over the bus the root hub provides.
So the root hub is managed directly through the host controller's register interface. Software reads a port's status and writes a port's control by accessing controller registers, not by constructing a request for a device. The operations are the same — power, enable, reset, read status — and the access path is entirely different.
Operating systems typically hide this. A USB software stack wants to treat the whole tree uniformly, so it commonly presents the root hub to its upper layers as though it were an ordinary hub device, while the host-controller driver underneath translates those uniform operations into register accesses. This is a genuinely useful abstraction and a genuinely common source of confusion, because it means the thing software calls a hub device may have no bus address and may not exist as a device at all.
3. Where Bus Events Become Software-Visible
Now the part that matters most to a hardware engineer, because it is the first place in Module 2 where an architectural responsibility becomes a concrete register.
Something is plugged into a root-hub port. That is a physical event, occurring at an unpredictable moment, detected by electrical means Module 3 owns. For anything to come of it, that event has to travel:
electrical change on the port
↓
detected in the controller's port logic
↓
recorded as software-visible status
↓
software notified that something changed
↓
software reads what changed and actsEvery arrow in that chain is a place the event can be lost, and the losses have different symptoms. This is the chain Chapter 2.1 §7 walked when a device was electrically healthy and never appeared to software — now with its middle links named.
Three properties of the third step are not obvious and are where designs go wrong.
The status must be sticky. Software does not read the instant a change occurs; it reads when it gets to it. If the hardware only exposed the current state, an attach followed quickly by a detach would leave the port reading “empty” with nothing to indicate anything had happened — and software would never know a device had come and gone. So the hardware records that a change occurred, separately from what the state currently is, and holds that record until software acknowledges it.
Acknowledgement must be explicit. If reading cleared the record automatically, two readers would race and one would lose the event. The usual arrangement is that software clears a change flag by writing to it, which makes acknowledgement deliberate.
Set must beat clear. If a new change arrives in the same cycle software clears the old one, the new one must survive. Getting this backwards produces the worst class of bug in this area: an event lost only when it coincides with a clear, which is rare, timing-dependent, and essentially impossible to reproduce on demand.
4. The Event Path as Hardware
Here is that third step as RTL. It is small, and every line of it corresponds to a paragraph above.
// ─────────────────────────────────────────────────────────────────────────
// port_change_status
//
// Classification: CONCEPTUAL ARCHITECTURE ABSTRACTION. Synthesizable, but
// it is NOT a USB root hub and implements no USB mechanism.
//
// WHAT IT IS. The generic shape of the path by which an asynchronous port
// event becomes something software can read: synchronisation into the
// register clock domain, edge detection, a STICKY change flag that survives
// until acknowledged, write-one-to-clear acknowledgement, and an interrupt
// summarising "something needs attention".
//
// WHAT IT IS NOT. No USB port state machine, no speed detection, no reset
// sequencing, no enumeration, no descriptors, no USB register layout, and
// no correspondence to any real controller's register map. Module 3 owns
// electrical detection, Module 6 enumeration, Module 18 hub behaviour and
// Module 22 real host-controller interfaces.
//
// The single idea worth taking away is the SET-BEATS-CLEAR ordering in the
// change-flag update, which is where this pattern is usually got wrong.
// ─────────────────────────────────────────────────────────────────────────
module port_change_status #(
parameter int N_PORTS = 4
) (
input logic clk, // register / controller clock
input logic rst_n,
// Raw per-port presence indication from the port logic. ASYNCHRONOUS to
// clk -- produced by analogue detection on its own timing, not by us.
input logic [N_PORTS-1:0] port_present_async,
// Software-visible interface.
output logic [N_PORTS-1:0] port_status, // current, synchronised state
output logic [N_PORTS-1:0] port_change, // sticky: "this changed"
input logic [N_PORTS-1:0] change_w1c, // write-1-to-clear from software
output logic irq // any change outstanding
);
// ── Cross into the register clock domain ───────────────────────────────
// Two flops against metastability, a third to give the edge detector a
// previous value. Per-port, because the ports are independent.
logic [N_PORTS-1:0] meta_q, sync_q, prev_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
meta_q <= '0;
sync_q <= '0;
prev_q <= '0;
end else begin
meta_q <= port_present_async;
sync_q <= meta_q;
prev_q <= sync_q;
end
end
// Either direction is an event: attach AND detach both need reporting.
logic [N_PORTS-1:0] changed;
assign changed = sync_q ^ prev_q;
// ── Sticky change flags ────────────────────────────────────────────────
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
port_change <= '0;
end else begin
// SET BEATS CLEAR. A change arriving in the same cycle software
// acknowledges the previous one must survive, or an event is lost
// exactly when the two coincide -- rare, timing-dependent, and
// effectively impossible to reproduce deliberately.
//
// Written as (old AND NOT clear) OR new, so the new set is applied
// after the clear rather than being masked by it.
port_change <= (port_change & ~change_w1c) | changed;
end
end
// Current state is reported separately from the fact that it changed.
// Software needs both: "what is there now" and "did something happen".
assign port_status = sync_q;
// Level interrupt: asserted while any change is outstanding. A pulse
// would be lost if software were not listening at that instant.
assign irq = |port_change;
endmoduleWhat it models. The generic asynchronous-event-to-software path: synchronise, detect either edge, latch stickily, acknowledge explicitly, summarise as an interrupt.
Hardware implied. Three flops per port of synchroniser and edge detect, one sticky flag per port, and an OR reduction. Tiny, and entirely about ordering.
Why the structure is as it is. The synchroniser exists because port_present_async is produced by analogue detection on its own timing — the same argument Chapter 1.5 made in full, and the reason this module cannot simply sample the input. Detection is on either edge because a removal is as much an event as an attachment. port_status and port_change are separate outputs because a device that attaches and detaches between two software reads leaves the status unchanged while the change flag is the only evidence anything happened. And the update expression puts the new set outside the clear mask, which is the set-beats-clear ordering.
Assumptions. That software acknowledges by writing one to a bit; that a level interrupt is appropriate, which it is here because the condition persists until acknowledged; and that two synchroniser stages give adequate margin at this clock — an assumption to check, not a guarantee.
What a waveform would show. sync_q lagging the asynchronous input by two cycles, changed pulsing for one cycle on either edge, port_change setting and then holding indefinitely, and irq following it until a change_w1c write drops both.
What it deliberately omits. Everything USB. There is no port state machine, no speed detection, no reset sequencing, and no resemblance to any real controller's register map.
5. The Assertions That Protect It
// ─────────────────────────────────────────────────────────────────────────
// Assertions for port_change_status.
//
// Classification: TEACHING ASSERTIONS for this abstraction's event-path
// semantics. They are not USB checks.
// ─────────────────────────────────────────────────────────────────────────
// R1 -- THE ONE THAT MATTERS. A change must never be lost because software
// happened to acknowledge in the same cycle. If a port changed this cycle,
// its flag is set next cycle regardless of any concurrent clear.
property p_set_beats_clear;
@(posedge clk) disable iff (!rst_n)
changed[0] |=> port_change[0];
endproperty
assert property (p_set_beats_clear);
// R2 -- stickiness. A flag that is set, and is not being acknowledged and
// not being re-set, must still be set next cycle. Without this the event
// path silently depends on software reading fast enough.
property p_change_is_sticky;
@(posedge clk) disable iff (!rst_n)
(port_change[0] && !change_w1c[0] && !changed[0]) |=> port_change[0];
endproperty
assert property (p_change_is_sticky);
// R3 -- acknowledgement works. Clearing with no coincident new event must
// actually clear, or software cannot make progress and the interrupt
// storms.
property p_w1c_clears;
@(posedge clk) disable iff (!rst_n)
(change_w1c[0] && !changed[0]) |=> !port_change[0];
endproperty
assert property (p_w1c_clears);
// R4 -- the interrupt summarises exactly the outstanding flags. An irq that
// can be asserted with nothing outstanding gives software no way to make it
// stop; one that can be low with a flag set loses the event's urgency.
property p_irq_matches_flags;
@(posedge clk) disable iff (!rst_n)
irq == (|port_change);
endproperty
assert property (p_irq_matches_flags);
// R5 -- nothing advances on the raw asynchronous input. A structural check
// that a later edit has not bypassed the synchroniser.
property p_change_only_from_synchronised;
@(posedge clk) disable iff (!rst_n)
changed[0] |-> (sync_q[0] != prev_q[0]);
endproperty
assert property (p_change_only_from_synchronised);R1 is the assertion this section exists for. The bug it catches — an event lost only when it coincides with an acknowledgement — is rare, timing-dependent, and produces a device that occasionally is not noticed when plugged in. No directed test will find it reliably; a property checked on every cycle of every random run will.
6. Verification and Debug at This Boundary
Stimulus that matters. Attach and detach at arbitrary cycle offsets relative to software's acknowledgement, specifically including the same cycle — that is R1's case and it will not occur by luck. Rapid attach-detach pairs between two software reads, which is the case port_status alone cannot represent. Several ports changing simultaneously. And reset asserted with changes outstanding.
Representative coverage dimensions — not a verification plan:
- change and acknowledge in the same cycle, and in adjacent cycles
- attach-then-detach entirely between two software reads
- multiple ports changing in one cycle
- interrupt asserted with one flag, with several, and cleared incrementally
- reset while flags are outstanding
For debug, this block is where a whole class of symptom is decided. Chapter 2.1 §7's ordered search asked whether the controller reported the attachment to software. This chapter shows that “reported” is itself several steps: was the event detected at all, was it latched, is the flag still set, is the interrupt asserted, is it masked, did the driver's handler run, did it read the right register. A device that is never noticed when plugged in has failed at exactly one of those, and they are distinguishable by inspection — the flag is either set or it is not.
That is the debugging framework of this module in its most concrete form: ask which layer should have produced the event, then ask whether its input condition was present and whether its output was observed by the next layer.
7. Common Misconceptions
8. Reason It Through
A board occasionally fails to notice a device on insertion. Re-plugging always works. It happens perhaps once in fifty insertions, never on the engineer's bench, and no test reproduces it.
What does the symptom's shape tell you? Rare, non-deterministic, load-correlated and not reproducible on demand — the signature of a timing coincidence rather than a logic error, because a logic error would fail the same way every time under the same inputs.
Which coincidences exist in this path? Two. An event arriving in the same cycle software acknowledges a previous one, which loses it if clear beats set. And an event arriving so close to the synchroniser's sampling that it is not cleanly captured — though a two-flop synchroniser makes that failure vanishingly rare, so the first is the stronger suspect.
Why does re-plugging always work? Because it creates a fresh event at a new random offset, and the coincidence is unlikely to recur. That the workaround succeeds is evidence for a timing coincidence, not against a real bug.
How would you confirm it? By inspection first — check whether the change-flag update applies the clear after the set, which is a code review rather than an experiment. Then by assertion R1 in simulation, with stimulus that deliberately aligns the event with the acknowledgement, since random stimulus will hit that alignment far too seldom.
The general lesson. In an event path, the interesting bugs live at the boundaries between who writes and who clears, and they are found by reasoning about ordering and by properties that check it every cycle — not by more directed tests, which are least likely to construct exactly the coincidence that matters.
9. Understanding Check
10. Summary
The root hub is the hub the host controller provides, occupying tier 1 and rooting the entire tree. Functionally it is Chapter 2.3's hub: per-port power, enable, reset, status and forwarding.
Structurally it is unlike every other hub, because it is inside the host controller and therefore cannot be managed by being addressed on the bus — the controller would have to send a request to itself over a bus that the root hub provides. It is managed through the controller's register interface instead, and operating systems commonly present it to their upper layers as an ordinary hub device for uniformity, an abstraction that is useful and routinely misleading.
The root hub is also where bus events first become software-visible, and that path has three properties that are not obvious. The change record must be sticky, because a device that attaches and detaches between two software reads leaves the current state unchanged. Acknowledgement must be explicit, or concurrent readers race and lose events. And set must beat clear, or an event is lost precisely when it coincides with an acknowledgement — rare, timing-dependent, cured by re-plugging, and effectively impossible to reproduce deliberately.
The teaching RTL makes those three properties inspectable and adds the detail that decides whether such a block works: synchronise the asynchronous port indication before using it, detect either edge because removal is also an event, report current state and change as separate outputs, and use a level interrupt because the condition persists until acknowledged.
And the transferable idea: architecture and implementation need not agree about what counts as a component. The architecture needs a hub at tier 1 for its model to be uniform; the implementation supplies the function without the componenthood.
11. What Comes Next
This chapter kept saying the controller's registers without examining what else is behind them. That is now the obvious gap: the root hub is one small part of a host controller, and everything else the host does on the bus — scheduling, moving data, reporting results — also happens there.
Chapter 2.5 takes the host controller as its subject: what it does for software, what it does for the bus, why its software-facing interface is standardised by documents separate from USB itself, and where the boundary falls between it and the PHY beneath it. It is the chapter where Module 2's hardware/software partition is stated in full — and where the question should this responsibility be in silicon or in a driver? gets a principled answer rather than a case-by-case one.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
PS/2 Keyboard / Mouse
The dedicated input port where the device supplies the clock and the host receives on someone else's timing. The two-wire open-drain bus, the framed byte, why sampling a foreign clock directly is a real hardware bug, and a synthesizable teaching receiver with its synchroniser, recovery timeout and assertions.
- Related topic
The Parallel Port
Why presenting eight data lines at once forces an explicit data/strobe/acknowledge handshake, what that costs in timing discipline, a synthesizable teaching FSM that implements it with the assertions that protect it, and why an interface shaped around one peripheral's operational model cannot generalise.
- Related topic
The USB Device
What is left when every decision belongs to the host: the responder discipline, why a device must answer even when it has nothing to say, the split between the USB-facing device controller and the function it exists to provide, and a teaching abstraction with the assertion that protects the ownership model.
- Related topic
The Host Controller
The hardware engine behind the host role: what it does for software and what it does for the bus, why its software interface is standardised by documents separate from USB itself, where the boundary with the PHY falls, and the principle deciding whether a responsibility belongs in silicon or in a driver.
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.
