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:
#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.
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 endpoint | IN endpoint | |
|---|---|---|
| Who fills the buffer | host | device |
| Who empties it | device | host |
| Device's timing role | react to arrival | anticipate collection |
| Hard case | no room when data arrives | nothing ready when asked |
| Can the device signal readiness? | not applicable | no |
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 cycles4. 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.
// ─────────────────────────────────────────────────────────────────────────
// 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;
endmodulePurpose. 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.
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.
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_validasserted × 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
Related tutorials
- Related topic
Endpoint Buffers
Why an endpoint needs storage, why one buffer forces a choice between filling and being collected, and the ping-pong structure that removes it — with the occupancy-versus-flags invariant a single comparison breaks.
- Related topic
The Parallel Port
Why presenting eight data lines at once forces an explicit data/strobe/acknowledge handshake, what that costs in timing discipline, a synthesizable teaching FSM that implements it with the assertions that protect it, and why an interface shaped around one peripheral's operational model cannot generalise.
- Related topic
PS/2 Keyboard / Mouse
The dedicated input port where the device supplies the clock and the host receives on someone else's timing. The two-wire open-drain bus, the framed byte, why sampling a foreign clock directly is a real hardware bug, and a synthesizable teaching receiver with its synchroniser, recovery timeout and assertions.
- Related topic
The USB Device
What is left when every decision belongs to the host: the responder discipline, why a device must answer even when it has nothing to say, the split between the USB-facing device controller and the function it exists to provide, and a teaching abstraction with the assertion that protects the ownership model.
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.
