Skip to content
VLSI Mentor

USB · Module 1

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.

The two stacks examined so far both leave the host in charge of timing. RS-232 settles the bit period by prior agreement, so the host knows when to sample before anything arrives. The parallel port has the host assert a strobe, so the host decides when data is valid. Different mechanisms, same underlying assumption: the machine that wants the data controls when it arrives.

The dedicated keyboard and mouse port inverts that. The device generates the clock, for traffic in both directions, and the host receives on timing it does not control and cannot predict. A keystroke happens when a human presses a key; the resulting bits arrive on a clock produced inside the keyboard, bearing no relationship whatsoever to the clock inside the computer.

That inversion is why this is the most valuable of the three chapters for a hardware engineer. An input arriving on a foreign clock cannot simply be wired into a state machine — doing so is not a style problem or an inefficiency but a genuine hardware fault with a characteristic failure signature. The synchroniser that Chapter 1.4 used in passing becomes this chapter's subject, and the receiver built here is the one an FPGA engineer would recognise.

1. A Dedicated Port, and What That Buys

Chapter 1.2 placed this stack at the far end of the dedicated-purpose axis: a port that exists to have a keyboard on it, and a second, electrically identical one that exists to have a mouse on it.

The simplification that buys is real. The host does not need to discover what is attached, because the port's identity answers the question. It does not need to negotiate a mode, select a driver at runtime, or resolve addressing between competing devices — there is exactly one device, its kind is known in advance, and its traffic is small and sporadic. Two wires carry everything.

The cost is the mirror image, and Chapter 1.2's table already recorded it: one device per port, capacity fixed when the machine was built, the device assumed present at start-up, and an interface whose assumptions do not transfer to any other device class. What this chapter adds is the engineering consequence of that bargain — because the design that makes a two-wire, device-clocked interface work is genuinely interesting, and it is where the transferable skill lives.

2. Two Wires, Both Shared

The interface is a clock line and a data line, and two properties govern everything built on them.

Both lines are bidirectional and driven open-drain. Neither end drives a line high; each end can only pull it low or release it, and a pull-up resistor restores the idle high level. That arrangement is what lets two devices share a wire without contention: if both release, the line is high; if either pulls, the line is low; and two simultaneous pulls do no damage. It is the same electrical idea used by other shared two-wire buses, chosen for the same reason.

The device drives the clock. In the device-to-host direction — the common case, a keystroke or a movement report — the device produces both the clock transitions and the data bits. In the host-to-device direction, used for commands such as setting indicator lamps or configuring reporting behaviour, the host requests the exchange but the device still generates the clock. The host does not get to impose its own timing in either direction.

Because both lines are shared and open-drain, the host retains one blunt but decisive power: it can pull the clock line low and hold it, which prevents the device from producing usable clock transitions and so inhibits communication. That is how a host stops an input device from talking while it is not ready to listen — and it is also the opening move when the host wants to send a command, since it must first silence the device before claiming the bus.

A host and a PS/2 input device share two open-drain lines, clock and data, each held high by a pull-up resistor and pulled low by either end. The device contains the clock generator, so both device-to-host and host-to-device transfers run on the device's timing. The host side contains a receiver that must first synchronise the incoming clock and data into its own clock domain before any state machine uses them, and it can inhibit the device by holding the clock line low.Host receiversynchronise, then decodeClock lineopen-drain · driven by deviceData lineopen-drain · sharedHost holds clock lowinhibit — the host's one leverInput deviceowns the clock generatorgenerates12
Figure 1 — two shared open-drain lines, with the clock produced at the device end.

Figure 1 is a structural diagram: it shows who drives what and where the receiver's domain boundary falls. The orange clock line is the one that matters for §4 onward, because it is the signal that enters the host on no clock of the host's own.

3. The Frame, and Where the Bits Are Valid

A device-to-host transfer carries one byte inside a small frame, and the frame's shape is what the receiver in §5 is built to recognise.

