Skip to content
VLSI Mentor

USB · Module 9

Endpoint Direction

IN and OUT are named from the host's point of view, so an IN endpoint sends data from the device. Why the host initiates both directions, and why the two directions are not mirror images in hardware.

Chapter 9.2 established that bit 7 of an endpoint address is a direction, and Chapter 9.3 that each direction of each number is independently implemented. Neither said what the direction means, and the answer is the 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 to the host. An OUT endpoint receives data from the host.

Every engineer meets this backwards once. The Linux kernel states it in two words in the header that defines the constants:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#define USB_DIR_OUT   0      /* to device */
#define USB_DIR_IN    0x80   /* to host   */

This chapter is about why the naming is that way, and about the thing the naming hides: the two directions are not mirror images in hardware.

1. Why the Names Are Backwards From the Device

They are not backwards. They are consistent — with a point of view that is not the device's.

USB is host-centric. Chapter 2.6 established that the host schedules everything and devices only respond. So the protocol's vocabulary is written from where the decisions are made, and every term means what it means to the host.

Read it as the host and it is obvious. Data coming in to the host is an IN transfer. Data going out from the host is an OUT transfer. Nothing is inverted; the sentences are simply about the host.

Read it as the device and it is maddening, because a device engineer thinks about their device, and from there an IN endpoint is an output.

2. Both Directions Are Host-Initiated

The part that surprises people more than the naming, and it is where the asymmetry begins.

A device never starts a transfer. Not in either direction. Chapter 2.6 established this and it applies without exception here: a device with urgent data on an IN endpoint waits.

So what does a device with data actually do? It makes the data available in the endpoint's buffer and waits for the host to ask. When the host eventually addresses that endpoint, the data goes out. Until then it sits there.

And what happens if the host asks and there is nothing? The device has to say so — it cannot simply stay silent, because silence is indistinguishable from a device that is broken or absent. The mechanism by which it says not yet is a protocol behaviour Module 12 owns; what matters here is that being empty when asked is a normal, expected condition that the endpoint must be able to report.

A sequence diagram comparing the two endpoint directions. In the OUT direction the host addresses an OUT endpoint and sends data, and the device accepts it into that endpoint's buffer; the device learns data has arrived only when it arrives. In the IN direction the device first places data into an IN endpoint's buffer on its own schedule, then waits. The host later addresses that IN endpoint, and the device returns the waiting data. If the host addresses the IN endpoint while the buffer is empty, the device must report that it has nothing ready rather than staying silent. Every exchange in both directions is begun by the host.Who starts what — both directionsHostDeviceOUT: addressesendpoint, sends datadata lands in theOUT bufferIN: device fills thebuffer, on its ownschedule…and waits. Itcannot ask to beasked.IN: addressesendpoint — anythingfor me?the waiting dataIN: addresses itagainnothing ready — andit must SAY so
Figure 1 — both directions begin with the host. The device's role differs entirely between them: on OUT it is told when data arrives, and on IN it must already have data ready when asked, because it cannot ask to be asked.

That last exchange is the asymmetry in one line. An OUT endpoint is told when something happens. An IN endpoint must be ready in advance, because it has no way to signal that it has become ready.

3. The Directions Are Not Mirror Images

The engineering consequence, and the reason this chapter exists as more than a naming note.

An OUT endpoint is reactive. Data arrives when the host sends it. The endpoint's job is to have room and to accept. Its hard question is what if there is no room? — the device is being handed something it cannot take.

An IN endpoint is speculative. The device must decide when to fill the buffer without knowing when the host will collect it. Its hard question is what if the host asks before I am ready? — and that question has no equivalent on the OUT side, because nothing asks an OUT endpoint for anything.

OUT endpointIN endpoint
Who fills the bufferhostdevice
Who empties itdevicehost
Device's timing rolereact to arrivalanticipate collection
Hard caseno room when data arrivesnothing ready when asked
Can the device signal readiness?not applicableno

The last row is the one that shapes hardware. A device cannot tell the host I now have data. It can only have data, and wait. Everything about how an IN endpoint is filled — how early, how much, how it is refilled — follows from that, and Chapter 9.5 is where it becomes a buffering strategy.

IN and OUT on one endpoint number — controller-domain view

