Skip to content
VLSI Mentor

USB · Module 9

The Endpoint Concept

Why a device needs more than one destination: endpoints as independently-stateful buffers rather than wires, what a controller instantiates per endpoint, and why an endpoint in a descriptor but not in silicon is the module's defining failure.

Module 8 established when a device may communicate. A Configured device is permitted to move data; an Addressed one is not. Six chapters referred to the thing that becomes permitted as an abstract enable and never said what it was.

This module says what it is, and the question it answers is one the previous modules could not:

A device is configured and the host wants to send it something. Where does that something go?

A device is not a single destination. A headset receives audio, sends microphone audio, and reports button presses — three flows with different requirements, arriving on one cable. Something has to say which is which.

That something is the endpoint, and this chapter builds the abstraction from the problem rather than from a definition.

1. Why One Destination Is Not Enough

Start with the simplest possible device model and break it.

Suppose a device has exactly one destination. Everything the host sends arrives in one place; everything the device returns comes from one place. This is essentially Module 1's serial port, and it works for a device that does one thing.

Now give the device two functions. A keyboard that also has a volume dial. The host sends a command to set an LED and the device sends key events and dial movements. With one destination:

  • The device must demultiplex. Every arriving byte must carry, somewhere in its own content, an indication of what it is for. That is a protocol on top of the protocol, invented per device, understood by nothing generic.
  • The device must multiplex. Key events and dial movements share one return path, so one can delay the other, and a burst of one starves the other.
  • Software has no separation. Chapter 7.3 established that a driver binds per interface. With one destination, two drivers would have to share it and cooperate — which means they are not independent at all.

So the device needs several destinations, distinguishable without inspecting content, with independent flow.

And they must be addressable by the host, because the host initiates everything (Chapter 2.6) — it has to be able to say this transaction is for that destination.

That is an endpoint. Not a definition imposed on the learner, but the thing the requirements just described.

2. What an Endpoint Actually Is

Now the mental model, and it is the one the whole module rests on.

An endpoint is a buffer with an identity and its own state.

Three parts, each doing work:

A buffer. Data has to sit somewhere between the moment the device produces it and the moment the host collects it — or between the moment the host delivers it and the moment the device consumes it. Chapter 9.5 is about that storage; what matters here is that it exists.

An identity. The host addresses it. Chapter 9.2 is about how.

Its own state. This is the part most often missed, and it is what makes an endpoint more than a memory region: each endpoint independently knows whether it has data, whether it can accept more, and whether it is able to participate at all. Two endpoints on the same device can be in completely different conditions at the same instant.

3. What a Controller Instantiates

Move from abstraction to silicon, because this is where the module earns its place.

For each endpoint a device implements, a controller contains a recognisable set of things:

A diagram of what a device controller instantiates for a single endpoint. There is a buffer holding packet data, a set of configuration values recording the endpoint's maximum packet size and transfer type, and control state recording whether the endpoint currently holds data, whether it can accept more, and whether it is halted. Shared across all endpoints are the address decoder that selects which endpoint a transaction is directed at, and the single physical differential pair that carries every transaction. An annotation notes that the buffer is duplicated per endpoint while the decoder and the wire are not.Bufferpacket data — per endpointConfigurationmax packet size, transfertypeControl statehas data? can accept?halted?Address decoderSHARED — selects oneendpointOne differential pairSHARED — one transaction ata timeselectsselectstransactions arriveconstrains12
Figure 1 — what an endpoint is in hardware. The buffer is the obvious part and the smallest part of the argument: the control state is what makes each endpoint independently addressable and independently ready, and it is per-endpoint because the conditions genuinely differ.

The buffer is per endpoint, and it is the part everyone thinks of. It is also the part that costs the most silicon, which is why Chapter 9.3 shows that endpoints are a budgeted resource rather than a free abstraction.

The configuration is per endpoint, and it comes from the descriptor. Chapter 7.4 established that an endpoint descriptor declares a maximum packet size and a transfer type, and that both are promises the hardware must keep — a descriptor advertising 64 bytes in front of a 32-byte buffer is a device that will fail the first time the host uses the full size.

