# Implementing a USB 1.0 Host Controller in SpinalHDL for RISC-V SoC
Developing a synthesizable system-on-chip based on RISC-V requires integrating modern peripheral interfaces. A USB 1.0 host controller enables connecting HID input devices: keyboards, mice, gamepads. Implementing it in SpinalHDL provides hardware protocol handling without the software compromises of PS/2 or analog joysticks.
The USB physical layer uses a differential pair D+ and D-, along with VBUS and GND lines. The half-duplex transmission mode distinguishes USB from UART: data is sent bit by bit in one direction at a time, with strict host and device role separation.
USB 1.0 Physical Layer
Electrical characteristics define signal levels: the idle state on D+ and D- sets the speed. Low-speed (1.5 Mbps) — D- high, full-speed (12 Mbps) — D+ high. NRZI encoding: signal transition encodes '1', absence encodes '0'. Synchronization via bit stuffing: an inverted bit is inserted after six identical bits.
Control signals:
- SE0 (Single Ended Zero): both D+/D- low — for bus reset.
- J/K states: differential levels for idle and speed.
- Chirp: K/J exchange to detect hub speed.
Transmission starts with SYNC (8 bits '0'), followed by PID (Packet ID).
USB Protocol: Packets and Transactions
PID defines the packet type:
- Tokens: SETUP (control requests), IN (read), OUT (write), SOF (start of frame).
- Data: DATA0/1 (toggle bits for integrity).
- Handshake: ACK (acknowledgment), NAK (reject), STALL (error).
Transactions:
- Control Transfer: SETUP + DATA (optional) + IN/OUT + ACK.
- Interrupt Transfer: IN/OUT + DATA + ACK — for HID reports.
- Bulk Transfer: large blocks, with retry.
- Isochronous: no handshake, for streams.
Checksums: CRC5 for tokens, CRC16 for data. Formulas:
// CRC5 for address/kontsa
crc5_next = crc5_reg ^ data_in;
// Polinom: x^5 + x^2 + 1 (0b001101)
Descriptors and Device Initialization
Descriptors describe the device:
- Device Descriptor: bLength=18, bDescriptorType=1, idVendor, idProduct.
- Configuration Descriptor: bConfigurationValue, maxPower.
- Interface/Endpoint: bInterfaceClass (HID=3), bEndpointAddress, bmAttributes (interrupt).
- HID Descriptor: bcdHID, bCountryCode, Report Descriptor.
Initialization:
- Bus Reset (SE0 >10 μs).
- GET_DESCRIPTOR (Device).
- SET_ADDRESS.
- GET_DESCRIPTOR (Configuration).
- SET_CONFIGURATION.
For HID, add GET_DESCRIPTOR (Report). Keyboard report example: modifier byte + reserved + 6 keycodes.
Hardware Implementation in SpinalHDL
The controller is built as a hierarchy of finite state machines:
- USBMain: main FSM, manages the sequence.
- USBSendSE0: SE0 generation for reset.
- USBSendShortToken: short tokens (SOF).
- USBSendLongToken: full tokens (addr+endp).
- USBSendData: NRZI encoding + bit stuffing.
- USBReceiver: NRZI decoding, CRC check.
Interfaces:
import spinal.core._
case class USB_IO() extends Bundle {
val dpp = in Bool()
val dpn = in Bool()
val txen = out Bool()
val txdp = out Bool()
val txdn = out Bool()
}
Apb3USB10Ctrl integrates into the SoC via APB3. Clocking: 48 MHz for full-speed.
Integration into KarnixSoC and Driver
Connection: USB Type A on the Karna board, signals routed from FPGA. SpinalHDL configuration adds the module to the top level.
C driver for RISC-V:
- usb10_bus_reset(): SE0 for 20 μs.
- usb10_in_request(): IN token + DATA1 + ACK.
- usb10_setup_request(): SETUP + DATA + IN.
Initialization: usb10_init() detects the device, reads descriptors, sets address.
Testing with HID Devices
Application karnix_usb10_test:
- Gamepad: analog axes, buttons in interrupt endpoint 1.
- Mouse: X/Y delta, buttons in 4-byte reports.
- Keyboard: boot protocol, 8-byte reports (modifiers + keys).
Integration into TetRISC-V: replacing PS/2 with USB input.
Key Points:
- USB 1.0 full-speed (12 Mbps) implemented with FSM without MACRO-IP.
- SpinalHDL generates RTL for FPGA synthesis.
- HID boot protocol simplifies drivers for keyboard/mouse.
- NRZI + bit stuffing require precise timing (48 MHz clk).
- CRC5/16 verification prevents packet errors.
— Editorial Team
No comments yet.