9 cycles
A waveform comparing the two directions of one endpoint number. Early on, the device writes data into its IN buffer on its own schedule, and the IN buffer's has-data indication goes high; nothing else happens, because the device cannot announce readiness. Later the host addresses the IN endpoint, the selection becomes valid with direction IN, and the device drives the waiting data onto the bus, after which the IN buffer is empty. The host then addresses the IN endpoint again while the buffer is still empty, and the device reports not-ready-empty rather than staying silent. Separately the host addresses the OUT endpoint and sends data, which is written into the OUT buffer because room exists. The figure shows controller-domain decoded signals rather than electrical bus conditions, and depicts no real durations.device fills IN buffer — unprompted, unannounceddevice fills IN buffer —unprompted, unannouncedhost finally asks; data goes outhost finally asks; datagoes outasked again while empty — must SAY soasked again while empty —must SAY soOUT: host sends, device acceptsOUT: host sends, deviceacceptssel.validsel.dir------IN--IN--OUT--in_buf_has_databus_tx_validnot_ready_emptyout_buf_has_roombuf_wr_ennot_ready_fullt0t1t2t3t4t5t6t7t8
Figure 2 — the same endpoint number in both directions, over time. Note that the device fills the IN buffer long before it is collected and has no way to announce it, while the OUT buffer is filled by an event the device does not control — which is the asymmetry that makes one direction speculative and the other reactive.

4. Steering the Data Path, as RTL

The direction determines which way data moves through the endpoint, and the block that decides it is small and worth writing explicitly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_ep_datapath_steer
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL. It models the
// direction-dependent routing of one endpoint's data path, and the
// direction-dependent readiness that section 3 argues is NOT symmetric.
//
// WHAT IT MODELS. Which way data flows for a selected endpoint, which
// readiness condition applies, and the fact that the two directions ask
// different questions of the same buffer.
//
// WHAT IT DOES NOT MODEL. The buffer itself (Chapter 9.5 owns its
// structure, and this block talks to it through a deliberately minimal
// interface), the transaction that carries the data (Module 12), what the
// device SAYS when it is not ready (Module 12 owns the response), packet
// framing (Module 11), or transfer types (Module 10).
//
// ON THE "not ready" OUTPUT. This block reports that the endpoint cannot
// participate right now and does NOT decide what the device answers. That
// separation is deliberate: the reason differs by direction, and the
// response is a protocol behaviour owned elsewhere.
// ─────────────────────────────────────────────────────────────────────────
module usb_ep_datapath_steer
  import usb_ep_addr_pkg::*;
(
  // The selected endpoint, from Chapter 9.2's decoder.
  input  ep_select_t   sel,

  // ── This endpoint's buffer, as seen from here. Chapter 9.5 owns what is
  // behind these; all this block needs is "is there data" and "is there
  // room", which are the two questions the two directions ask.
  input  logic         buf_has_data,   // something is waiting to be sent
  input  logic         buf_has_room,   // room exists for something arriving

  // ── Data from the bus (an OUT transaction) and to the bus (an IN one).
  input  logic [7:0]   bus_rx_data,
  input  logic         bus_rx_valid,
  input  logic [7:0]   buf_rd_data,

  // ── To the buffer
  output logic [7:0]   buf_wr_data,
  output logic         buf_wr_en,

  // ── To the bus
  output logic [7:0]   bus_tx_data,
  output logic         bus_tx_valid,

  // The endpoint cannot participate in this transaction. WHY differs by
  // direction, which is section 3's point, so the reason is exported
  // separately rather than folded into one flag.
  output logic         not_ready,
  output logic         not_ready_empty,   // IN:  asked, and nothing ready
  output logic         not_ready_full     // OUT: sent, and no room
);

  always_comb begin
    // Deterministic defaults. An endpoint that was not selected drives
    // nothing at all -- which matters, because every endpoint's steer block
    // shares the bus-facing signals.
    buf_wr_data     = 8'h00;
    buf_wr_en       = 1'b0;
    bus_tx_data     = 8'h00;
    bus_tx_valid    = 1'b0;
    not_ready_empty = 1'b0;
    not_ready_full  = 1'b0;

    if (sel.valid) begin
      if (sel.dir == EP_DIR_IN) begin
        // ── IN: device -> host. The device is the SOURCE.
        // The hard case (section 3): the host has asked and the buffer is
        // empty. The device cannot have signalled readiness in advance,
        // so this is a normal condition, not an error.
        if (buf_has_data) begin
          bus_tx_data  = buf_rd_data;
          bus_tx_valid = 1'b1;
        end else begin
          not_ready_empty = 1'b1;
        end
      end else begin
        // ── OUT: host -> device. The device is the SINK.
        // The hard case: data has arrived and there is no room. Note this
        // is tested against buf_has_room and NOT against buf_has_data --
        // the two directions ask different questions of the same buffer,
        // and using one condition for both is section 3's naive symmetry.
        if (buf_has_room) begin
          buf_wr_data = bus_rx_data;
          buf_wr_en   = bus_rx_valid;
        end else if (bus_rx_valid) begin
          not_ready_full = 1'b1;
        end
      end
    end
  end

  assign not_ready = not_ready_empty | not_ready_full;

