Skip to content
VLSI Mentor

USB · Module 9

Endpoint Numbers

Why sixteen, why endpoint zero is not like the other fifteen, and what a slot costs in silicon — with the build-time check that turns a configuration referencing absent hardware into a compile error.

Chapter 9.2 established that an endpoint address is four bits of number and one bit of direction. It did not justify the four.

This chapter does, and the justification turns out to be about silicon rather than protocol:

Endpoint numbers are not free names. Each one a device implements is a buffer, a configuration and a state machine that exist whether or not anything uses them.

It is also where one number turns out to be unlike the other fifteen — and where the containment invariant of Chapter 9.1 §6 stops being a diagram and becomes a compile error.

1. Why Four Bits

Work out the constraints and the number falls out.

The field rides in every transaction. An endpoint address is part of the traffic that selects a destination, and every bit of it is bus time spent on addressing rather than on data. A wide field costs throughput on every transfer of every device on the bus, forever.

So the pressure is downward, and the question becomes: what is the smallest field that does not constrain real devices?

How many endpoints does a device actually need? A simple device needs one flow in each direction — two endpoints — plus control. A complex composite device has several functions, each with a few endpoints. Chapter 7.3's headset has audio out, audio in and buttons; a webcam has video and audio; a composite storage-and-serial device has four or five.

Very few real devices exceed a handful per direction. Four bits gives sixteen numbers per direction, which is comfortably above what devices need and cheap enough to carry in every transaction.

And the cost of being wrong in each direction is asymmetric. Too few numbers would have made classes of device impossible to build. Too many would have cost every transfer ever made a few bits. Sixteen is far enough above the requirement that the first risk is gone, and small enough that the second is negligible.

2. Endpoint Zero Is Different

One number is not like the others, and the differences are structural rather than conventional.

It always exists. Every USB device implements endpoint zero in both directions. A device without it cannot be enumerated at all, because every step of Module 6 — reset, addressing, descriptor reads, configuration — happens through it.

It exists before any configuration. Chapter 8.3 established that a device in Default has no configuration and no assigned address, and can still communicate. What it communicates through is endpoint zero. So endpoint zero is not enabled by a configuration; it is available whenever the device is powered and reset.

It is not described by an endpoint descriptor. Chapter 7.4 §6 established this and it follows from the previous point: an endpoint descriptor lives inside a configuration, and endpoint zero exists before any configuration does. Its packet size is declared instead by the device descriptor's own field — the one Chapter 6.4's short first read exists to obtain.

It is bidirectional in a way no other endpoint is. Endpoint 1 IN and endpoint 1 OUT are two endpoints that happen to share a number (Chapter 9.2). Endpoint zero's two halves are two directions of one conversation — a request goes out, a response comes back, and they are parts of the same operation. Module 13 owns the structure of that operation; what matters here is that the two halves are coupled in a way other numbers' halves are not.

3. Directions Are Not Symmetric

A consequence of Chapter 9.2's two-array structure that affects how devices are built.

A device may implement a number in one direction only. Module 7's example device does exactly that: endpoints 0x81 and 0x01 — number 1 in both directions — and 0x82, number 2 IN only. There is no endpoint 2 OUT, and nothing is wrong with that.

Devices are usually asymmetric, because data usually is. A sensor produces far more than it consumes; a printer consumes far more than it produces. Building both directions of every number would double the buffer cost to serve flows that do not exist.

So an endpoint number is not a channel pair. It is a name, and a device implements whichever (number, direction) combinations it needs.

A diagram of the example device's endpoint inventory. Endpoint zero exists in both directions and is always present, used for control. Endpoint one exists in both the IN and OUT directions, carrying bulk data in each. Endpoint two exists only in the IN direction, carrying interrupt data, and there is no endpoint two OUT. Numbers three through fifteen are unimplemented in both directions. An annotation notes that each number and direction cell is independently implemented in silicon.EP0 INcontrol — always presentEP0 OUTcontrol — always presentEP1 INbulk, 64 bytesEP1 OUTbulk, 64 bytesEP2 INinterrupt, 8 bytesEP2 OUTNOT implementedEP3 … EP15neither directionimplementedone conversationtwo ENDPOINTSno OUT halfnumber space12
Figure 1 — the example device's endpoint inventory. Number 2 exists only in the IN direction, which is normal rather than an omission: each (number, direction) cell is independently paid for in silicon, so a device buys only the cells its data flows require.