The control state is per endpoint, and it is the interesting part. Whether this endpoint has data waiting, whether it can accept more, whether it has been halted — these differ between endpoints on the same device at the same moment, which is exactly why they cannot be global.

The decoder is shared, because there is one stream of traffic to decode. Chapter 9.2 builds it.

4. The Endpoint Record, as RTL

The smallest honest representation of §3, and it is deliberately a data structure rather than a machine.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_ep_pkg -- the per-endpoint record
//
// Classification: CONCEPTUAL RTL. It is a type definition plus the helper
// that resets a record to a defined state. There is no behaviour here on
// purpose: this chapter's claim is that an endpoint IS state, and the
// cleanest way to make that claim is to write the state down.
//
// WHAT IT MODELS. Section 3's three groups -- configuration that arrives
// from the descriptor, control state that changes as transactions occur,
// and the buffer, which is referenced rather than contained because
// Chapter 9.5 owns its structure.
//
// WHAT IT DOES NOT MODEL. Any transaction behaviour: what makes an endpoint
// hold data, what clears it, what halts it, and what a host does about any
// of it. Those are Modules 10 to 13. The buffer's organisation is Chapter
// 9.5's. The decode that selects a record is Chapter 9.2's.
// ─────────────────────────────────────────────────────────────────────────
package usb_ep_pkg;

  // Bits 1:0 of bmAttributes (Chapter 7.4). Named here because the record
  // stores one, and deliberately NOT explained here -- what each type does
  // is Module 10's subject, and this module only needs to know a type
  // exists and is per-endpoint.
  typedef enum logic [1:0] {
    EP_CONTROL = 2'd0,
    EP_ISOC    = 2'd1,
    EP_BULK    = 2'd2,
    EP_INTR    = 2'd3
  } ep_xfer_type_e;

  // ── CONFIGURATION: written once when a configuration is selected
  // (Chapter 8.5), read constantly, never changed by a transaction.
  typedef struct packed {
    logic               implemented;   // does this slot exist in silicon?
    logic               enabled;       // does the active configuration use it?
    ep_xfer_type_e      xfer_type;
    logic [10:0]        max_packet;    // bits 10:0 of wMaxPacketSize (7.4)
  } ep_config_t;

  // ── CONTROL STATE: changes as transactions occur, and differs between
  // endpoints on the same device at the same instant. This is the group
  // that makes an endpoint more than a memory region.
  typedef struct packed {
    logic               has_data;      // something is waiting to be sent
    logic               can_accept;    // room exists for something arriving
    logic               halted;        // this endpoint is not participating
  } ep_status_t;

  typedef struct packed {
    ep_config_t cfg;
    ep_status_t st;
  } ep_record_t;

  // The value an endpoint record must hold when nothing has configured it.
  // Written as a function rather than a constant so that every reset path
  // uses the same definition -- Chapter 8.5's argument about a fact stored
  // in several places, applied to a reset value.
  function automatic ep_record_t ep_record_reset(input logic implemented);
    ep_record_t r;
    r.cfg.implemented = implemented;   // a property of the SILICON, not of
    r.cfg.enabled     = 1'b0;          // any configuration -- so it survives
    r.cfg.xfer_type   = EP_CONTROL;
    r.cfg.max_packet  = 11'd0;
    r.st.has_data     = 1'b0;
    r.st.can_accept   = 1'b0;          // nothing may be accepted until the
    r.st.halted       = 1'b0;          // endpoint is enabled
    return r;
  endfunction

endpackage

Purpose. To write down what an endpoint is, so that the claim an endpoint is state has something concrete behind it.

Inputs and outputs. None — it is a type definition and a function.

State. The record itself: configuration that arrives from a descriptor, and control state that changes with traffic.

Hardware implied. Per endpoint: a few flip-flops for the status bits, a small register for the packet size and type, and a validity bit. Elaborating the record gives 18 bits per endpoint — 15 of configuration and 3 of status — and that number is worth holding on to, because Chapter 9.3 multiplies it by the number of slots and finds that the records are the cheap part.