endmodule

Purpose. To route one endpoint's data the correct way and to report the direction-appropriate reason it cannot.

Inputs. The selection from Chapter 9.2; two buffer conditions; the bus receive data and the buffer read data.

State. None — combinational. The state lives in the buffer (Chapter 9.5) and in the endpoint record (Chapter 9.1).

Outputs. Buffer write, bus transmit, and three readiness signals — a combined one and the two direction-specific reasons.

Hardware implied. A multiplexer on the direction bit and a little combinational logic, per endpoint or shared depending on the controller's structure.

Reset. None required.

Assumptions. That sel comes from Chapter 9.2's decoder and is already validated; that buf_has_data and buf_has_room refer to this endpoint's buffer; and that an unselected endpoint's outputs are ignored or combined by whatever aggregates them.

Omissions. The buffer, the transaction, the device's actual response, packet framing, and transfer types.

What DV should verify. That an IN selection never writes the buffer and an OUT selection never drives the bus; that a deselected endpoint drives nothing; that the empty and full reasons are mutually exclusive and direction-correct; and that buf_has_data is never consulted on an OUT transaction nor buf_has_room on an IN one.

5. Mutation Test

Four mutations, run against two plans: one observing only what the transaction engine consumes — the data path and the combined not_ready — and one that also observes the two specific reasons.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mutation                            consumer-only plan   plan incl. the reasons
  ────────────────────────────────────────────────────────────────────────────────
  correct steering                    OK                   OK
  D1  one condition for both dirs     BROKEN (2)           BROKEN (5)
  D2  direction sense swapped         BROKEN (4)           BROKEN (7)
  D3  no selection guard              BROKEN (1)           BROKEN (2)
  D4  both reasons asserted together  OK                   BROKEN (3)

D1 — one readiness condition for both directions

The naive symmetry of §3, in its most plausible form: test buf_has_data regardless of direction.

Result. IN transactions behave correctly. OUT transactions accept data only when the buffer already contains some — so an empty OUT endpoint, which is the normal state of one waiting for its first data, rejects everything.

The symptom is strange and diagnostic: the first write to an OUT endpoint fails, and once data somehow gets in, further writes succeed until the buffer fills. A device that works only after it has already worked.

D2 — swap the direction sense

The naming error of §1, in silicon.

Result. Seven failures. Data flows exactly backwards — the host's OUT data is never written to the buffer, and IN requests write bus data into the buffer instead of returning any.

And the trace is what makes it hard. Nothing is malformed: the host addresses valid endpoints and the device responds to every transaction. The bus looks entirely healthy, which is why this is found inside the controller rather than on an analyser.

D3 — drive the bus from a deselected endpoint

Remove the sel.valid guard.

Result. Every endpoint's steer block drives the shared bus-facing signals at once. With several endpoints instantiated, the transmitted data is whatever the aggregation makes of several drivers.

This is what the deterministic defaults are for. An unselected endpoint must drive nothing, and nothing has to be a defined value rather than an unassigned one — which is exactly what the assignments at the top of the always_comb provide, and why they are not merely style.

D4 — assert both not-ready reasons together

Result: it depends entirely on what the plan observes.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  consumer-only plan      OK (0 errors)      ← survives completely
  plan including reasons  BROKEN (3 errors)

The combined not_ready is unchanged, and that is all the transaction engine consumes — so every data-path check passes. What fires is the exclusivity sweep: an exhaustive walk of all 32 input combinations, checking that empty only ever arises on IN and full only on OUT.

6. Verification

This chapter's commit point is data moved the correct way, or the correct reason was reported.

Stimulus. IN and OUT transactions to the same endpoint number; an IN transaction with the buffer empty and with it full; an OUT transaction with the buffer full and with it empty; a transaction to a deselected endpoint; and both directions in immediate succession on the same number.

The stimulus requirement §5 makes non-negotiable: an OUT transaction to an empty buffer. That is the normal state of an endpoint waiting for its first data, and it is the only case that distinguishes D1 — a plan that fills a buffer before testing it never generates it.

Observation. The buffer write, the bus transmit, and the two specific reasons. D4 is invisible to an observation of the combined flag alone.

Reference model. A direction-indexed function: for IN, the expected output is the buffer's data when it has data and a not-ready-empty otherwise; for OUT, a buffer write when there is room and a not-ready-full otherwise. Four cases, which is small enough that the model is exhaustive rather than sampled.

Representative coverage — crosses:

  • direction IN × OUT, crossed with buffer empty × partially full × full — all six cells
  • selected × deselected, in both directions
  • both directions of one number, back to back
  • bus_rx_valid asserted × deasserted while an OUT endpoint is selected

