Skip to content
VLSI Mentor

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.

A comparison of how the host manages a root hub versus an external hub. To manage an external hub the host software programs the host controller, which generates bus activity addressed to that hub as a device, and the hub answers as an ordinary responder. To manage the root hub the host software instead reads and writes the host controller's own registers directly, because the root hub is inside the controller and is not a device addressed on the bus. Both paths produce the same per-port operations of power, enable, reset and status.Host softwarewants to power or reset a portController registersdirect access — the root-hub pathHost controllergenerates bus activityRoot-hub portstier 1 — inside the controllerExternal huban addressed device on the busBus request to that hubthe external-hub pathan external hubplugs in here12
Figure 1 — an external hub is managed by addressing it over the bus; the root hub is managed through the controller's registers. Same function, different access path — and the difference decides where the software boundary falls.

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:

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

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

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

endmodule

What 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

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

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.