Skip to content
VLSI Mentor

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.

A host controller shown between its two interfaces. On the software side, a host-controller driver accesses control and status registers and shares data structures in system memory, and receives interrupts. Inside, the controller contains scheduling logic that decides what to attempt and when, a protocol engine that performs the exchanges, data movement machinery between memory and the bus, and per-port root hub state. On the bus side it drives a PHY, which handles electrical signalling only. The register interface and the PHY interface are different abstractions with different clocks.Host-controller driversoftware — expresses intentShared memory structureswork lists and buffersRegisters + interruptsthe software-facing boundarySchedulingwhat to attempt, and whenProtocol engineperforms the exchangesRoot-hub port stateChapter 2.4PHYelectrical signalling only — Module3The busdevices and hubs12
Figure 1 — the controller faces software through registers and memory structures, and the bus through a PHY. Neither side sees the other's abstractions, which is exactly what makes the boundary useful for localising faults.

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.

ConcernControllerPHY
Protocol stateyes — this is its subjectessentially none
Electrical signallingnoyes — this is its subject
Knows device addressesyesno
Software-visibleyes, through registersnormally not directly
Clock domaincontroller/systemits own, driven by signalling needs
Changes with generationprotocol behavioursignalling 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.

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

endmodule

What 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

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

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.