Negative cases with defined outcomes: an IN selection must never assert a buffer write; an OUT selection must never assert bus transmit; a deselected endpoint must drive nothing; and the two not-ready reasons must never assert together.

7. Debugging: Which Direction Fails Tells You What Broke

The asymmetry of §3 makes direction a first-class diagnostic axis, and this is where it pays.

Symptom A. OUT transfers work. IN transfers frequently return nothing, though the device clearly has data to send.

What does OUT works establish? The decode is right, the endpoint is enabled, and the buffer exists. The shared path is fine.

What is specific to IN? §3's row: the device must fill the buffer in advance, because it cannot signal readiness. Frequent empties mean the fill is not keeping up with the host's asking — a timing problem, not a correctness one.

Where do you look? At when the device writes the buffer relative to when the host polls it. Chapter 9.5 is the chapter about fixing it.

Symptom B. IN transfers work. OUT transfers drop data under load.

What is specific to OUT? The hard case is no room when data arrives. Dropping under load means the device is not draining the buffer fast enough — a capacity problem.

Why is this not the same bug? Because the resources differ: symptom A is about producing early enough, symptom B is about consuming fast enough. Same buffer, opposite pressure.

Symptom C. Both directions work individually. Used together, data appears in the wrong stream.

This is not a direction problem at all — it is Chapter 9.2 §5's decoder merging 0x81 and 0x01. The tell is that each works alone: a direction fault breaks one direction, and a decode fault breaks the combination.

The discipline: ask which direction fails before asking what is wrong. One direction failing points at that direction's asymmetric hard case; both failing together points at something shared; each working alone and failing together points at the decode.

8. Common Misconceptions

9. Reason It Through

A device controller is built with one endpoint block, parameterised by direction, on the grounds that a buffer is a buffer and duplicating the logic would be wasteful.

Is the premise right about the data path? Yes. The storage is identical; only the direction of flow differs, and a multiplexer handles that — §4's block is exactly this and is a dozen lines.

Where does it stop being right? At the control. §3's table has five rows and only the first two are symmetric. Who fills and who empties are mirror images; the timing role, the hard case, and the ability to signal readiness are not.

What does that mean concretely for the parameterised block? The IN path needs a fill policy — a decision about when the device writes the buffer — and the OUT path has no equivalent, because nothing decides when data arrives. A parameter cannot express this logic exists in one configuration and not the other; it can only select between behaviours that both exist.

So is the shared block wrong? No — it is right for the part that is shared. The error is expecting the parameter to cover everything. §4's block is deliberately only the steering: it takes two buffer conditions as inputs and asks the direction-appropriate one, leaving the fill policy outside.

And the general principle? Parameterise what differs by value; separate what differs by existence. A direction parameter handles which way does data flow. It cannot handle does this logic exist at all, and attempting it produces a block with a policy that is dead in half its instantiations — which is worse than two blocks, because the dead half is still maintained.

10. Understanding Check

11. Summary

IN means into the host. The names are written from the host's point of view because USB is host-centric, so an IN endpoint is a source on the device and an OUT endpoint is a sink — the kernel's own header marks them to host and to device.

Both directions are host-initiated. A device never starts a transfer; it makes data available and waits. And when the host polls an empty IN endpoint, the device must report it — silence is indistinguishable from absence, so being empty is a normal condition the endpoint has to be able to express.

The directions are not mirror images, and only the first two rows of the comparison are symmetric. An OUT endpoint is reactive, with the hard case no room when data arrives. An IN endpoint is speculative, with the hard case nothing ready when asked — and the reason is the decisive one: a device cannot signal that it has become ready.

That asymmetry reaches the RTL. §4's steering block asks the direction-appropriate buffer question, and §5 measured a single shared condition making an empty OUT endpoint reject everything — a device that works only after it has already worked. Swapping the direction sense produces a perfectly healthy bus trace with data flowing backwards, which is why it must be found inside the controller.

And direction is a diagnostic axis. One direction failing points at that direction's asymmetric hard case — IN empty is a fill-timing problem, OUT dropping is a capacity problem. Both working alone and failing together points somewhere else entirely, at the decode. Ask which direction fails before asking what is wrong.

12. What Comes Next

§3 left a question hanging that §7 then turned into a symptom: an IN endpoint must be filled in advance, and nothing tells the device when collection will happen.

Chapter 9.5 is the answer, and it is the module's last and densest chapter. Buffering is what lets a device produce on its own schedule and be collected on the host's — and the interesting structure is double buffering, where one bank is being filled while the other is being emptied, so the device never has to choose between the two.

It is also where Chapter 9.1's claim that the buffer dominates an endpoint's cost gets paid, and where a single < in a full-flag comparison decides whether data is silently lost.

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.