The line rests idle with both clock and data high. A transfer opens with a start bit, which is always 0. Then come eight data bits, least-significant first. Then a parity bit, using odd parity — chosen so that the count of ones across the data bits and the parity bit together is always odd. Then a stop bit, always 1, which returns the data line to its idle level and makes the next start bit unambiguous.

The timing rule is the one to memorise: for device-to-host traffic, the data line is valid around the falling edge of the clock, and the host captures each bit there.

PS/2 device-to-host frame — bus view

10 cycles
A PS/2 device-to-host transfer viewed on the bus. The clock line, generated by the device, falls once per bit. The data line carries a start bit of zero, then eight data bits least-significant first. Each bit is stable around the falling edge of the clock, which is where the host captures it. The figure shows the start bit and the first three data bits.start bit captured (always 0)start bit captured (always0)D0 — LSB firstD0 — LSB firstD1D1D2D2ps2_clkps2_dat1001100111bitidleSTARTSTARTD0D0D1D1D2D2D2t0t1t2t3t4t5t6t7t8t9
Figure 2 — the PS/2 bus during a device-to-host frame; bits are captured on clock falling edges.

The host-to-device direction exists and is used for real things — setting keyboard indicator lamps, configuring how a mouse reports. Its sequence is more involved, because the host must first inhibit the device by holding the clock low, then signal its intent on the data line, then release the clock so the device can generate the clock for the bytes the host is sending, and the frame carries an extra acknowledgement bit from the device at the end. This chapter does not build that path. It is named so you know the interface is genuinely bidirectional, and so the receiver below is understood as one half of a larger picture.

4. The Problem the Host Actually Has

Now the part that generalises far beyond this interface.

The clock and data lines arrive at the host from a device with its own oscillator. Nothing relates their transitions to the host's clock. They can change at any instant, including during the setup and hold window of whatever flip-flop first samples them.

A flip-flop sampled while its input is changing does not simply take the old or new value. It can enter a metastable state, sitting at an indeterminate level for an unbounded time before resolving to 0 or 1. It will resolve — the probability of remaining metastable decays extremely quickly — but the resolution time is not bounded in advance, and if the flip-flop's output is consumed before it settles, the consumers can disagree about what they saw.

That last point is the one that turns a statistical curiosity into a real bug. Feed an unsynchronised external signal directly into a state machine and different state bits, computed through different logic paths, can capture different values from the same metastable flop. The state register then holds a combination the encoding does not define — a state the designer never wrote, from which behaviour is undefined. A receiver can land somewhere it cannot leave.

The standard mitigation is a two-flop synchroniser: sample the foreign signal into one flop, then sample that flop's output into a second, and use only the second flop's output. The first flop may go metastable; it is given a full clock period to resolve before anything reads it, and only the resolved value propagates. Crucially, this is a probabilistic mitigation, not a proof. It makes the mean time between failures enormous — typically far beyond the product's life — but not infinite, and the margin depends on the clock period, the process and the flop's characteristics. Adding a third stage buys more margin where a design needs it.

Two further properties matter for what follows. A synchroniser costs latency — the signal appears inside the domain a cycle or two late, which is harmless here and not always. And a synchroniser gives no guarantee about pulses shorter than the sampling period: a narrow foreign pulse can pass between two sampling edges and never be seen. That is exactly the hazard Chapter 1.4's acknowledge edge detector was guarding against, and here it is the reason the receiver watches an edge of a synchronised clock rather than trusting levels.

5. A Teaching Receiver

