USB · Module 2
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.
Chapter 2.4 kept saying the controller's registers while examining only one small thing behind them. Everything else the host does on the bus happens there too, and this chapter takes the whole of it.
The host controller is where Module 2's abstractions become a chip. Chapter 2.1 established that the host owns every decision; the controller is what turns those decisions into activity on a wire, and turns what happened on the wire back into something software can read. It faces two very different worlds at once, and almost everything interesting about it follows from that.
1. Two Interfaces, Not One
A host controller sits between a processor running software and a bus carrying peripherals. Those two sides have nothing in common — different clocks, different abstractions, different failure modes, different rates of change — and the controller must present a coherent face to each.
2. What the Controller Does for the Bus
On the bus side, the controller is the only thing in the system that produces host-originated activity. Every request any device ever answers was emitted by a host controller.
It generates bus activity according to what software asked for and what the schedule permits.
It performs the exchanges, including the parts too fast for software to participate in. This is the first appearance of a principle §5 makes general: some responsibilities have timing requirements that rule software out entirely, and those necessarily live in hardware.
It moves data between the bus and system memory, because the payloads belong to software but the transfers happen at bus timing.
It observes results — what answered, what did not, what went wrong — and records them where software can find them.
It holds root-hub port state, as Chapter 2.4 established.
3. What the Controller Does for Software
The software-facing side is where the controller's design is most constrained, and for an interesting reason: it is standardised by a different document than USB is.
USB specifies what happens on the bus. It does not specify how software drives the hardware that speaks it. That second problem is solved separately, by host-controller interface specifications — and over USB's history there have been several, with xHCI the one that covers modern generations. Module 22 owns them in depth; what matters in Module 2 is why they exist as a separate layer at all.
The reason is reuse in a different direction from the one Module 1 discussed. USB standardisation means a device works with any host. Controller-interface standardisation means an operating system works with any controller. Without it every vendor's controller would need its own driver in every operating system — which is exactly the fragmentation of Chapter 1.1, reappearing one layer up.
The mechanism is generally a combination of two things, and the split is worth understanding.
Registers carry control and status that is small and immediate: operational state, port control, interrupt status and enables.
Memory-resident structures carry the work itself — descriptions of transfers to perform and places for results to go — because a register interface is a poor way to convey a queue of pending work, and because the controller can then fetch work autonomously rather than being spoon-fed by software for every transfer.
That second point is the important one architecturally. The controller is given work to do, not instructions to follow. Software builds a description of what it wants and tells the controller to go; the controller proceeds on its own. Any other arrangement would require software to participate at bus timing, which §5 shows is impossible.
4. The Controller Is Not the PHY
This distinction deserves its own section because confusing it produces bad RTL partitioning and worse debugging.
The PHY handles electrical signalling: driving and receiving the physical line, and the analogue behaviour that entails. It is a physical-layer component, and Module 3 owns it.
The controller handles protocol behaviour: what to send, when, to whom, what the answer means, and what to tell software. It holds state; the PHY, broadly, does not hold protocol state.
| Concern | Controller | PHY |
|---|---|---|
| Protocol state | yes — this is its subject | essentially none |
| Electrical signalling | no | yes — this is its subject |
| Knows device addresses | yes | no |
| Software-visible | yes, through registers | normally not directly |
| Clock domain | controller/system | its own, driven by signalling needs |
| Changes with generation | protocol behaviour | signalling entirely |
Three consequences follow.
The boundary is a clock-domain boundary. The PHY runs at whatever the signalling requires; the controller's protocol logic and register interface run on system clocks. Any real implementation therefore has synchronisation at this interface, with all the discipline Chapter 1.5 established — and the failure modes there are the ones described in that chapter, not USB-specific ones.
Confusing them produces misattributed bugs. “The PHY is broken” is a conclusion engineers reach when a device does not respond, and it is usually wrong: a PHY that is transmitting cleanly can sit beneath a controller that is asking the wrong thing, or not asking at all. Clean signals establish only that the lowest layer worked — Chapter 2.1 §7's point, now with the boundary named.
Confusing them produces bad partitioning. Protocol state pushed into a PHY makes an analogue-adjacent block carry logic that changes with protocol revisions. Signalling behaviour pulled into a controller makes a digital block carry constraints it cannot meet.
5. Hardware or Software? A Principle, Not a List
Now the question this chapter exists to answer properly, because engineers meet it on every project: which responsibilities belong in the controller and which in the driver?
The unhelpful answer is a list, which varies by product. The useful answer is a criterion with three tests.
Can software meet the timing? If a response is required faster than an interrupt can be taken and serviced, software is disqualified and the responsibility is hardware's. This is not a preference; it is arithmetic. Much of what a controller does exists for this reason alone.
Does it change more often than silicon can? Policy — which device to configure how, which transfer to submit next, what to do after an error — changes with operating systems, with products, and with bugs found after tape-out. Anything that changes at that rate must be software, because silicon that encodes a policy is silicon that outlives its correctness.
Does it require knowledge only one side has? Software knows which application asked, what the system's priorities are, and what the user has configured. Hardware knows what just happened on the wire, to the cycle. Responsibilities follow the knowledge.
Apply these and the partition mostly falls out. Performing an exchange at bus timing: hardware, by the first test. Deciding which device to configure and with what: software, by the second and third. Detecting and recording an error: hardware, by the first. Deciding what to do about the error: software, by the second.
The interesting cases are where the tests disagree, and those are where real controller architectures differ from one another. Retry after a transient failure, for instance, is timing-sensitive enough to want hardware and policy-laden enough to want software — which is why controllers differ in how much retry they perform autonomously, and why a design team argues about it. There is no universal answer and this module does not pretend otherwise; what it offers is the criterion those arguments should be conducted with.
6. The Software Boundary as Hardware
Here is the shape of that boundary as RTL — the second half of Chapter 2.4's event path, now in the direction software drives hardware.
// ─────────────────────────────────────────────────────────────────────────
// work_doorbell
//
// Classification: CONCEPTUAL ARCHITECTURE ABSTRACTION. Synthesizable, but
// it is NOT a USB host controller and implements no USB mechanism.
//
// WHAT IT IS. The generic shape of "software hands work to hardware and is
// told when it is done": a doorbell software rings to say work is ready, a
// busy indication, a completion event that is sticky until acknowledged,
// and an interrupt that software can mask. This is the register-level form
// of section 3's claim that the controller is GIVEN WORK, not instructed.
//
// WHAT IT IS NOT. No transfer descriptors, no scheduling, no USB protocol,
// no packets, no endpoints, no DMA engine, and no resemblance to xHCI or
// any other real controller interface. Module 22 owns real host-controller
// interfaces; Module 17 owns scheduling; Module 21 owns controller design.
//
// The transferable ideas are the DOORBELL-WHILE-BUSY case and the
// MASK-DOES-NOT-DISCARD rule, which are where this pattern is usually got
// wrong and which sections 7 and 8 examine.
// ─────────────────────────────────────────────────────────────────────────
module work_doorbell (
input logic clk,
input logic rst_n,
// ── Software-facing ────────────────────────────────────────────────────
input logic sw_doorbell, // write-1 pulse: "work is ready"
input logic sw_done_w1c, // write-1-to-clear the done flag
input logic sw_irq_enable, // interrupt mask (1 = allowed out)
output logic stat_busy, // hardware is working
output logic stat_done, // sticky: a unit of work completed
output logic stat_missed, // sticky: a doorbell arrived while busy
output logic irq,
// ── Engine-facing ──────────────────────────────────────────────────────
output logic eng_start, // 1-cycle pulse: begin a unit of work
input logic eng_complete // 1-cycle pulse: that unit finished
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
stat_busy <= 1'b0;
stat_done <= 1'b0;
stat_missed <= 1'b0;
eng_start <= 1'b0;
end else begin
eng_start <= 1'b0; // single-cycle pulse
// ── Accepting work ──────────────────────────────────────────────────
if (sw_doorbell) begin
if (!stat_busy) begin
stat_busy <= 1'b1;
eng_start <= 1'b1;
end else begin
// A doorbell while already busy. Silently dropping it is the bug
// in section 7: software believes work was accepted and waits
// forever for a completion that will never come. Recording it
// means the discrepancy is at least VISIBLE.
stat_missed <= 1'b1;
end
end
// ── Completion ──────────────────────────────────────────────────────
// Set beats clear, for Chapter 2.4's reason: a completion landing in
// the same cycle software acknowledges the previous one must survive.
if (eng_complete) begin
stat_busy <= 1'b0;
stat_done <= 1'b1;
end else if (sw_done_w1c) begin
stat_done <= 1'b0;
end
end
end
// MASKING HIDES, IT DOES NOT DISCARD. stat_done remains set with the
// interrupt masked, so software that polls still sees the completion and
// software that later unmasks is interrupted immediately. A mask that
// cleared the underlying condition would lose completions whenever an
// interrupt was disabled -- which is exactly when a driver is in a
// critical section and least able to tolerate it.
assign irq = stat_done && sw_irq_enable;
endmoduleWhat it models. The generic software-hands-work-to-hardware boundary: request, busy, completion, acknowledgement, masking.
Hardware implied. Three status flops, a pulse output and one gate. As with §4 of Chapter 2.4, the content is the ordering rather than the size.
Why the structure is as it is. eng_start is a pulse because starting is an event. stat_busy exists because software must be able to tell whether the engine is available without inferring it. stat_missed exists because the alternative — dropping a doorbell silently — produces §7's hang. Completion set beats clear for exactly Chapter 2.4's reason. And the mask gates only the interrupt output, leaving stat_done set, so masking hides a notification without destroying the fact.
Assumptions. That sw_doorbell and sw_done_w1c are single-cycle write strobes already decoded in this clock domain; that one unit of work is outstanding at a time, which real controllers do not assume; and that eng_complete is generated in this domain — in a real design it crosses from the protocol side and needs the treatment of §4.
What a waveform would show. A doorbell pulse, eng_start one cycle later, stat_busy high across the work, stat_done setting on completion and holding until acknowledged, and irq following stat_done gated by the enable.
What it deliberately omits. All of USB, and much of what a real controller interface needs: multiple outstanding work items, descriptors in memory, errors, aborts and DMA.
7. The Assertions, and the Bug They Protect
// ─────────────────────────────────────────────────────────────────────────
// Assertions for work_doorbell.
//
// Classification: TEACHING ASSERTIONS for this abstraction's hardware /
// software handshake. Not USB checks.
// ─────────────────────────────────────────────────────────────────────────
// H1 -- work starts only when the engine was free. Starting while busy
// would abandon the work in flight without telling anyone.
property p_start_only_when_free;
@(posedge clk) disable iff (!rst_n)
eng_start |-> $past(sw_doorbell && !stat_busy);
endproperty
assert property (p_start_only_when_free);
// H2 -- THE ONE THAT MATTERS. A doorbell is never silently ignored: it
// either starts work or is recorded as missed. Software that believes work
// was accepted and waits for a completion that will never arrive produces a
// hang with no error anywhere -- the worst diagnostic outcome available.
property p_doorbell_never_silently_dropped;
@(posedge clk) disable iff (!rst_n)
sw_doorbell |=> (eng_start || stat_missed);
endproperty
assert property (p_doorbell_never_silently_dropped);
// H3 -- completion survives a coincident acknowledgement (Chapter 2.4's
// set-beats-clear, in the completion path).
property p_completion_survives_clear;
@(posedge clk) disable iff (!rst_n)
eng_complete |=> stat_done;
endproperty
assert property (p_completion_survives_clear);
// H4 -- MASKING HIDES, IT DOES NOT DISCARD. Disabling the interrupt must
// not clear the underlying completion, or a driver loses completions
// precisely while it has interrupts off.
property p_mask_does_not_discard;
@(posedge clk) disable iff (!rst_n)
(stat_done && !sw_irq_enable && !sw_done_w1c && !eng_complete) |=> stat_done;
endproperty
assert property (p_mask_does_not_discard);
// H5 -- busy is released by completion, so the engine cannot be left
// permanently unavailable after finishing.
property p_busy_released_on_completion;
@(posedge clk) disable iff (!rst_n)
eng_complete |=> !stat_busy;
endproperty
assert property (p_busy_released_on_completion);H2 is the assertion this section exists for. A dropped doorbell produces the worst symptom class in hardware/software interfaces: a hang with no error. Software waits for a completion; hardware never started; nothing anywhere reports a fault. Every layer is individually healthy and the system does nothing. Recording the miss does not fix the race, but it converts an invisible hang into an inspectable status bit, which is the difference between a bug you can find and one you cannot.
8. A Failure Traced Across the Boundary
A driver submits work. Occasionally — perhaps once in thousands of submissions, under load — the transfer never completes. No error is reported by hardware or software. The device is fine. Resetting the controller clears it.
The wrong mental model. “I wrote the doorbell register, so the hardware has the work.” A register write is not an acceptance; it is a request to accept.
The implementation mistake. A doorbell arriving while the engine is busy, dropped without record — either in hardware that ignores it, or in a driver that rings without checking stat_busy.
The observable failure. Software waits for a completion that cannot arrive. Because nothing failed, nothing is reported: no error bit, no timeout in hardware, no bus activity to see on an analyser. The absence of evidence is the evidence — the distinguishing feature is that there is no failure anywhere, and an engineer looking for a fault will find none.
Why load-dependent? Because it needs a doorbell to coincide with a busy engine, which becomes likely only when submissions are frequent enough to overlap.
The debug evidence that resolves it. stat_missed, if the design records it. Without that bit the investigation is a search across two disciplines for something that left no trace — which is why §6 includes it and H2 protects it.
The correct model. A hardware/software handshake needs an acceptance, not just a request. Either hardware records what it could not take, or software checks before ringing, and a robust design does both.
9. Verification at This Boundary
The controller's two faces make it the richest verification surface in Module 2, and they need different treatment.
On the software side, stimulus is register and memory activity, and the corners are the concurrency cases: a doorbell while busy; an acknowledgement in the same cycle as a completion; masking and unmasking around a completion; and reset with work outstanding. These are cheap to provoke deliberately and nearly impossible to hit reliably by chance — the reason they are written as properties rather than trusted to random tests.
On the bus side, stimulus is device behaviour, and the corners are the responses Chapter 2.2 enumerated: answers with data, answers with nothing, and no answer at all. A device model that always responds promptly exercises none of the controller's error handling.
Representative coverage dimensions — not a verification plan:
- doorbell when free, and doorbell when busy
- completion coincident with acknowledge, and separated from it
- interrupt masked across a completion, then unmasked
- reset asserted while busy, and while a completion is outstanding
- bus-side: data answer, empty answer, no answer
And the observability point, which is this module's contribution to how a DV engineer thinks: a monitor on the register interface sees intent, a monitor on the PHY interface sees signalling, and neither sees the other. A failure is localised by asking whether each boundary carried what the next expected — which is why an environment that observes only one of them can tell you a transfer failed but not where.
10. Common Misconceptions
11. Reason It Through
A team is deciding where to implement retry after a failed exchange: in the controller, autonomously, or in the driver.
What does the timing test say? It favours hardware. A retry that must occur promptly cannot wait for an interrupt to be taken, a handler to run, and a register to be written — by then the opportunity has passed, and on a bus serving many devices the delay is visible to everything else.
What does the change-rate test say? It favours software. How many times to retry, whether to retry at all for a given transfer, and what to do when retries are exhausted are policy, and policy changes with operating systems, products and field experience in a way silicon cannot.
What does the knowledge test say? It splits. Hardware alone knows exactly what happened on the wire; software alone knows which application is waiting and how much this transfer matters.
So what is the answer? That the tests disagree is the answer, and it is why real controllers differ here. A common resolution is to give hardware a bounded, configurable amount of autonomous retry — fast enough to be useful, with the limit set by software so the policy stays where policy belongs — and to escalate to software when that is exhausted.
What is the general lesson? The partition is not a fact to memorise but a judgement to make, and the three tests are how to make it defensibly. When they agree, the answer is obvious and every implementation does the same thing. When they disagree, you are looking at a genuine design decision — and at the place where a specification is likely to leave latitude, which means at the place where two implementations may behave differently and interoperability testing earns its cost.
12. Understanding Check
13. Summary
The host controller is a translator between two incompatible worlds. Toward the bus it is the only source of host-originated activity: it generates exchanges, performs the parts too fast for software, moves data to and from memory, observes results, and holds root-hub port state. Toward software it presents registers for small immediate control and memory-resident structures for the work itself — because the controller is given work, not instructions, which is an architectural necessity rather than an optimisation, since the alternative would require software to participate at bus timing.
Its software-facing interface is standardised by a different document than USB, solving a different problem: USB lets any device work with any host, while a controller-interface specification lets any operating system work with any vendor's controller.
The controller is not the PHY. The PHY does electrical signalling and holds essentially no protocol state; the controller holds protocol state and no analogue behaviour; and the boundary between them is a clock-domain boundary requiring real synchronisation. Confusing them misattributes bugs and produces partitioning neither block can meet.
The hardware/software partition follows a criterion, not a list: can software meet the timing, does the responsibility change faster than silicon can, and does it need knowledge only one side has. Where the tests agree the answer is obvious; where they disagree — retry being the classic case — you are looking at a genuine design decision, which is exactly where implementations differ and interoperability testing earns its cost.
Two rules from the teaching RTL generalise to every hardware/software interface you will build. A doorbell must never be silently dropped, because the result is a hang with no error anywhere — the hardest failure class to diagnose. And masking hides a notification without discarding the event, or a driver loses completions precisely while its interrupts are off.
14. What Comes Next
Five chapters have now described the architecture in detail: a host that owns every decision, devices that answer, hubs that extend reach without gaining authority, a root hub where the tree begins, and a controller that turns decisions into bus activity.
What none of them has done is justify the arrangement. Concentrating all authority in one participant is a choice with alternatives, and every mechanism in Module 2 is more expensive because of it — the host is the most complex participant by a wide margin, devices cannot talk to one another, and a device with urgent data waits to be asked.
Chapter 2.6 treats that choice as what it is: a trade with a bill. It asks what single-master buys, what it costs, what the alternatives would have cost instead, and why an architecture aimed at cheap, numerous, unknown-in-advance peripherals reasonably concluded that the complexity belonged at the end of the wire where there is only ever one of them.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- 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
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 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 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.
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.