Reset. ep_record_reset returns everything to a defined value — except implemented, which is passed in rather than cleared. That distinction is the next callout.

Assumptions. That the configuration fields are written when a configuration is selected and not by transactions; that max_packet has already been masked to its meaningful bits (Chapter 7.4 owns the encoding).

Omissions. All transaction behaviour, the buffer's organisation, the decode, and everything about what each transfer type means.

What DV should verify. That a reset leaves every status bit false and enabled false; that implemented is unaffected by any reset or configuration change; and that no transaction path writes a configuration field.

5. Endpoints Are Independent

The property that makes the abstraction worth having, and it is worth being precise about what independent means and does not.

Independent in state. Endpoint 1 can hold data while endpoint 2 is empty. One can be halted while another operates normally. Each carries its own status, and nothing about one implies anything about another.

Independent in flow. A device with nothing to say on one endpoint does not block another. This is what §1's single-destination model could not do, and it is the main reason endpoints exist.

Not independent in bandwidth. They share one bus. Every transaction to every endpoint on every device on that bus is serialised onto one differential pair. An endpoint is not a reservation of throughput — it is a destination.

Not independent in time. Exactly one transaction is in progress on the bus at any moment. A device can have several endpoints with data waiting, and they will be served one at a time, in an order the host chooses (Chapter 2.6).

6. Where Endpoints Come From

Three sources, and keeping them apart prevents a whole class of confusion.

The silicon decides which endpoints can exist. A controller is built with a certain number of endpoint slots, each with a buffer of a certain size. This is fixed at design time and is implemented in §4's record.

The descriptor declares which endpoints a configuration uses. Chapter 7.4 established that each endpoint descriptor is a promise: an endpoint at this address, of this type, with packets up to this size. A configuration selects a subset of what the silicon offers.