Here is the receive path, built around the two obligations established above: synchronise first, then decode a frame whose validity is checked rather than assumed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// ps2_rx
//
// Classification: SYNTHESIZABLE EDUCATIONAL RECEIVER RTL.
//
// MODELS: the device-to-host receive path -- two-flop synchronisation of the
// externally clocked bus, falling-edge detection on the synchronised clock,
// LSB-first shift-in of an 11-bit frame (start, 8 data, odd parity, stop),
// validation of the start, parity and stop bits, and an inactivity timeout
// that abandons a partial frame so a glitch cannot wedge the receiver.
//
// DOES NOT MODEL: the host-to-device direction at all, including inhibit,
// request-to-send and the device's acknowledge bit (section 3); scan-code
// or mouse-packet interpretation, which is a layer above this one; the
// open-drain pad, its pull-up and any bus-contention behaviour; and the
// interface's numeric timing obligations, which come from its own
// definition and are deliberately absent here.
//
// THE SYNCHRONISER IS A PROBABILISTIC MITIGATION, NOT A GUARANTEE. Two
// stages is the common default; a design with a tighter period or a harsher
// MTBF target uses more. See section 4.
// ─────────────────────────────────────────────────────────────────────────
module ps2_rx #(
  // Cycles of bus inactivity, measured in this module's clock, after which a
  // partially received frame is abandoned. Must exceed the interface's
  // longest legal bit period by a comfortable margin, so a slow but legal
  // device is never mistaken for a stalled one. The default is a placeholder.
  parameter int TIMEOUT_CYCLES = 20000
) (
  input  logic       clk,
  input  logic       rst_n,

  // Raw bus inputs. ASYNCHRONOUS to clk -- never use these directly.
  input  logic       ps2_clk,
  input  logic       ps2_dat,

  // Byte interface, in this module's clock domain.
  output logic       byte_valid,    // one-cycle pulse: byte_data is good
  output logic [7:0] byte_data,
  output logic       err_parity,    // one-cycle pulse: odd parity failed
  output logic       err_framing,   // one-cycle pulse: bad start or stop bit
  output logic       err_timeout    // one-cycle pulse: partial frame abandoned
);

  // ── Stage 1: cross into this clock domain ──────────────────────────────
  // _meta may go metastable; it is read only by _sync, a full clock later.
  // _prev exists solely to give the edge detector a previous value.
  logic ps2_clk_meta_q, ps2_clk_sync_q, ps2_clk_prev_q;
  logic ps2_dat_meta_q, ps2_dat_sync_q;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      ps2_clk_meta_q <= 1'b1;   // idle high -- reset to the idle level, not 0,
      ps2_clk_sync_q <= 1'b1;   // or reset release looks like a falling edge
      ps2_clk_prev_q <= 1'b1;
      ps2_dat_meta_q <= 1'b1;
      ps2_dat_sync_q <= 1'b1;
    end else begin
      ps2_clk_meta_q <= ps2_clk;
      ps2_clk_sync_q <= ps2_clk_meta_q;
      ps2_clk_prev_q <= ps2_clk_sync_q;
      ps2_dat_meta_q <= ps2_dat;
      ps2_dat_sync_q <= ps2_dat_meta_q;
    end
  end

  // The device presents data around the falling edge of its clock.
  logic ps2_clk_falling;
  assign ps2_clk_falling = ps2_clk_prev_q && !ps2_clk_sync_q;

  // ── Stage 2: frame assembly ────────────────────────────────────────────
  // bit_cnt 0 = start, 1..8 = data (LSB first), 9 = parity, 10 = stop.
  logic [3:0] bit_cnt_q;
  logic [7:0] shift_q;
  logic       parity_q;      // parity bit as received
  logic       start_ok_q;    // start bit was 0

  logic [$clog2(TIMEOUT_CYCLES+1)-1:0] idle_q;

  logic in_frame;
  assign in_frame = (bit_cnt_q != 4'd0);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      bit_cnt_q   <= '0;
      shift_q     <= '0;
      parity_q    <= 1'b0;
      start_ok_q  <= 1'b0;
      idle_q      <= '0;
      byte_valid  <= 1'b0;
      byte_data   <= '0;
      err_parity  <= 1'b0;
      err_framing <= 1'b0;
      err_timeout <= 1'b0;
    end else begin
      // All outputs are single-cycle pulses.
      byte_valid  <= 1'b0;
      err_parity  <= 1'b0;
      err_framing <= 1'b0;
      err_timeout <= 1'b0;

      // Inactivity watchdog. Only armed mid-frame: an idle bus between
      // frames is normal and must not raise an error.
      if (ps2_clk_falling) begin
        idle_q <= '0;
      end else if (in_frame) begin
        if (idle_q == TIMEOUT_CYCLES[$bits(idle_q)-1:0]) begin
          bit_cnt_q   <= '0;      // abandon the partial frame and resynchronise
          idle_q      <= '0;
          err_timeout <= 1'b1;
        end else begin
          idle_q <= idle_q + 1'b1;
        end
      end

      if (ps2_clk_falling) begin
        unique case (bit_cnt_q)
          4'd0: begin
            // Start bit. A high here is line noise or a mid-frame resync,
            // not the opening of a frame -- stay put rather than shifting
            // garbage into the register.
            if (ps2_dat_sync_q == 1'b0) begin
              start_ok_q <= 1'b1;
              bit_cnt_q  <= 4'd1;
            end
          end
          4'd9: begin              // parity bit
            parity_q  <= ps2_dat_sync_q;
            bit_cnt_q <= 4'd10;
          end
          4'd10: begin             // stop bit -- frame ends either way
            bit_cnt_q <= 4'd0;
            if (ps2_dat_sync_q != 1'b1 || !start_ok_q) begin
              err_framing <= 1'b1;
            end else if ((^{shift_q, parity_q}) != 1'b1) begin
              // Odd parity: data bits and the parity bit together must
              // contain an odd number of ones, so their XOR must be 1.
              err_parity <= 1'b1;
            end else begin
              byte_data  <= shift_q;
              byte_valid <= 1'b1;
            end
          end
          default: begin           // data bits 1..8, LSB first
            shift_q   <= {ps2_dat_sync_q, shift_q[7:1]};
            bit_cnt_q <= bit_cnt_q + 4'd1;
          end
        endcase
      end
    end
  end

endmodule

What hardware this implies. Five flops of synchroniser and edge detect, an 8-bit shift register, a 4-bit bit counter, a timeout counter, and a handful of status flops. It is a small block, and deliberately so — the interesting content is in which five flops, not in how many.

Why the structure exists. The synchroniser exists for §4's reason and nothing else. The falling-edge detector exists because the bus clock is a foreign signal whose level tells you nothing about when. The reset values are 1'b1 rather than '0 because the bus idles high, and resetting the synchroniser chain to zero would manufacture a false falling edge on the first cycle after reset release — a subtle bug that produces a spurious start bit on every reset. Shifting in at the top ({ps2_dat_sync_q, shift_q[7:1]}) implements least-significant-first assembly without a separate index. The timeout exists because a device that stops mid-frame, or a glitch that fakes a start bit, would otherwise leave the receiver waiting forever for bits that are not coming.

What it assumes. That two synchroniser stages give adequate MTBF at this clock — an assumption to be checked against the actual period and library, not a universal truth. That TIMEOUT_CYCLES comfortably exceeds the longest legal bit period, since setting it too low turns a slow-but-legal device into a stream of spurious timeouts. And that a single bit-time of synchroniser latency is irrelevant here, which it is for a keyboard and would not be everywhere.

What a waveform would show. Inside the host, ps2_clk_sync_q lagging the bus clock by two host cycles, ps2_clk_falling as a one-cycle pulse per bit, bit_cnt_q walking 0 → 10, and a single-cycle byte_valid pulse one detected edge after the stop bit.

What it intentionally omits. The entire host-to-device path, scan-code and mouse-packet meaning, the open-drain pad and its electrical behaviour, and every numeric timing figure. The header says so, and the reason it says so is that a receiver like this is exactly the kind of code that gets copied into a real project — and the person copying it needs to know what they are not getting.

6. What a Verification Engineer Owns Here

Two things about this block make it a good teaching case for DV: some of its most important properties cannot be verified in RTL simulation, and the rest are unusually crisp.

Start with the honest limitation. Metastability does not exist in RTL simulation. A simulator resolves every signal to a definite value at every edge, so a testbench that drives ps2_clk from an unrelated clock will happily exercise the design and never once produce the failure the synchroniser prevents. The synchroniser's correctness is therefore established by structure and review, supported by CDC analysis tooling, not by passing tests. Writing a test that "proves the synchroniser works" is writing a test that proves nothing.

What simulation can own is everything above that boundary.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Representative assertions for ps2_rx.
//
// Classification: TEACHING ASSERTIONS for this example's frame handling.
// They deliberately assert NOTHING about metastability, which simulation
// cannot exhibit -- see the discussion above.
// ─────────────────────────────────────────────────────────────────────────

// P1 -- the results are mutually exclusive. A frame yields at most one of
// a good byte, a parity error, a framing error or a timeout. Overlap means
// two paths fired for one frame, and a scoreboard downstream would double
// count.
property p_result_onehot;
  @(posedge clk) disable iff (!rst_n)
    $onehot0({byte_valid, err_parity, err_framing, err_timeout});
endproperty
assert property (p_result_onehot);

// P2 -- byte_valid is a single-cycle pulse. A consumer that samples a level
// would otherwise read one byte many times; this is the classic pulse-vs-
// level integration bug, and it belongs to whoever produces the signal.
property p_byte_valid_is_pulse;
  @(posedge clk) disable iff (!rst_n)
    byte_valid |=> !byte_valid;
endproperty
assert property (p_byte_valid_is_pulse);

// P3 -- the bit counter never leaves its legal range. Reaching an
// out-of-range count would mean the frame FSM had lost its place, which is
// precisely the symptom an unsynchronised input produces on real silicon.
property p_bitcnt_in_range;
  @(posedge clk) disable iff (!rst_n)
    bit_cnt_q <= 4'd10;
endproperty
assert property (p_bitcnt_in_range);

// P4 -- a good byte is only ever announced at the end of a frame that
// actually began with a valid start bit.
property p_valid_implies_started;
  @(posedge clk) disable iff (!rst_n)
    byte_valid |-> $past(start_ok_q);
endproperty
assert property (p_valid_implies_started);

// P5 -- the timeout must return the receiver to the idle frame position,
// or an abandoned frame leaves the counter mid-frame and the NEXT frame is
// decoded against the wrong bit positions.
property p_timeout_resyncs;
  @(posedge clk) disable iff (!rst_n)
    err_timeout |-> (bit_cnt_q == 4'd0);
endproperty
assert property (p_timeout_resyncs);

// P6 -- the receiver never advances on anything but a detected falling
// edge of the SYNCHRONISED clock, or an abandoning timeout. This is the
// structural check that the raw bus input has not crept back into the
// control path during a later edit -- a review property as much as a
// simulation one. Note the asymmetry: ps2_clk_falling is combinational and
// so is sampled with $past, while err_timeout is REGISTERED and is
// therefore asserted in the same cycle the counter clears.
property p_advance_only_on_edge;
  @(posedge clk) disable iff (!rst_n)
    $changed(bit_cnt_q) |-> ($past(ps2_clk_falling) || err_timeout);
endproperty
assert property (p_advance_only_on_edge);

Representative coverage dimensions — axes worth watching, not a verification plan:

  • a clean frame; consecutive back-to-back frames with minimal gap; long idle between frames
  • parity failure, and specifically both parities of data byte so odd-parity logic is exercised in each direction
  • a bad stop bit, and a false start bit (a high sampled at bit position 0)
  • a frame truncated mid-way, triggering the timeout, followed by a good frame — the resynchronisation case P5 protects
  • reset asserted in each bit position, particularly mid-frame
  • data bytes of 8'h00 and 8'hFF, which sit at the extremes of the parity calculation
  • a device clock at the fast and slow ends of the legal range, against a fixed host clock

The error injections that actually teach something: a glitch on the clock line producing an extra falling edge mid-frame; a device that stops clocking after five bits; a parity bit inverted; and — the one that separates a correct receiver from a lucky one — a device whose bit period approaches the host's sampling resolution.

7. A Failure, Traced

An FPGA design reads a PS/2 keyboard. It works on the bench for hours. In the field, occasionally, the keyboard stops responding until the board is reset. No test reproduces it.

The wrong mental model. "The PS/2 clock is a slow signal and my system clock is much faster, so I can sample it directly." Speed ratio is irrelevant. A signal unrelated to your clock can change inside any flip-flop's aperture no matter how slow it is; the ratio changes how often, not whether.

The implementation mistake. ps2_clk used directly — in an always_ff @(negedge ps2_clk) block, or in a condition that advances bit_cnt_q from the raw input.

The symptom. Rare, unreproducible lockup. A metastable sample resolved differently for different bits of the bit counter, which landed on a value the case does not handle, and the receiver stopped advancing. Everything else in the system is fine, which is what makes it so hard to localise.

The debugging clue. It never reproduces in simulation — not because the test is weak, but because the simulator cannot represent the failure mechanism. It correlates with ambient conditions and board-to-board variation rather than with any input sequence. And CDC analysis reports the path immediately, which is why such tooling exists at all.

The correct model. Every signal entering from outside the clock domain crosses a boundary and must be synchronised before any logic uses it, regardless of how slow it is or how well it seems to work. And because simulation cannot show you this class of defect, the mitigation has to be structural — enforced by review and by tooling, not discovered by testing.

8. Why This Matters to a Semiconductor Engineer

This chapter is the one an RTL or FPGA engineer will actually reuse, and the reasons generalise past keyboards.

Domain crossings are defined by signal origin, not by signal speed. A slow button, a foreign interface clock, a reset from another block, a status bit from a different power domain — all cross, all need treatment. The habit worth forming is to ask of every module input: what clock produced this?

Some correctness is structural, not testable. The synchroniser is the clearest example in this curriculum of a property that passing tests can never establish. Recognising which of your guarantees live in that category — and defending those by construction, review and tooling — is a mark of engineering maturity.

Reset values are part of the design. The synchroniser resets to the bus's idle level specifically so reset release does not fabricate an edge. That kind of detail is invisible in a block diagram and decides whether a device works on the first cycle after reset.

Recovery is a feature. The timeout exists because real buses glitch and real devices are unplugged mid-frame. A receiver with no way back to a known state is a receiver that eventually needs a power cycle — which is precisely the user-visible symptom §7 describes.

9. Why This Never Became a Universal Peripheral Architecture

The dedicated input port is a genuinely good design for its job. Two wires, cheap logic at both ends, and enough bandwidth for a human's fingers with room to spare. Ask, as in the previous two chapters, why it could not generalise.

Its simplification is its ceiling. Everything that makes it cheap comes from knowing the device in advance. Remove that assumption and the interface has nothing left with which to identify anything — no identity field, no capability report, not even a way to ask.

One device, one port, decided at manufacture. There is no addressing and no topology, so capacity is fixed in hardware.

Device-supplied clock does not scale. Handing timing to the peripheral works when the peripheral is one known, slow device. As a general mechanism it gives the host no control over bus scheduling at all — and a host serving many devices must be able to decide who talks and when, which is an argument Chapter 2 develops properly.

Attachment is assumed, not handled. The device is expected to be present at start-up; there is no defined arrival event.

Its traffic model suits one class. Small, sporadic, latency-sensitive messages are exactly right for input and wrong for bulk transfer.

The pattern across 1.3, 1.4 and 1.5 should now be unmistakable. Three interfaces, three different and locally sensible sets of assumptions, and each one bought its simplicity by fixing something a general architecture would have to leave open. RS-232 fixed the meaning-agreement outside the link. The parallel port fixed the device class into its vocabulary. PS/2 fixed the device identity into the port itself. None of them is wrong. None of them composes.

10. Common Misconceptions

11. Reason It Through

A receiver is built without the ps2_clk_prev_q flop. Instead of detecting a falling edge, it advances the frame state whenever the synchronised clock is low: if (!ps2_clk_sync_q) begin ... end. The input is properly synchronised, so metastability is handled. What still goes wrong?

What is right about it? The crossing genuinely is handled — the raw input is never used, and the metastability argument of §4 is satisfied.

What is wrong? The receiver now advances on a level, and the bus clock is low for many host clock cycles per bit. A single bit period therefore shifts the same data bit in repeatedly, and the bit counter races to its terminal value within one bit time. The frame decodes as garbage and every subsequent frame is misaligned.

What would you see? bit_cnt_q sweeping 0 → 10 far faster than bits arrive; framing errors on essentially every frame; and a byte stream bearing no relationship to keys pressed. Unlike §7's defect, this one fails immediately and reproducibly — it is a logic error, not a physical one.

What is the principle? Synchronising a foreign signal and interpreting it are two separate obligations. The synchroniser makes the value safe to look at; it says nothing about when to act on it. A foreign clock carries its information in its transitions, so the consumer must detect edges — which is the same conclusion Chapter 1.4 reached about the acknowledge pulse, arrived at from the opposite direction.

12. Understanding Check

13. Summary

The dedicated input port inverts the assumption the previous two chapters shared: the device generates the clock, in both directions, so the host receives on timing it does not control. Two shared open-drain lines carry everything, with a pull-up defining the idle level and either end able to pull low — which is what gives the host its one lever, holding the clock low to inhibit the device.

A device-to-host frame is a start bit of 0, eight data bits least-significant first, an odd parity bit, and a stop bit of 1, with each bit captured around the clock's falling edge.

The engineering weight of the chapter is in what receiving on a foreign clock requires. Signals arriving from another clock domain can change inside a flip-flop's aperture and go metastable, and an unsynchronised input feeding a state machine can leave it in a state the encoding never defined. A two-flop synchroniser is the standard mitigation and is probabilistic rather than proven; it costs latency, and it guarantees nothing about pulses narrower than the sampling period. Crucially, RTL simulation cannot exhibit this failure at all, so the guarantee is structural — defended by design pattern, review and CDC analysis rather than by passing tests. The teaching receiver adds the details that decide whether such a block works in practice: reset to the bus's idle level so reset release does not fabricate an edge, decode on the edge of the synchronised clock rather than its level, validate start, parity and stop rather than assuming them, and include a timeout so an abandoned frame resynchronises instead of wedging the receiver.

And the interface could not generalise, for the same structural reason as its neighbours: everything that makes it cheap follows from knowing the device in advance. Three chapters, three locally excellent interfaces, three different assumptions baked in — and no way to compose them.

14. What Comes Next

Module 1 has now built its evidence. Chapter 1.1 argued that peripheral connectivity had become a system-level architecture problem; 1.2 showed the landscape as a set of independent attachment stacks; and 1.3, 1.4 and 1.5 went down three of those stacks in detail, each revealing a different assumption fixed inside the interface.

Chapter 1.6 turns the evidence into requirements. Rather than asking what was wrong — which 1.1 already answered in the abstract — it asks the engineering question the last three chapters have earned: given that each of these interfaces bought its simplicity by fixing something, what must an architecture leave open in order to serve peripheral classes that do not exist yet, and what does it have to provide in their place?

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.