4. What a Slot Costs

The argument that makes endpoint numbers an engineering decision rather than a naming one.

Chapter 9.1 §4 measured an endpoint's record at 18 bits — 15 of configuration, 3 of status. That is the cheap part, and quoting it alone would be misleading.

The buffer dominates, by orders of magnitude. An endpoint declaring 64-byte packets needs at least 64 bytes of storage — 512 bits — and Chapter 9.5 shows why a serious design wants two of them. So one bulk endpoint is roughly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  record            18 bits
  single buffer    512 bits      (64 bytes)
  double buffer  1 024 bits      (Chapter 9.5's argument)
                ─────────────
  ≈ 1 042 bits per double-buffered 64-byte endpoint

Multiply by a fully-populated device. Thirty-two slots at that size is roughly 33 000 bits of storage — and that is before any of the control logic, and for a device that almost certainly uses six of them.

Which is why devices do not implement all 32. They implement what their configurations declare, and the number is usually between two and eight. Endpoint slots are a budgeted resource, and the budget is dominated by buffers rather than by the state that makes them addressable.

5. The Endpoint Inventory, as RTL

A parameterised array of slots, with the generate structure that makes the cost visible.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_ep_inventory
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL. It models the set
// of endpoint slots a device implements, and nothing about what they do.
//
// WHAT IT MODELS. Section 3's independence -- each (number, direction) cell
// is separately implemented -- and section 4's cost, by instantiating one
// record per implemented cell and none for the rest. The generate is the
// point: unimplemented cells produce no hardware at all.
//
// WHAT IT DOES NOT MODEL. Buffers (Chapter 9.5 owns their structure and is
// where the real area lives), the decode that selects a slot (Chapter 9.2),
// transaction behaviour (Modules 10-13), or endpoint 0's special treatment
// beyond reserving it -- Module 13 owns the control endpoint's behaviour.
//
// ON THE PARAMETERS. IMPL_IN and IMPL_OUT are the SAME masks Chapter 9.2's
// decoder uses, and they must come from one place. Two copies of "which
// endpoints does this device have" is Chapter 7.2 section 9's descriptor
// drift, relocated into RTL.
// ─────────────────────────────────────────────────────────────────────────
module usb_ep_inventory
  import usb_ep_pkg::*, usb_ep_addr_pkg::*;
#(
  parameter logic [N_EP_NUM-1:0] IMPL_IN  = 16'b0000_0000_0000_0111,
  parameter logic [N_EP_NUM-1:0] IMPL_OUT = 16'b0000_0000_0000_0011
)(
  input  logic clk,
  input  logic rst_n,

  // A configuration has been selected (Chapter 8.5). These masks say which
  // endpoints it declares; the block enforces the containment rather than
  // trusting them (Chapter 9.1 section 6).
  input  logic                   config_commit,
  input  logic [N_EP_NUM-1:0]    declared_in,
  input  logic [N_EP_NUM-1:0]    declared_out,

  input  logic                   bus_reset,

  output logic [N_EP_NUM-1:0]    enabled_in,
  output logic [N_EP_NUM-1:0]    enabled_out,

  // Asserted if a configuration declared an endpoint the silicon lacks.
  // Section 6 argues this should be impossible by the time it reaches
  // hardware -- and that hardware should still report it.
  output logic                   config_exceeds_silicon
);

  // ── ELABORATION-TIME FACTS ──────────────────────────────────────────────
  // Endpoint 0 must exist in both directions, or the device cannot be
  // enumerated at all (section 2). A device is not permitted to economise
  // here, and catching it at elaboration is better than discovering it when
  // nothing answers at the default address.
  initial begin : ep0_required
    assert (IMPL_IN[0] && IMPL_OUT[0])
      else $fatal(1, {"endpoint 0 must be implemented in both directions -- ",
                      "a device without it cannot be enumerated"});
  end

  // ── The slots themselves ────────────────────────────────────────────────
  // One record per implemented cell. The generate condition is what makes
  // section 4's cost real: an unimplemented number costs nothing, not even
  // a tied-off register.
  genvar gi;
  generate
    for (gi = 0; gi < N_EP_NUM; gi++) begin : g_in
      if (gi == 0) begin : g_ep0
        // ENDPOINT ZERO IS DERIVED, NOT STORED. Section 2: it exists
        // whenever the device is powered, before any configuration, and it
        // must survive a bus reset -- a device that lost it on reset could
        // never be re-addressed, because addressing happens through it.
        //
        // Deriving it rather than registering it is Chapter 8.5 section 3's
        // argument: a stored enable needs a clearing path per exit and a
        // NON-clearing path per reset, and the one that gets forgotten is
        // whichever the author was not thinking about. Endpoint zero's
        // enable has no correct reason to ever be 0, so it is a constant.
        assign enabled_in[gi] = IMPL_IN[gi];
      end else if (IMPL_IN[gi]) begin : g_impl
        ep_record_t rec;
        always_ff @(posedge clk or negedge rst_n) begin
          if (!rst_n)          rec <= ep_record_reset(1'b1);
          else if (bus_reset)  rec <= ep_record_reset(1'b1);
          else if (config_commit)
            rec.cfg.enabled <= declared_in[gi];
        end
        assign enabled_in[gi] = rec.cfg.enabled;
      end else begin : g_absent
        assign enabled_in[gi] = 1'b0;   // no hardware, never enabled
      end
    end

    for (gi = 0; gi < N_EP_NUM; gi++) begin : g_out
      if (gi == 0) begin : g_ep0
        assign enabled_out[gi] = IMPL_OUT[gi];
      end else if (IMPL_OUT[gi]) begin : g_impl
        ep_record_t rec;
        always_ff @(posedge clk or negedge rst_n) begin
          if (!rst_n)          rec <= ep_record_reset(1'b1);
          else if (bus_reset)  rec <= ep_record_reset(1'b1);
          else if (config_commit)
            rec.cfg.enabled <= declared_out[gi];
        end
        assign enabled_out[gi] = rec.cfg.enabled;
      end else begin : g_absent
        assign enabled_out[gi] = 1'b0;
      end
    end
  endgenerate

  // ── The containment invariant, in hardware ──────────────────────────────
  // Chapter 9.1 section 6's enabled ⊆ declared ⊆ implemented. The second
  // containment is checked at build time (section 6); this reports a
  // violation of the first that reached hardware anyway.
  assign config_exceeds_silicon =
        ((declared_in  & ~IMPL_IN)  != '0)
     || ((declared_out & ~IMPL_OUT) != '0);

endmodule

Purpose. To hold the per-endpoint records a device implements, and to make the cost of implementing one visible in the structure.

Inputs. Clock and reset; a configuration commit with the declared masks; a bus reset.

State. One record per implemented cell. Unimplemented cells hold nothing — the generate produces no register at all, which is the point.

Outputs. The enabled masks (consumed by Chapter 9.2's decoder), and a report that a configuration exceeded the silicon.

Hardware implied. One 18-bit record per implemented data endpoint, plus the enable fan-out; endpoint zero costs no register at all, because its enable is a constant. For the example device: three data cells, so fifty-four flip-flops — and no buffers, which §4 shows are the real cost.

Reset. Both resets return every data endpoint's record to its reset value with implemented preserved, which is Chapter 9.1 §4's distinction, and clear their enables because Chapter 8.5 requires it.

Endpoint zero is exempt, and deliberately not by a special case inside the reset logic. Its enable is derived from implemented rather than registered, so there is no reset path for it to be omitted from. That is Chapter 8.5 §3's argument in its strongest form: a stored enable would need a clearing path per exit and a deliberate non-clearing path on reset, and §7 measures what a controller that gets the second one wrong does.

Assumptions. That the masks come from one source shared with the decoder; that config_commit is qualified (Chapter 6.5); and that the declared masks reflect the configuration actually selected.

Omissions. Buffers, the decode, all transaction behaviour, and endpoint zero's operational behaviour.

What DV should verify. That an unimplemented cell never reports enabled; that endpoint zero stays enabled across a configuration change and an un-select; that a bus reset disables every non-zero endpoint; that a declaration exceeding the silicon is reported; and that the generate really does omit hardware for unimplemented cells.

6. Fit Is a Build-Time Question

Chapter 9.1 §6 stated the containment and deferred the check. Here it is, and the point is when it runs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Classification: STATIC DATA-INTEGRITY CHECKS (elaboration-time).
//
// Whether a configuration's endpoints fit inside the silicon's is a fact
// about two sets of constants. Deciding it at elaboration means it fails in
// EVERY build, unconditionally, rather than in the simulations that happen
// to select that configuration -- Chapter 7.2 section 8's argument, applied
// to hardware inventory rather than to descriptor bytes.
//
// SCOPE. This needs the descriptor's declared endpoints as constants, which
// means it belongs wherever the descriptor image and the design parameters
// are both visible. Chapter 7.4 section 6 made the same point about its
// consistency model: the two sides must be INDEPENDENT, so the declared set
// must come from the descriptor image and the implemented set from the
// design -- not both from one hand-written table.
// ─────────────────────────────────────────────────────────────────────────
initial begin : endpoint_fit_check

  logic [N_EP_NUM-1:0] excess_in, excess_out;
  int unsigned n_impl, n_decl;

  excess_in  = CFG_DECLARED_IN  & ~IMPL_IN;
  excess_out = CFG_DECLARED_OUT & ~IMPL_OUT;

  // F1 -- every declared endpoint must be implemented. This is the check
  // that turns Chapter 7.4's "descriptor advertises an endpoint the design
  // does not have" from a silent field failure into a build failure.
  assert (excess_in == '0)
    else $fatal(1, {"configuration declares IN endpoint(s) 0x%04h that the ",
                    "silicon does not implement"}, excess_in);

  assert (excess_out == '0)
    else $fatal(1, {"configuration declares OUT endpoint(s) 0x%04h that the ",
                    "silicon does not implement"}, excess_out);

  // F2 -- endpoint 0 must be implemented in both directions (section 2).
  assert (IMPL_IN[0] && IMPL_OUT[0])
    else $fatal(1, "endpoint 0 is not implemented in both directions");

  // F3 -- endpoint 0 must NOT be declared by a configuration. It exists
  // before any configuration, is not described by an endpoint descriptor
  // (Chapter 7.4 section 6), and a configuration claiming it has
  // misunderstood what it is.
  assert (!CFG_DECLARED_IN[0] && !CFG_DECLARED_OUT[0])
    else $fatal(1, {"a configuration declares endpoint 0 -- the control ",
                    "endpoint is implicit and must not be declared"});

  // F4 -- a device that implements nothing but endpoint 0 can enumerate and
  // can do nothing else. Legal, and almost always a mistake, so it warns
  // rather than fails: Chapter 7.5 section 6's judgment about matching a
  // check's severity to its confidence.
  n_impl = $countones(IMPL_IN) + $countones(IMPL_OUT);
  if (n_impl <= 2)
    $warning({"this device implements only endpoint 0: it can enumerate ",
              "but has no data endpoints"});

  n_decl = $countones(CFG_DECLARED_IN) + $countones(CFG_DECLARED_OUT);
  $display("endpoint fit OK: %0d cells implemented, %0d declared by this configuration",
           n_impl, n_decl);
end

F1 is the check this chapter exists to produce. Chapter 7.4 §7 measured the defect it prevents: a descriptor advertising an endpoint the design does not implement, which is byte-perfect, structurally flawless, and produces a device that enumerates and then never responds. That defect is a fact about two constants, and a fact about two constants should fail a build rather than a lab.

F3 catches the inverse misunderstanding — a configuration that declares endpoint zero, which Chapter 7.4's consistency model also checks from the descriptor side. Two checks on one rule, from two directions, is appropriate here because the rule is violated by two different mistakes.

7. Mutation Test

Four mutations. Two of them corrected this chapter while it was being written, and both corrections are in the published design above.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mutation                                    result
  ──────────────────────────────────────────────────────────────────────
  correct inventory                           OK
  N1  declare an unimplemented endpoint       reported, and cannot happen
  N2  store endpoint 0's enable               4 failures — EP0 never on
  N3  omit the generate condition             an unimplemented endpoint
                                                becomes ENABLED
  N4  drop the build-time fit check           passes the build; caught
                                                much later, in the wrong terms

N1 — declare an endpoint the silicon lacks

Result. config_exceeds_silicon asserts, and the enabled mask does not change — the generate produced no register for that cell, so there is nothing to set.

Which is the ideal shape for a check: the hardware cannot do the wrong thing, and it says so when asked to.

N2 — store endpoint zero's enable instead of deriving it

This mutation was not invented. It was the chapter's first draft, and simulation found it.

The original inventory registered every endpoint's enable uniformly, clearing them all on a bus reset and setting them from the declared mask on a configuration commit — with a special case writing 1 for endpoint zero.

Result, measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  after reset             EP0 IN = 0      ← the defect
  after configuration     EP0 IN = 1
  after bus reset         EP0 IN = 0      ← the defect again

Endpoint zero is disabled after every bus reset, and the device therefore cannot be re-addressed — because addressing happens through endpoint zero. A device that lost it on reset could enumerate exactly once and never recover.

And the special case was not enough, which is the instructive part. Writing 1 for endpoint zero on a configuration commit is correct and irrelevant: the failure is on the reset path, and a stored enable needs a deliberate non-clearing path there. Two paths to get right, and the second is the one nobody writes.

The fix in §5 is not a better special case — it is removing the register. Endpoint zero's enable has no correct reason ever to be zero, so it is derived from implemented and there is no reset path to omit it from. Chapter 8.5 §3 argued exactly this and §5 is the argument applied where it bites hardest.

N3 — omit the generate condition

Instantiate a record for every number regardless of the implemented mask, relying on the mask to keep unimplemented cells disabled.

Result — and this contradicts the intuition that it is a pure area trade:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  configuration declares EP9 IN, which the silicon does not implement

  correct inventory   enabled_in = 0000000000000111   EP9 not enabled
  N3                  enabled_in = 0000001000000111   EP9 ENABLED

An unimplemented endpoint becomes enabled. Without the generate condition there is a register for endpoint 9, and the configuration commit writes the declared mask into it unconditionally — nothing gates the write against the implemented mask.

So the generate is not an area optimisation. It is the structural guarantee, and removing it removes the only thing preventing a configuration from enabling hardware that does not exist. §9 works through the choice with this measurement in hand.

N4 — drop the build-time fit check

Result. The build passes on a configuration declaring an endpoint the silicon lacks. Nothing fails until simulation reaches a transaction to that endpoint — and Chapter 9.2's decoder correctly refuses it, so the simulation reports a refused transaction rather than a malformed device.

That difference is the whole argument for build-time checking. The runtime symptom is real and reported in the wrong vocabulary: an engineer sees an endpoint refusing traffic and investigates the endpoint, when the fault is that a descriptor and a design disagree about what hardware exists.

For comparison, the build-time check reports it directly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FATAL: configuration declares IN endpoint(s) 0x0200 that the silicon
       does not implement

8. Verification

This chapter's commit point is the device's endpoint inventory agrees with its descriptors.

Stimulus. Configuration selection with each configuration the device offers; an un-select; a bus reset while endpoints are enabled; a declaration naming an unimplemented endpoint; a declaration naming endpoint zero; and — the case §7's N2 needs — traffic to endpoint zero after a configuration commit.

The stimulus requirement §7 makes non-negotiable: exercise endpoint zero after configuring. N2 breaks only there, and a plan that tests enumeration and then tests data endpoints never sends a control transaction to a configured device.

Observation. The enabled masks for both directions, plus the containment report. Checking that the declared endpoints became enabled is not enough — N2's defect is an endpoint that should have stayed enabled and did not.

Reference model. Two sets: expected enabled IN and OUT. The transition rule is enabled = declared ∪ {0} on a configuration commit, {0} on a bus reset. Small, and the union with endpoint zero is exactly the behaviour N2 removes.

Representative coverage — crosses:

  • endpoint number 0 through 15 × direction, for both implemented and declared
  • configuration commit × un-select × bus reset, observing endpoint zero through each
  • declared ⊆ implemented × declared ⊄ implemented
  • a number implemented in one direction only, declared in each direction
  • the minimum device — endpoint zero alone — and a fully populated one

Negative cases with defined outcomes: an unimplemented endpoint must never report enabled; a configuration declaring endpoint zero must fail the build; a configuration exceeding the silicon must fail the build and, if it somehow reaches hardware, be reported; and endpoint zero must remain enabled through every configuration change.

9. Reason It Through

A reviewer proposes removing §5's generate condition and instantiating all 32 records unconditionally, on the grounds that the implemented mask already prevents an unimplemented endpoint from being used and the code is simpler.

Is the premise true? It is the premise this chapter started with, and it is false — §7's N3 measured it.

Work out why. Without the condition, endpoint 9 has a record. A configuration commit writes declared_in[9] into rec.cfg.enabled, and nothing in that write consults the implemented mask. The mask's only appearance was in the g_absent branch, which produced the tie-off — and that branch disappears along with the condition.

So what actually prevented the defect? Not the mask, and not a check. The absence of a register. The generate did not optimise away something harmless; it removed the storage that a wrong value could occupy.

Could the flat version be made correct? Easily — gate the write with IMPL_IN[gi]. And that is the real comparison: the generate version is correct because of its structure, and the flat version is correct because of a condition someone remembered to write.

Which is better, given both can be right? The structural one, for the reason §7's ladder gives: an error that cannot be expressed needs no check, no test and no review. The conditional one is correct today and depends on that condition surviving every future edit — including the one that adds a debug path writing the record directly, which the generate version simply refuses to compile.

And the area? 90 flip-flops against 576 for the example device, which matters on a small device and not on a large one. It was never the main argument, and believing it was is what made the flat version look like a free simplification.

The transferable lesson: when a structure appears to be a pure optimisation, check whether it is also carrying a guarantee. §7's measurement is the only reason this one was noticed — the intuition, written down confidently in this chapter's first draft, was wrong.

10. Common Misconceptions

11. Understanding Check

12. Summary

Four bits because the field rides in every transaction. Every bit costs bus time on every transfer forever, and real devices need only a handful of endpoints per direction — so sixteen numbers is comfortably above the requirement and cheap enough to carry everywhere.

Sixteen numbers, two directions, up to thirty-two endpoints, and a device implements whichever subset it paid for. The three counts are routinely confused and are all different.

Endpoint zero is structurally unlike the others. It always exists, exists before any configuration, is not described by an endpoint descriptor, and its two halves are two directions of one conversation. It resolves the bootstrap of needing an endpoint in order to configure endpoints — the same reserve-one-value pattern as the default address and the string language table — at the cost of one number.

Directions are not symmetric. Each cell is independently implemented, and most devices are asymmetric because most data flows are.

And a slot is not a free name. The record is 18 bits; the buffer is 512 bits for a 64-byte packet and roughly 1,000 double-buffered. Thirty-two slots would be about 33,000 bits for a device that uses six.

§7 measured four mutations, and two of them changed the published design.

Endpoint zero's enable was originally stored, cleared on bus reset like every other endpoint and set by a special case on configuration. Simulation showed it disabled after every bus reset — so the device could enumerate once and never be re-addressed, because addressing happens through endpoint zero. The special case was correct and irrelevant: the failure was on the reset path, which needs a deliberate non-clearing branch. The fix was to remove the register, not to improve the special case.

Removing the generate condition was expected to be a pure area trade and is not: without it, a configuration declaring endpoint 9 enables endpoint 9, because the write consults the declared mask and nothing consults the implemented one. The generate was carrying a guarantee, not an optimisation.

Which is the chapter's transferable judgment: make it impossible, else fail at build, else fail in simulation, else fail in the lab — and the question is not which check to write but how far up that ladder the failure can be moved. Both of this chapter's corrections moved one to the top rung, where no check is needed at all.

Which is the chapter's transferable judgment: make it impossible, else fail at build, else fail in simulation, else fail in the lab — and the question is not which check to write but how far up that list the failure can be moved.

13. What Comes Next

An address is a number and a direction, the numbers are budgeted, and their asymmetry is normal. One word in all of this has been used without being examined.

Chapter 9.4 examines direction, and it is the chapter where a name that looks obvious turns out to be the single most reliably misunderstood convention in USB. IN and OUT are named from the host's point of view — an IN endpoint sends data from the device — and the kernel's own header says so in two words: USB_DIR_OUT is to device, USB_DIR_IN is to host.

Every engineer meets that convention backwards once. The chapter is about why the naming is what it is, and about what a device controller does differently for the two directions — because they are not mirror images.

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.