The host's configuration selection decides which are live right now. Chapter 8.5 established that endpoints operate only while the device is Configured, and that the enable must be derived from the state rather than stored separately wherever possible.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
silicon         ⊇   descriptor         ⊇   currently enabled
(implemented)       (declared in a         (the active
                     configuration)          configuration's)

The containments are the invariants, and each has a failure attached:

  • A descriptor declaring an endpoint the silicon does not implement is Chapter 7.4 §7's measured defect: a device that enumerates perfectly and then does not respond.
  • An endpoint enabled while not declared by the active configuration is Chapter 8.5 §7's: hardware operating without authorisation.
  • An endpoint declared and implemented but not enabled is correct — that is simply an alternate configuration's endpoint while a different configuration is active.

And one of these is checkable before simulation. Whether the descriptor's endpoints fit inside the silicon's is a fact about two constants, so Chapter 9.3 makes it an elaboration-time check.

7. Mutation Test

§4's package has no behaviour, which makes it look unmutable. It has one function, and that function encodes §4's distinction between a property of the silicon and a property of a configuration — so it has exactly the defects that distinction exists to prevent.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mutation                                caught by
  ────────────────────────────────────────────────────────────────
  correct package                         (nothing)
  C1  reset clears `implemented` too      both implemented checks
  C2  reset leaves `can_accept` high      the readiness invariant

C1 — reset clears implemented along with everything else

The natural mistake: a reset function that clears every field uniformly, because clearing everything is what reset functions do.

Result. Both checks on implemented fire.

And the defect it models is real. implemented is a property of the silicon — this controller was built with a buffer and control state for this endpoint. A reset that cleared it produces hardware that has forgotten it exists: every endpoint reports itself unimplemented, every configuration referencing one appears to exceed the silicon, and a device that was working before the reset has no endpoints afterwards.

Which is why §4's reset function takes it as an argument rather than assigning it. A field that must survive a reset cannot be written by the reset, and passing it in makes that structural rather than remembered — the same reasoning Chapter 9.3 applies to endpoint zero's enable.

C2 — reset leaves can_accept asserted

Result. The readiness invariant fires: a slot reports itself ready to accept data while enabled is false.

The defect is a containment violation of §6's chain, at its innermost link. An endpoint that can accept before it is enabled is one that will take data in a state where Chapter 8.5 says it must not operate — hardware acting without authorisation, which that chapter measured from the other direction.

8. Verification

This chapter's contribution is the model the rest of the module verifies against, and one invariant worth stating now.

The containment invariant:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
enabled  ⟹  declared  ⟹  implemented

Why it needs a composition check. Chapter 8.4 established the technique and the reason: implemented is a property of the design, declared is a property of the descriptor image, and enabled is a property of the protocol state. Three owners, so no property written inside any one of them can relate them.

Stimulus for this chapter. Configuration selection and un-selection with each configuration the device offers; a bus reset while endpoints are enabled; and — the case that matters — a descriptor declaring an endpoint outside the implemented set, which must fail at build time rather than in simulation.

Observation. All three facts together. Checking enabled alone reports an endpoint operating correctly while it operates without hardware behind it.

Reference model. For this chapter the model is a set: which endpoints the active configuration declares. Compare against which are enabled, after every configuration event.

And the boundary this module respects: nothing here checks what an endpoint does with a transaction. That is Module 10's onward, and a plan that tried to verify transfer behaviour from this chapter's model would be verifying something it has no representation of.

9. Common Misconceptions

10. Reason It Through

A device declares four endpoints and is expected to sustain its rated throughput on all four at once. In the lab it achieves it on any one endpoint alone and falls well short when all four are active.

What is the first thing to rule out? Per-endpoint logic. Each endpoint works at full rate alone, so no endpoint's buffer, decode or control state is the limit.

What does that leave? Something shared. §3's figure lists exactly two shared things: the decoder and the bus.

Which one? The bus, almost certainly. §5's point: endpoints are multiplexed in time onto one differential pair, so the four endpoints are dividing one bandwidth rather than each having their own.

What was the design error? Sizing the device as though endpoints were independent in bandwidth. The correct sum was computed per endpoint and never added up.

How would you confirm it in one measurement? Total throughput across all four endpoints. If the sum is roughly what one endpoint achieves alone, the bus is saturated and the device is behaving correctly — the specification was wrong, not the implementation.

And what makes this worth a section? Because there is no bug to find. Every block is correct, every test passes, and the device does exactly what it was built to do. The error was in a mental model, made before any RTL was written, and the only evidence that distinguishes it from an implementation defect is an arithmetic check nobody thought to do.

11. Understanding Check

12. Summary

A device needs several destinations because it has several independent flows, and with one destination it would have to demultiplex inside the data, invent a per-device protocol, and let one flow block another — while drivers that are supposed to be independent shared a single path.

An endpoint is a buffer with an identity and its own state.

The state is the part most often missed and the part that matters: each endpoint independently knows whether it holds data, can accept more, or is participating at all, and two endpoints on one device differ at the same instant.

An endpoint is not a wire. There is one differential pair; endpoints are logical destinations selected by an address in the traffic, multiplexed in time. They are independent in state and flow and not in bandwidth or time — a distinction that determines whether a device is sized correctly before any RTL is written.

In hardware, each endpoint is a buffer, a configuration from its descriptor, and control state; the decoder and the bus are shared. implemented and enabled are separate bits because one is fixed at design time and survives every reset while the other changes with every configuration commit — and keeping them apart is what makes a configuration referencing hardware that does not exist a detectable error.

And the containment that organises the module: enabled ⊆ declared ⊆ implemented, with a real failure attached to each boundary and three different owners, which is why it needs a composition check rather than ordinary per-block assertions.

13. What Comes Next

An endpoint is addressable. That was the requirement §1 derived and the chapter has not yet said how an address is formed.

Chapter 9.2 is that encoding, and it is smaller than it sounds and more consequential than it looks: one byte, of which four bits are a number and one bit is a direction. The consequence is that two endpoints can share a number and be entirely different endpoints — 0x81 and 0x01 are distinct hardware with distinct buffers and distinct state, and a decoder that ignores one bit merges them.

That merge is the module's first measurable bug, and it works perfectly until both directions are used at once.

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.