From 22ab9efa761d8ecfdb617aad4491eb57851b72e5 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 22 Oct 2024 14:17:33 +0200 Subject: [PATCH] docs: add gateware usb device tutorial --- .../tutorials/gateware-usb-device-01.py | 69 +++ .../tutorials/gateware-usb-device-02.py | 102 +++++ .../tutorials/gateware-usb-device-03.py | 177 ++++++++ .../tutorials/gateware-usb-device-04.py | 272 ++++++++++++ .../tutorials/test-gateware-usb-device-01.py | 19 + .../tutorials/test-gateware-usb-device-02.py | 19 + .../tutorials/test-gateware-usb-device-03.py | 87 ++++ .../tutorials/test-gateware-usb-device-04.py | 128 ++++++ .../with_wcid.png | Bin 0 -> 68115 bytes .../without_wcid.png | Bin 0 -> 70803 bytes .../tutorials/gateware_usb_device_01.rst | 367 ++++++++++++++++ .../tutorials/gateware_usb_device_02.rst | 397 ++++++++++++++++++ .../tutorials/gateware_usb_device_03.rst | 306 ++++++++++++++ .../tutorials/gateware_usb_device_04.rst | 386 +++++++++++++++++ 14 files changed, 2329 insertions(+) create mode 100644 cynthion/python/examples/tutorials/gateware-usb-device-01.py create mode 100644 cynthion/python/examples/tutorials/gateware-usb-device-02.py create mode 100644 cynthion/python/examples/tutorials/gateware-usb-device-03.py create mode 100644 cynthion/python/examples/tutorials/gateware-usb-device-04.py create mode 100644 cynthion/python/examples/tutorials/test-gateware-usb-device-01.py create mode 100644 cynthion/python/examples/tutorials/test-gateware-usb-device-02.py create mode 100644 cynthion/python/examples/tutorials/test-gateware-usb-device-03.py create mode 100644 cynthion/python/examples/tutorials/test-gateware-usb-device-04.py create mode 100644 docs/images/tutorial_gateware_usb_device/with_wcid.png create mode 100644 docs/images/tutorial_gateware_usb_device/without_wcid.png create mode 100644 docs/source/tutorials/gateware_usb_device_01.rst create mode 100644 docs/source/tutorials/gateware_usb_device_02.rst create mode 100644 docs/source/tutorials/gateware_usb_device_03.rst create mode 100644 docs/source/tutorials/gateware_usb_device_04.rst diff --git a/cynthion/python/examples/tutorials/gateware-usb-device-01.py b/cynthion/python/examples/tutorials/gateware-usb-device-01.py new file mode 100644 index 00000000..79031493 --- /dev/null +++ b/cynthion/python/examples/tutorials/gateware-usb-device-01.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# This file is part of Cynthion. +# +# Copyright (c) 2024 Great Scott Gadgets +# SPDX-License-Identifier: BSD-3-Clause + +from amaranth import * +from luna.usb2 import USBDevice +from usb_protocol.emitters import DeviceDescriptorCollection + +VENDOR_ID = 0x1209 # https://pid.codes/1209/ +PRODUCT_ID = 0x0001 + +class GatewareUSBDevice(Elaboratable): + """ A simple USB device that can only enumerate. """ + + def create_standard_descriptors(self): + """ Create the USB descriptors for the device. """ + + descriptors = DeviceDescriptorCollection() + + # all USB devices have a single device descriptor + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + + d.bNumConfigurations = 1 + + # and at least one configuration descriptor + with descriptors.ConfigurationDescriptor() as c: + + # with at least one interface descriptor + with c.InterfaceDescriptor() as i: + i.bInterfaceNumber = 0 + + # interfaces also need endpoints to do anything useful + # but we'll add those later! + + return descriptors + + + def elaborate(self, platform): + m = Module() + + # configure cynthion's clocks and reset signals + m.submodules.car = platform.clock_domain_generator() + + # request the physical interface for cynthion's TARGET C port + ulpi = platform.request("target_phy") + + # create the USB device + m.submodules.usb = usb = USBDevice(bus=ulpi) + + # create our standard descriptors and add them to the device's control endpoint + descriptors = self.create_standard_descriptors() + control_endpoint = usb.add_standard_control_endpoint(descriptors) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + + +if __name__ == "__main__": + from luna import top_level_cli + top_level_cli(GatewareUSBDevice) diff --git a/cynthion/python/examples/tutorials/gateware-usb-device-02.py b/cynthion/python/examples/tutorials/gateware-usb-device-02.py new file mode 100644 index 00000000..65d6af25 --- /dev/null +++ b/cynthion/python/examples/tutorials/gateware-usb-device-02.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# This file is part of Cynthion. +# +# Copyright (c) 2024 Great Scott Gadgets +# SPDX-License-Identifier: BSD-3-Clause + +from amaranth import * +from luna.usb2 import USBDevice +from usb_protocol.emitters import DeviceDescriptorCollection + +from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + MicrosoftOS10RequestHandler, +) +from usb_protocol.emitters.descriptors.standard import get_string_descriptor +from usb_protocol.types.descriptors.microsoft10 import RegistryTypes + +VENDOR_ID = 0x1209 # https://pid.codes/1209/ +PRODUCT_ID = 0x0001 + +class GatewareUSBDevice(Elaboratable): + """ A simple USB device that can also enumerate on Windows. """ + + def create_standard_descriptors(self): + """ Create the USB descriptors for the device. """ + + descriptors = DeviceDescriptorCollection() + + # all USB devices have a single device descriptor + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + + d.bNumConfigurations = 1 + + # and at least one configuration descriptor + with descriptors.ConfigurationDescriptor() as c: + + # with at least one interface descriptor + with c.InterfaceDescriptor() as i: + i.bInterfaceNumber = 0 + + # interfaces also need endpoints to do anything useful + # but we'll add those later! + + return descriptors + + + def elaborate(self, platform): + m = Module() + + # configure cynthion's clocks and reset signals + m.submodules.car = platform.clock_domain_generator() + + # request the physical interface for cynthion's TARGET C port + ulpi = platform.request("target_phy") + + # create the USB device + m.submodules.usb = usb = USBDevice(bus=ulpi) + + # create our standard descriptors and add them to the device's control endpoint + descriptors = self.create_standard_descriptors() + control_endpoint = usb.add_standard_control_endpoint( + descriptors, + avoid_blockram=True # allow dynamic string descriptors + ) + + # add the microsoft os string descriptor + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + + # add a microsoft descriptor collection for our other two microsoft descriptors + msft_descriptors = MicrosoftOS10DescriptorCollection() + + # add the microsoft compatible id feature descriptor + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + + # add microsoft extended properties feature descriptor + with msft_descriptors.ExtendedPropertiesDescriptor() as d: + with d.Property() as p: + p.dwPropertyDataType = RegistryTypes.REG_SZ + p.PropertyName = "DeviceInterfaceGUID" + p.PropertyData = "{88bae032-5a81-49f0-bc3d-a4ff138216d6}" + + # add the request handler for Microsoft descriptors + msft_handler = MicrosoftOS10RequestHandler(msft_descriptors, request_code=0xee) + control_endpoint.add_request_handler(msft_handler) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + + +if __name__ == "__main__": + from luna import top_level_cli + top_level_cli(GatewareUSBDevice) diff --git a/cynthion/python/examples/tutorials/gateware-usb-device-03.py b/cynthion/python/examples/tutorials/gateware-usb-device-03.py new file mode 100644 index 00000000..861fbc27 --- /dev/null +++ b/cynthion/python/examples/tutorials/gateware-usb-device-03.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# +# This file is part of Cynthion. +# +# Copyright (c) 2024 Great Scott Gadgets +# SPDX-License-Identifier: BSD-3-Clause + +from amaranth import * +from luna.usb2 import USBDevice +from usb_protocol.emitters import DeviceDescriptorCollection + +from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + MicrosoftOS10RequestHandler, +) +from usb_protocol.emitters.descriptors.standard import get_string_descriptor +from usb_protocol.types.descriptors.microsoft10 import RegistryTypes + +from luna.gateware.stream.generator import StreamSerializer +from luna.gateware.usb.request.control import ControlRequestHandler +from luna.gateware.usb.request.interface import SetupPacket +from luna.gateware.usb.usb2.request import RequestHandlerInterface +from luna.gateware.usb.usb2.transfer import USBInStreamInterface +from usb_protocol.types import USBRequestType + +VENDOR_ID = 0x1209 # https://pid.codes/1209/ +PRODUCT_ID = 0x0001 + +class VendorRequestHandler(ControlRequestHandler): + VENDOR_SET_FPGA_LEDS = 0x01 + VENDOR_GET_USER_BUTTON = 0x02 + + def elaborate(self, platform): + m = Module() + + # shortcuts + interface: RequestHandlerInterface = self.interface + setup: SetupPacket = self.interface.setup + + # get a reference to the FPGA LEDs and USER button + fpga_leds = Cat(platform.request("led", i).o for i in range(6)) + user_button = platform.request("button_user").i + + # create a streamserializer for transmitting IN data back to the host + serializer = StreamSerializer( + domain = "usb", + stream_type = USBInStreamInterface, + data_length = 1, + max_length_width = 1, + ) + m.submodules += serializer + + # we've received a setup packet containing a vendor request. + with m.If(setup.type == USBRequestType.VENDOR): + # take ownership of the interface + m.d.comb += interface.claim.eq(1) + + # use a state machine to sequence our request handling + with m.FSM(domain="usb"): + with m.State("IDLE"): + with m.If(setup.received): + with m.Switch(setup.request): + with m.Case(self.VENDOR_SET_FPGA_LEDS): + m.next = "HANDLE_SET_FPGA_LEDS" + with m.Case(self.VENDOR_GET_USER_BUTTON): + m.next = "HANDLE_GET_USER_BUTTON" + with m.Default(): + m.next = "UNHANDLED" + + with m.State("UNHANDLED"): + # stall unhandled requests + with m.If(interface.status_requested | interface.data_requested): + m.d.comb += interface.handshakes_out.stall.eq(1) + m.next = "IDLE" + + with m.State("HANDLE_SET_FPGA_LEDS"): + # if we have an active data byte, set the FPGA LEDs to the payload + with m.If(interface.rx.valid & interface.rx.next): + m.d.usb += fpga_leds.eq(interface.rx.payload[0:6]) + + # once the receive is complete, respond with an ACK + with m.If(interface.rx_ready_for_response): + m.d.comb += interface.handshakes_out.ack.eq(1) + + # finally, once we reach the status stage, send a ZLP + with m.If(interface.status_requested): + m.d.comb += self.send_zlp() + m.next = "IDLE" + + with m.State("HANDLE_GET_USER_BUTTON"): + # write the state of the user button into a local data register + data = Signal(8) + m.d.comb += data[0].eq(user_button) + + # transmit our data using a built-in handler function that + # automatically advances the FSM back to the 'IDLE' state on + # completion + self.handle_simple_data_request(m, serializer, data) + + return m + + +class GatewareUSBDevice(Elaboratable): + """ A simple USB device that can communicate with the host via vendor requests. """ + + def create_standard_descriptors(self): + """ Create the USB descriptors for the device. """ + + descriptors = DeviceDescriptorCollection() + + # all USB devices have a single device descriptor + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + + d.bNumConfigurations = 1 + + # and at least one configuration descriptor + with descriptors.ConfigurationDescriptor() as c: + + # with at least one interface descriptor + with c.InterfaceDescriptor() as i: + i.bInterfaceNumber = 0 + + # interfaces also need endpoints to do anything useful + # but we'll add those later! + + return descriptors + + def elaborate(self, platform): + m = Module() + + # configure cynthion's clocks and reset signals + m.submodules.car = platform.clock_domain_generator() + + # request the physical interface for cynthion's TARGET C port + ulpi = platform.request("target_phy") + + # create the USB device + m.submodules.usb = usb = USBDevice(bus=ulpi) + + # create our standard descriptors and add them to the device's control endpoint + descriptors = self.create_standard_descriptors() + control_endpoint = usb.add_standard_control_endpoint( + descriptors, + avoid_blockram=True # allow dynamic string descriptors + ) + + # add microsoft os 1.0 descriptors and request handler + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + msft_descriptors = MicrosoftOS10DescriptorCollection() + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + with msft_descriptors.ExtendedPropertiesDescriptor() as d: + with d.Property() as p: + p.dwPropertyDataType = RegistryTypes.REG_SZ + p.PropertyName = "DeviceInterfaceGUID" + p.PropertyData = "{88bae032-5a81-49f0-bc3d-a4ff138216d6}" + msft_handler = MicrosoftOS10RequestHandler(msft_descriptors, request_code=0xee) + control_endpoint.add_request_handler(msft_handler) + + # add the vendor request handler + control_endpoint.add_request_handler(VendorRequestHandler()) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + + +if __name__ == "__main__": + from luna import top_level_cli + top_level_cli(GatewareUSBDevice) diff --git a/cynthion/python/examples/tutorials/gateware-usb-device-04.py b/cynthion/python/examples/tutorials/gateware-usb-device-04.py new file mode 100644 index 00000000..e2bc6052 --- /dev/null +++ b/cynthion/python/examples/tutorials/gateware-usb-device-04.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# +# This file is part of Cynthion. +# +# Copyright (c) 2024 Great Scott Gadgets +# SPDX-License-Identifier: BSD-3-Clause + +from amaranth import * +from luna.usb2 import USBDevice +from usb_protocol.emitters import DeviceDescriptorCollection + +from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + MicrosoftOS10RequestHandler, +) +from usb_protocol.emitters.descriptors.standard import get_string_descriptor +from usb_protocol.types.descriptors.microsoft10 import RegistryTypes + +from luna.gateware.stream.generator import StreamSerializer +from luna.gateware.usb.request.control import ControlRequestHandler +from luna.gateware.usb.request.interface import SetupPacket +from luna.gateware.usb.usb2.request import RequestHandlerInterface +from luna.gateware.usb.usb2.transfer import USBInStreamInterface +from usb_protocol.types import USBRequestType + +from luna.usb2 import USBStreamInEndpoint, USBStreamOutEndpoint +from luna.gateware.stream import StreamInterface +from usb_protocol.types import USBDirection, USBTransferType + +VENDOR_ID = 0x1209 # https://pid.codes/1209/ +PRODUCT_ID = 0x0001 + +MAX_PACKET_SIZE = 512 + +class VendorRequestHandler(ControlRequestHandler): + VENDOR_SET_FPGA_LEDS = 0x01 + VENDOR_GET_USER_BUTTON = 0x02 + + def elaborate(self, platform): + m = Module() + + # shortcuts + interface: RequestHandlerInterface = self.interface + setup: SetupPacket = self.interface.setup + + # get a reference to the FPGA LEDs and USER button + fpga_leds = Cat(platform.request("led", i).o for i in range(6)) + user_button = platform.request("button_user").i + + # create a streamserializer for transmitting IN data back to the host + serializer = StreamSerializer( + domain = "usb", + stream_type = USBInStreamInterface, + data_length = 1, + max_length_width = 1, + ) + m.submodules += serializer + + # we've received a setup packet containing a vendor request. + with m.If(setup.type == USBRequestType.VENDOR): + # take ownership of the interface + m.d.comb += interface.claim.eq(1) + + # use a state machine to sequence our request handling + with m.FSM(domain="usb"): + with m.State("IDLE"): + with m.If(setup.received): + with m.Switch(setup.request): + with m.Case(self.VENDOR_SET_FPGA_LEDS): + m.next = "HANDLE_SET_FPGA_LEDS" + with m.Case(self.VENDOR_GET_USER_BUTTON): + m.next = "HANDLE_GET_USER_BUTTON" + with m.Default(): + m.next = "UNHANDLED" + + with m.State("UNHANDLED"): + # stall unhandled requests + with m.If(interface.status_requested | interface.data_requested): + m.d.comb += interface.handshakes_out.stall.eq(1) + m.next = "IDLE" + + with m.State("HANDLE_SET_FPGA_LEDS"): + # if we have an active data byte, set the FPGA LEDs to the payload + with m.If(interface.rx.valid & interface.rx.next): + m.d.usb += fpga_leds.eq(interface.rx.payload[0:6]) + + # once the receive is complete, respond with an ACK + with m.If(interface.rx_ready_for_response): + m.d.comb += interface.handshakes_out.ack.eq(1) + + # finally, once we reach the status stage, send a ZLP + with m.If(interface.status_requested): + m.d.comb += self.send_zlp() + m.next = "IDLE" + + with m.State("HANDLE_GET_USER_BUTTON"): + # write the state of the user button into a local data register + data = Signal(8) + m.d.comb += data[0].eq(user_button) + + # transmit our data using a built-in handler function that + # automatically advances the FSM back to the 'IDLE' state on + # completion + self.handle_simple_data_request(m, serializer, data) + + return m + + +class StreamingMemoryStore(Elaboratable): + """ A simple memory storage module that exposes a read/write streaming interface. """ + + def __init__(self, stream_out: StreamInterface, stream_in: StreamInterface): + self.stream_out = stream_out + self.stream_in = stream_in + + # high when a memory write is in process + self.write_active = Signal() + + def elaborate(self, platform): + m = Module() + + # create a memory we will use as a data source/sink for our bulk endpoints + m.submodules.ram = ram = Memory( + width = 8, + depth = MAX_PACKET_SIZE, + init = [0] * MAX_PACKET_SIZE + ) + w_port = ram.write_port(domain="usb") + r_port = ram.read_port(domain="usb") + + # set the write_active status to the write port's enable status + m.d.comb += self.write_active.eq(w_port.en) + + # shortcuts + stream_out = self.stream_out + stream_in = self.stream_in + + # - EP 0x01 OUT logic ------------------------------------------------ + + # let the stream know we're always ready to start reading + m.d.comb += stream_out.ready.eq(1) + + # wire the payload from the host up to our memory write port + m.d.comb += w_port.data.eq(stream_out.payload) + + # read each byte coming in on the stream and write it to memory + with m.If(stream_out.valid & stream_out.ready): + m.d.comb += w_port.en.eq(1) + m.d.usb += w_port.addr.eq(w_port.addr + 1); + with m.Else(): + m.d.comb += w_port.en.eq(0) + m.d.usb += w_port.addr.eq(0) + + # - EP 0x82 IN logic ------------------------------------------------- + + # wire the payload to the host up to our memory read port + m.d.comb += stream_in.payload.eq(r_port.data) + + # when the stream is ready and the write port is not active, + # read each byte from memory and write it out to the stream + with m.If(stream_in.ready & ~w_port.en): + m.d.usb += stream_in.valid.eq(1) + m.d.usb += r_port.addr.eq(r_port.addr + 1) + with m.Else(): + m.d.usb += stream_in.valid.eq(0) + m.d.usb += r_port.addr.eq(0) + + return m + + +class GatewareUSBDevice(Elaboratable): + """ A simple USB device that can communicate with the host via vendor and bulk requests. """ + + def create_standard_descriptors(self): + """ Create the USB descriptors for the device. """ + + descriptors = DeviceDescriptorCollection() + + # all USB devices have a single device descriptor + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + + d.bNumConfigurations = 1 + + # and at least one configuration descriptor + with descriptors.ConfigurationDescriptor() as c: + + # with at least one interface descriptor + with c.InterfaceDescriptor() as i: + i.bInterfaceNumber = 0 + + # an endpoint for receiving bulk data from the host + with i.EndpointDescriptor() as e: + e.bEndpointAddress = USBDirection.OUT.to_endpoint_address(0x01) # EP 0x01 OUT + e.bmAttributes = USBTransferType.BULK + e.wMaxPacketSize = MAX_PACKET_SIZE + + # and an endpoint for transmitting bulk data to the host + with i.EndpointDescriptor() as e: + e.bEndpointAddress = USBDirection.IN.to_endpoint_address(0x02) # EP 0x82 IN + e.bmAttributes = USBTransferType.BULK + e.wMaxPacketSize = MAX_PACKET_SIZE + + return descriptors + + def elaborate(self, platform): + m = Module() + + # configure cynthion's clocks and reset signals + m.submodules.car = platform.clock_domain_generator() + + # request the physical interface for cynthion's TARGET C port + ulpi = platform.request("target_phy") + + # create the USB device + m.submodules.usb = usb = USBDevice(bus=ulpi) + + # create our standard descriptors and add them to the device's control endpoint + descriptors = self.create_standard_descriptors() + control_endpoint = usb.add_standard_control_endpoint( + descriptors, + avoid_blockram=True # allow dynamic string descriptors + ) + + # add microsoft os 1.0 descriptors and request handler + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + msft_descriptors = MicrosoftOS10DescriptorCollection() + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + with msft_descriptors.ExtendedPropertiesDescriptor() as d: + with d.Property() as p: + p.dwPropertyDataType = RegistryTypes.REG_SZ + p.PropertyName = "DeviceInterfaceGUID" + p.PropertyData = "{88bae032-5a81-49f0-bc3d-a4ff138216d6}" + msft_handler = MicrosoftOS10RequestHandler(msft_descriptors, request_code=0xee) + control_endpoint.add_request_handler(msft_handler) + + # add the vendor request handler + control_endpoint.add_request_handler(VendorRequestHandler()) + + # create and add stream endpoints for our device's Bulk IN & OUT endpoints + ep_out = USBStreamOutEndpoint( + endpoint_number=0x01, # (EP 0x01) + max_packet_size=MAX_PACKET_SIZE, + ) + usb.add_endpoint(ep_out) + ep_in = USBStreamInEndpoint( + endpoint_number=0x02, # (EP 0x82) + max_packet_size=MAX_PACKET_SIZE + ) + usb.add_endpoint(ep_in) + + # create a simple streaming memory storage module + m.submodules.store = store = StreamingMemoryStore(ep_out.stream, ep_in.stream) + + # invalidate any data queued on ep_in when the memory performs a write operation + m.d.comb += ep_in.discard.eq(store.write_active) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + + +if __name__ == "__main__": + from luna import top_level_cli + top_level_cli(GatewareUSBDevice) diff --git a/cynthion/python/examples/tutorials/test-gateware-usb-device-01.py b/cynthion/python/examples/tutorials/test-gateware-usb-device-01.py new file mode 100644 index 00000000..b3b25129 --- /dev/null +++ b/cynthion/python/examples/tutorials/test-gateware-usb-device-01.py @@ -0,0 +1,19 @@ +import usb1 + +# - list available usb devices ------------------------------------------------ + +def list_available_usb_devices(context): + for device in context.getDeviceList(): + try: + manufacturer = device.getManufacturer() + product = device.getProduct() + print(f"{device}: {manufacturer} - {product}") + except Exception as e: + print(f"{device}: {e}") + + +# - main ---------------------------------------------------------------------- + +if __name__ == "__main__": + with usb1.USBContext() as context: + list_available_usb_devices(context) diff --git a/cynthion/python/examples/tutorials/test-gateware-usb-device-02.py b/cynthion/python/examples/tutorials/test-gateware-usb-device-02.py new file mode 100644 index 00000000..b3b25129 --- /dev/null +++ b/cynthion/python/examples/tutorials/test-gateware-usb-device-02.py @@ -0,0 +1,19 @@ +import usb1 + +# - list available usb devices ------------------------------------------------ + +def list_available_usb_devices(context): + for device in context.getDeviceList(): + try: + manufacturer = device.getManufacturer() + product = device.getProduct() + print(f"{device}: {manufacturer} - {product}") + except Exception as e: + print(f"{device}: {e}") + + +# - main ---------------------------------------------------------------------- + +if __name__ == "__main__": + with usb1.USBContext() as context: + list_available_usb_devices(context) diff --git a/cynthion/python/examples/tutorials/test-gateware-usb-device-03.py b/cynthion/python/examples/tutorials/test-gateware-usb-device-03.py new file mode 100644 index 00000000..ecd8b850 --- /dev/null +++ b/cynthion/python/examples/tutorials/test-gateware-usb-device-03.py @@ -0,0 +1,87 @@ +import usb1 +import time + +VENDOR_ID = 0x1209 # https://pid.codes/1209/ +PRODUCT_ID = 0x0001 + +VENDOR_SET_FPGA_LEDS = 0x01 +VENDOR_GET_USER_BUTTON = 0x02 + +# - list available usb devices ------------------------------------------------ + +def list_available_usb_devices(context): + for device in context.getDeviceList(): + try: + manufacturer = device.getManufacturer() + product = device.getProduct() + print(f"{device}: {manufacturer} - {product}") + except Exception as e: + print(f"{device}: {e}") + + +# - wrappers for control requests --------------------------------------------- + +def set_fpga_leds(device_handle, led_state): + response = device_handle.controlWrite( + request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, + request = VENDOR_SET_FPGA_LEDS, + index = 0, + value = 0, + data = [led_state], + timeout = 1000, + ) + +def get_user_button(device_handle): + response = device_handle.controlRead( + request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE | usb1.ENDPOINT_OUT, + request = VENDOR_GET_USER_BUTTON, + index = 0, + value = 0, + length = 1, + timeout = 1000, + ) + return response[0] + + +# - test control endpoints ---------------------------------------------------- + +def test_control_endpoints(device_handle): + led_counter = 0 + last_button_state = False + + while True: + # led counter + set_fpga_leds(device_handle, led_counter) + led_counter = (led_counter + 1) % 256 + + # reset led counter when the USER button is pressed + button_state = get_user_button(device_handle) + if button_state: + led_counter = 0 + + # print button state when it changes + if button_state != last_button_state: + print(f"USER button is: {'ON' if button_state else 'OFF' }") + last_button_state = button_state + + # slow the loop down so we can see the counter change + time.sleep(0.1) + + +# - main ---------------------------------------------------------------------- + +if __name__ == "__main__": + with usb1.USBContext() as context: + # list available devices + list_available_usb_devices(context) + + # get a device handle to our simple usb device + device_handle = context.openByVendorIDAndProductID(VENDOR_ID, PRODUCT_ID) + if device_handle is None: + raise Exception("Device not found.") + + # claim the device's interface + device_handle.claimInterface(0) + + # pass the device handle to our control endpoint test + test_control_endpoints(device_handle) diff --git a/cynthion/python/examples/tutorials/test-gateware-usb-device-04.py b/cynthion/python/examples/tutorials/test-gateware-usb-device-04.py new file mode 100644 index 00000000..263a037d --- /dev/null +++ b/cynthion/python/examples/tutorials/test-gateware-usb-device-04.py @@ -0,0 +1,128 @@ +import usb1 +import time +import random + +VENDOR_ID = 0x1209 # https://pid.codes/1209/ +PRODUCT_ID = 0x0001 + +VENDOR_SET_FPGA_LEDS = 0x01 +VENDOR_GET_USER_BUTTON = 0x02 + +MAX_PACKET_SIZE = 512 + +# - list available usb devices ------------------------------------------------ + +def list_available_usb_devices(context): + for device in context.getDeviceList(): + try: + manufacturer = device.getManufacturer() + product = device.getProduct() + print(f"{device}: {manufacturer} - {product}") + except Exception as e: + print(f"{device}: {e}") + + +# - wrappers for control requests --------------------------------------------- + +def set_fpga_leds(device_handle, led_state): + response = device_handle.controlWrite( + request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, + request = VENDOR_SET_FPGA_LEDS, + index = 0, + value = 0, + data = [led_state], + timeout = 1000, + ) + +def get_user_button(device_handle): + response = device_handle.controlRead( + request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE | usb1.ENDPOINT_OUT, + request = VENDOR_GET_USER_BUTTON, + index = 0, + value = 0, + length = 1, + timeout = 1000, + ) + return response[0] + + +# - test control endpoints ---------------------------------------------------- + +def test_control_endpoints(device_handle): + led_counter = 0 + last_button_state = False + + while True: + # led counter + set_fpga_leds(device_handle, led_counter) + led_counter = (led_counter + 1) % 256 + + # reset led counter when the USER button is pressed + button_state = get_user_button(device_handle) + if button_state: + led_counter = 0 + + # print button state when it changes + if button_state != last_button_state: + print(f"USER button is: {'ON' if button_state else 'OFF' }") + last_button_state = button_state + + # slow the loop down so we can see the counter change + time.sleep(0.1) + + +# - wrappers for bulk requests ------------------------------------------------ + +def bulk_out_transfer(device_handle, data): + response = device_handle.bulkWrite( + endpoint = 0x01, + data = data, + timeout = 1000, + ) + return response + +def bulk_in_transfer(device_handle, length): + response = device_handle.bulkRead( + endpoint = 0x02, + length = length, + timeout = 1000, + ) + return response + + +# - test bulk endpoints ------------------------------------------------------- + +def test_bulk_endpoints(device_handle): + # bulk_out - write a list of random numbers to memory + data = list([random.randint(0, 255) for _ in range(MAX_PACKET_SIZE)]) + response = bulk_out_transfer(device_handle, data) + print(f"OUT endpoint transmitted {response} bytes: {data[0:4]} ... {data[-4:]}") + + # bulk_in - retrieve the contents of our memory + response = list(bulk_in_transfer(device_handle, MAX_PACKET_SIZE)) + print(f"IN endpoint received {len(response)} bytes: {response[0:4]} ... {response[-4:]}") + + # check that the stored data matches the sent data + assert(data == list(response)) + + +# - main ---------------------------------------------------------------------- + +if __name__ == "__main__": + with usb1.USBContext() as context: + # list available devices + list_available_usb_devices(context) + + # get a device handle to our simple usb device + device_handle = context.openByVendorIDAndProductID(VENDOR_ID, PRODUCT_ID) + if device_handle is None: + raise Exception("Device not found.") + + # claim the device's interface + device_handle.claimInterface(0) + + # pass the device handle to our bulk endpoint test + test_bulk_endpoints(device_handle) + + # pass the device handle to our control endpoint test + test_control_endpoints(device_handle) diff --git a/docs/images/tutorial_gateware_usb_device/with_wcid.png b/docs/images/tutorial_gateware_usb_device/with_wcid.png new file mode 100644 index 0000000000000000000000000000000000000000..7d719f17a49d322a71f3c443cf7e017f902287be GIT binary patch literal 68115 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 literal 0 HcmV?d00001 diff --git a/docs/images/tutorial_gateware_usb_device/without_wcid.png b/docs/images/tutorial_gateware_usb_device/without_wcid.png new file mode 100644 index 0000000000000000000000000000000000000000..c78f56afc716509e96f378b834142c63481342d4 GIT binary patch literal 70803 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 z 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: + +* :doc:`/tutorials/gateware_usb_device_01` *(This tutorial)* +* :doc:`/tutorials/gateware_usb_device_02` +* :doc:`/tutorials/gateware_usb_device_03` +* :doc:`/tutorials/gateware_usb_device_04` + +The goal of this tutorial is to create a gateware design for the simplest USB Device that can still be enumerated by a host. + + +Prerequisites +============= + + * Install the Cynthion tools by following :doc:`/getting_started`. + * Complete the :doc:`/tutorials/gateware_blinky` tutorial. + + +Define a USB Device +=================== + +USB devices are defined using a hierarchy of descriptors that contain information such as: + +* The product name and serial number. +* The vendor who made it. +* The class of device it is. +* The ways in which it can be configured. +* The number and types of endpoints it has. + +At the root of this hierarchy lies the *Device Descriptor* and a device can only have one. + +Create the Device Descriptor +---------------------------- + +Create a new file called ``gateware-usb-device.py`` and add the following code to it: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + + from amaranth import * + from usb_protocol.emitters import DeviceDescriptorCollection + + VENDOR_ID = 0x1209 # https://pid.codes/1209/ + PRODUCT_ID = 0x0001 + + class GatewareUSBDevice(Elaboratable): + def create_descriptors(self): + descriptors = DeviceDescriptorCollection() + + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + d.bNumConfigurations = 1 + + return descriptors + + def elaborate(self, platform): + m = Module() + return m + +We have now created a minimal device descriptor with a `vendor id `__, product id, a manufacturer, a product description and one *Configuration Descriptor*. + +USB devices can have multiple configurations but only one can be active at a time. This allows a USB device to be configured differently depending on the situtation. For example, a device might be configured differently if it's bus-powered vs self-powered. + + +Create the Configuration Descriptor +----------------------------------- + +Next, add a configuration descriptor for our device by adding the highlighted lines: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 18-20 + + from amaranth import * + from usb_protocol.emitters import DeviceDescriptorCollection + + VENDOR_ID = 0x1209 # https://pid.codes/1209/ + PRODUCT_ID = 0x0001 + + class GatewareUSBDevice(Elaboratable): + def create_descriptors(self): + descriptors = DeviceDescriptorCollection() + + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + d.bNumConfigurations = 1 + + with descriptors.ConfigurationDescriptor() as c: + with c.InterfaceDescriptor() as i: + i.bInterfaceNumber = 0 + + return descriptors + + def elaborate(self, platform): + m = Module() + return m + +We have now created the descriptors for a device with a single configuration descriptor and one interface descriptor with no endpoints. (We'll add some endpoints later!) + +.. note:: + + Each USB Configuration can have multiple interface descriptors and + they can all be active at the same time. This allows a USB device + to create functional groups that are each responsible for a single + function of the device. For example, a USB Audio Interface may have + one interface descriptor with two endpoints for audio input/output + and another interface descriptor with one endpoint for MIDI input. + + +Create Device Gateware +---------------------- + +Now that we have defined our device's descriptors we need to create the interface between our device's physical USB port and the gateware that implements the device's function(s). Fortunately the LUNA library takes care of all the hard work for us and we only need to add the following lines: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 28-40, 44- + + from amaranth import * + from luna.usb2 import USBDevice + from usb_protocol.emitters import DeviceDescriptorCollection + + VENDOR_ID = 0x1209 # https://pid.codes/1209/ + PRODUCT_ID = 0x0001 + + class GatewareUSBDevice(Elaboratable): + def create_descriptors(self): + descriptors = DeviceDescriptorCollection() + + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + d.bNumConfigurations = 1 + + with descriptors.ConfigurationDescriptor() as c: + with c.InterfaceDescriptor() as i: + i.bInterfaceNumber = 0 + + return descriptors + + def elaborate(self, platform): + m = Module() + + # configure cynthion's clocks and reset signals + m.submodules.car = platform.clock_domain_generator() + + # request the physical interface for cynthion's TARGET C port + ulpi = platform.request("target_phy") + m.submodules.usb = usb = USBDevice(bus=ulpi) + + # create our descriptors and add them to the device's control endpoint + descriptors = self.create_descriptors() + control_ep = usb.add_standard_control_endpoint(descriptors) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + + if __name__ == "__main__": + from luna import top_level_cli + top_level_cli(GatewareUSBDevice) + + +Testing the Device +================== + +Connect +------- + +We need to connect our Cynthion before we can test our new USB device. If you followed the prerequisites above, you should already have connected the Cynthion's **CONTROL** port to your computer. + +Now also connect the **TARGET C** port to your computer as this is the port we requested our USB Device to run on. The control host and target host can be two separate computers, but in this tutorial we will use the same computer as both the control host and the target host. + +.. image:: ../../images/cynthion-connections-facedancer-single-host.svg + :alt: Connection diagram for using Cynthion with a Gateware USB Device on a single host computer. + + +Build +----- + +Build the device gateware and upload it to your Cynthion by typing the following into your terminal shell: + +.. code-block :: sh + + python ./gateware-usb-device.py + +If everything went well and Cynthion's **TARGET C** port is connected we should now be able to check if the target host managed to succesfully enumerate our device. + + +Test +---- + +To check if the device was recognized by the target host's operating system follow the corresponding instructions: + +.. tab:: Python + + Create a new file called ``test-gateware-usb-device.py`` and add the following code to it: + + .. code-block :: python + :caption: test-gateware-usb-device.py + :linenos: + + import usb1 + + def list_available_usb_devices(context): + for device in context.getDeviceList(): + try: + manufacturer = device.getManufacturer() + product = device.getProduct() + print(f"{device}: {manufacturer} - {product}") + except Exception as e: + print(f"{device}: {e}") + + if __name__ == "__main__": + with usb1.USBContext() as context: + list_available_usb_devices(context) + + + Run the file with: + + .. code-block :: sh + + python ./test-gateware-usb-device.py + + And, if the device is recognized, you should see a line like: + + .. code-block :: sh + :emphasize-lines: 4 + + Bus 000 Device 001: ID 1d5c:5010: Fresco Logic, Inc. - USB2.0 Hub + Bus 000 Device 002: ID 1d5c:5000: Fresco Logic, Inc. - USB3.0 Hub + Bus 000 Device 003: ID 1d50:615c: Great Scott Gadgets - Cynthion Apollo Debugger + Bus 000 Device 007: ID 1209:0001: Cynthion Project - Gateware USB Device + + If you're running on Windows you may instead see something like: + + .. code-block :: sh + :emphasize-lines: 4 + + Bus 000 Device 001: ID 1d5c:5010: Fresco Logic, Inc. - USB2.0 Hub + Bus 000 Device 002: ID 1d5c:5000: Fresco Logic, Inc. - USB3.0 Hub + Bus 000 Device 003: ID 1d50:615c: Great Scott Gadgets - Cynthion Apollo Debugger + Bus 000 Device 007: ID 1209:0001: LIBUSB_ERROR_NOT_SUPPORTED [-12] + + The devices Product and Vendor ID's are correct (``1209:0001``) but Windows could not obtain the product or manufacturer strings. This behaviour is expected and we'll be taking a closer look at it in the next part of the tutorial. + + +.. tab:: Linux + + Run the following command in a terminal window: + + .. code-block :: sh + + lsusb + + If the device enumerated successfully you should see an entry similiar to the highlighted line: + + .. code-block :: sh + :emphasize-lines: 6 + + % lsusb + + Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub + Bus 001 Device 003: ID 2109:2822 VIA Labs, Inc. USB2.0 Hub + Bus 001 Device 045: ID 1d50:615c OpenMoko, Inc. Cynthion Apollo Debugger + Bus 001 Device 046: ID 1209:0001 Generic pid.codes Test PID + Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub + + To view the device's descriptors, pass the product and vendor id's by running: + + .. code-block :: sh + + lsusb -d 1209:0001 -v + + +.. tab:: macOS + + Run the following command in a terminal window: + + .. code-block :: sh + + ioreg -b -p IOUSB + + If the device enumerated successfully you should see an entry similiar to the highlighted line: + + .. code-block :: sh + :emphasize-lines: 8 + + % ioreg -b -p IOUSB + + +-o Root + +-o AppleT8103USBXHCI@00000000 `__. +* Beyond Logic's `USB in a NutShell `__. +* `LUNA Documentation `__ + + +Source Code +=========== + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/gateware-usb-device-01.py + :caption: gateware-usb-device-01.py + :language: python + :linenos: + + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/test-gateware-usb-device-01.py + :caption: test-gateware-usb-device-01.py + :language: python + :linenos: diff --git a/docs/source/tutorials/gateware_usb_device_02.rst b/docs/source/tutorials/gateware_usb_device_02.rst new file mode 100644 index 00000000..ac6dac1d --- /dev/null +++ b/docs/source/tutorials/gateware_usb_device_02.rst @@ -0,0 +1,397 @@ +USB Gateware: Part 2 - WCID Descriptors +####################################### + +This series of tutorial walks through the process of implementing a complete USB device with Cynthion and `LUNA `__: + +* :doc:`/tutorials/gateware_usb_device_01` +* :doc:`/tutorials/gateware_usb_device_02` *(This tutorial)* +* :doc:`/tutorials/gateware_usb_device_03` +* :doc:`/tutorials/gateware_usb_device_04` + +The goal of this tutorial is to define the descriptors that will tell Microsoft Windows to use the built-in generic WinUSB driver to communicate with our device. + +This tutorial is optional and only required if you would like to use your device on Windows. + + +Prerequisites +============= + + * Complete the :doc:`/tutorials/gateware_usb_device_01` tutorial. + + +WCID Devices +============ + +WCID devices or "Windows Compatible ID devices", are USB devices that provide extra information to Windows in order to facilitate automatic driver installation or, more frequently, allow programs to obtain direct access to the device. + +Historically, Windows required manual installation of drivers for non-class devices with custom vendor interfaces. Contrasted with Linux or macOS which will automatically assign a generic USB driver that allows for direct interaction with the device's endpoints via a cross-platform library such as `libusb `__ or operating system API's. + +Microsoft eventually relented and now provide a Windows-specific mechanism for a device to advertise that it requires a generic WinUSB driver. + +The full details are documented in the `Microsoft OS 1.0 `__ and `Microsoft OS 2.0 `__ specifications but the basic mechanism consists of a set of Windows-specific descriptor requests made by the host whenever a new device is plugged in. + +For Microsoft OS 1.0, this boils down to three descriptor requests we need to be able to handle: + +1. Microsoft OS String Descriptor +2. Microsoft Compatible ID Feature Descriptor +3. Microsoft Extended Properties Feature Descriptor + + +Microsoft OS String Descriptor +------------------------------ + +To start with, edit your ``gateware-usb-device.py`` file from the previous tutorial and add/modify the highlighted lines: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 5, 24-33 + + from amaranth import * + from luna.usb2 import USBDevice + from usb_protocol.emitters import DeviceDescriptorCollection + + from usb_protocol.emitters.descriptors.standard import get_string_descriptor + + ... + + class GatewareUSBDevice(Elaboratable): + ... + + def elaborate(self, platform): + m = Module() + + # configure cynthion's clocks and reset signals + m.submodules.car = platform.clock_domain_generator() + + # request the physical interface for cynthion's TARGET C port + ulpi = platform.request("target_phy") + + # create the USB device + m.submodules.usb = usb = USBDevice(bus=ulpi) + + # create our standard descriptors and add them to the device's control endpoint + descriptors = self.create_standard_descriptors() + control_endpoint = usb.add_standard_control_endpoint( + descriptors, + # add the parameter below to allow dynamic string descriptors + avoid_blockram=True + ) + + # add the microsoft os string descriptor + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + +The Microsoft OS String Descriptor responds to a standard String Descriptor request with an index of ``0xee``. It encodes two values: + +.. code-block :: python + + 0x12, # Descriptor Length: 18 bytes + 0x03, # Descriptor Type: 3 = String + 0x4d, 0x00, # M + 0x53, 0x00, # S + 0x46, 0x00, # F + 0x54, 0x00, # T + 0x31, 0x00, # 1 + 0x30, 0x00, # 0 + 0x30, 0x00, # 0 + 0xee, 0x00, # Vendor Code: 0xee + +The first 14 bytes correspond to the little-endian encoded Unicode string ``MSFT100``, with the remaining two bytes corresponding to the Vendor Code Windows should use when requesting the other descriptors. This is often set to the same value as the Microsoft OS String Descriptor index of ``0xee``, but you can use another value if it conflicts with an existing Vendor Code used by your device. + + +Microsoft Compatible ID Feature Descriptor +------------------------------------------ + +Next, add the Microsoft Compatible ID Feature Descriptor: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 5-7, 23-30 + + from amaranth import * + from luna.usb2 import USBDevice + from usb_protocol.emitters import DeviceDescriptorCollection + + from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + ) + from usb_protocol.emitters.descriptors.standard import get_string_descriptor + + + VENDOR_ID = 0x1209 # https://pid.codes/1209/ + PRODUCT_ID = 0x0001 + + class GatewareUSBDevice(Elaboratable): + ... + + def elaborate(self, platform): + ... + + # add the microsoft os string descriptor + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + + # add a microsoft descriptor collection for our other two microsoft descriptors + msft_descriptors = MicrosoftOS10DescriptorCollection() + + # add the microsoft compatible id feature descriptor + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + +Our remaining descriptors are not returned via Standard Requests, instead they are implemented as Vendor Requests with Microsoft-defined Vendor Indices and the Vendor Code supplied in the Microsoft OS String Descriptor. We will implement the actual vendor request handler in the final step of the tutorial but for now we are just defining the Microsoft OS 1.0 Descriptor Collection that will contain these descriptors. + +Our example is defining the simplest possible Compatible ID Feature descriptor, specifying a Function with a device interface number of ``0`` and a compatible ID of ``WINUSB``. This is how we tell Windows to use the generic WinUSB driver for the interface. + +If our device had multiple interfaces we could simply extended this by adding additional functions for each interface like so: + +.. code-block :: python + + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + with c.Function() as f: + f.bFirstInterfaceNumber = 1 + f.compatibleID = 'WINUSB' + ... + + + +Microsoft Extended Properties Feature Descriptor +------------------------------------------------ + +We now come to our third descriptor, the Microsoft Extended Properties Feature Descriptor: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 9, 31-36 + + from amaranth import * + from luna.usb2 import USBDevice + from usb_protocol.emitters import DeviceDescriptorCollection + + from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + ) + from usb_protocol.emitters.descriptors.standard import get_string_descriptor + from usb_protocol.types.descriptors.microsoft10 import RegistryTypes + + .. + + class GatewareUSBDevice(Elaboratable): + ... + + def elaborate(self, platform): + ... + + # add the microsoft os string descriptor + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + + # add a microsoft descriptor collection for our other two microsoft descriptors + msft_descriptors = MicrosoftOS10DescriptorCollection() + + # add the microsoft compatible id feature descriptor + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + + # add microsoft extended properties feature descriptor + with msft_descriptors.ExtendedPropertiesDescriptor() as d: + with d.Property() as p: + p.dwPropertyDataType = RegistryTypes.REG_SZ + p.PropertyName = "DeviceInterfaceGUID" + p.PropertyData = "{88bae032-5a81-49f0-bc3d-a4ff138216d6}" + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + +The Extended Properties Feature Descriptor can be used to define additional device registry settings but, in our example, we only define the Device Interface GUID we'd like our device to be accessed with. + +In this case it's the Microsoft-defined GUID of ``{88bae032-5a81-49f0-bc3d-a4ff138216d6}`` which is `defined as `__ *"all USB devices that don't belong to another class"*. If, for example, our device were a Keyboard or Mouse we'd need to use the appropriate value here. + + +Microsoft Descriptor Request Handler +------------------------------------ + +Finally, now that all our descriptors are defined we need to add the actual Vendor Request Handler that will be responsible for responding to descriptor requests from a Windows Host: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 7, 39-41 + + from amaranth import * + from luna.usb2 import USBDevice + from usb_protocol.emitters import DeviceDescriptorCollection + + from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + MicrosoftOS10RequestHandler, + ) + from usb_protocol.emitters.descriptors.standard import get_string_descriptor + from usb_protocol.types.descriptors.microsoft10 import RegistryTypes + + .. + + class GatewareUSBDevice(Elaboratable): + ... + + def elaborate(self, platform): + ... + + # add the microsoft os string descriptor + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + + # add a microsoft descriptor collection for our other two microsoft descriptors + msft_descriptors = MicrosoftOS10DescriptorCollection() + + # add the microsoft compatible id feature descriptor + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + + # add microsoft extended properties feature descriptor + with msft_descriptors.ExtendedPropertiesDescriptor() as d: + with d.Property() as p: + p.dwPropertyDataType = RegistryTypes.REG_SZ + p.PropertyName = "DeviceInterfaceGUID" + p.PropertyData = "{88bae032-5a81-49f0-bc3d-a4ff138216d6}" + + # add the request handler for Microsoft descriptors + msft_handler = MicrosoftOS10RequestHandler(msft_descriptors, request_code=0xee) + control_endpoint.add_request_handler(msft_handler) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + +LUNA provides a pre-defined implementation for handling Microsoft OS10 Descriptor Requests and only requires the descriptor collection and the vendor request code we defined in the Microsoft OS10 String Descriptor. + + +Testing the Device +================== + +Connect +------- + +* For this tutorial you will need to connect the Cynthion **TARGET C** port to a Windows computer for testing. +* Plug the **CONTROL** port into the computer you've been using to control Cynthion. If this is the same machine as the Windows computer you're using to test, plug it in there. + +Build +----- + +Build the device gateware and upload it to your Cynthion by typing the following into your terminal shell: + +.. code-block :: sh + + python ./gateware-usb-device.py + +If everything went well we should now be able to check if Windows can recognize the device. + + +Test +---- + +.. tab:: Windows + + To test whether the WCID descriptors have been recognized, open the Windows Device Manager and look for the device under the *"Universal Serial Bus devices"* section: + + .. image:: ../../images/tutorial_gateware_usb_device/with_wcid.png + :alt: Gateware USB Device on Windows without WCID Descriptors. + +.. tab:: Python + + You should find that the Python test program from :doc:`/tutorials/gateware_usb_device_01` now works as expected: + + .. code-block :: python + :caption: test-gateware-usb-device.py + :linenos: + + import usb1 + + def list_devices(context): + for device in context.getDeviceList(): + try: + manufacturer = device.getManufacturer() + product = device.getProduct() + print(f"{device}: {manufacturer} - {product}") + except Exception as e: + print(f"{device}: {e}") + + if __name__ == "__main__": + with usb1.USBContext() as context: + list_devices(context) + + Run the file with: + + .. code-block :: sh + + python ./test-gateware-usb-device.py + + And, if the device is recognized, you should see a line like: + + .. code-block :: sh + :emphasize-lines: 4 + + Bus 000 Device 001: ID 1d5c:5010: Fresco Logic, Inc. - USB2.0 Hub + Bus 000 Device 002: ID 1d5c:5000: Fresco Logic, Inc. - USB3.0 Hub + Bus 000 Device 003: ID 1d50:615c: Great Scott Gadgets - Cynthion Apollo Debugger + Bus 000 Device 007: ID 1209:0001: Cynthion Project - Gateware USB Device + + +Conclusion +========== + +Our device can now be enumerated by Microsoft Windows but it can't actually do anything yet. In the next part +we'll learn how to add Vendor Request Handlers to our device that allow it to receive and respond to control requests from the host: :doc:`/tutorials/gateware_usb_device_03` + + +Exercises +========= + +* Modify the example to use a different request code, does it still work? +* Could you use the information you learnt in this tutorial modify the LUNA `ACM Serial example `__ example to support Windows? +* Modify the ``PropertyData`` field of the extended properties descriptor to one of the `Microsoft-provided USB device class drivers `__. What happens? + + +More information +================ + +* Pete Batard's excellent introduction to `WCID Devices `__. +* `Microsoft OS 1.0 Descriptors Specification `__. +* `Microsoft OS 2.0 Descriptors Specification `__. +* Microsoft `USB device class drivers included in Windows `__. +* Microsoft `System-defined device setup classes available to vendors `__. + + +Source Code +=========== + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/gateware-usb-device-02.py + :caption: gateware-usb-device-02.py + :language: python + :linenos: + + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/test-gateware-usb-device-02.py + :caption: test-gateware-usb-device-02.py + :language: python + :linenos: diff --git a/docs/source/tutorials/gateware_usb_device_03.rst b/docs/source/tutorials/gateware_usb_device_03.rst new file mode 100644 index 00000000..d186a1d3 --- /dev/null +++ b/docs/source/tutorials/gateware_usb_device_03.rst @@ -0,0 +1,306 @@ +USB Gateware: Part 3 - Control Transfers +######################################## + +This series of tutorial walks through the process of implementing a complete USB device with Cynthion and `LUNA `__: + +* :doc:`/tutorials/gateware_usb_device_01` +* :doc:`/tutorials/gateware_usb_device_02` +* :doc:`/tutorials/gateware_usb_device_03` *(This tutorial)* +* :doc:`/tutorials/gateware_usb_device_04` + +The goal of this tutorial is to define a control interface for the device we created in Part 1 that will allow it to receive and respond to control requests from a host. + + +Prerequisites +============= + + * Complete the :doc:`/tutorials/gateware_usb_device_01` tutorial. + * Complete the :doc:`/tutorials/gateware_usb_device_02` tutorial. *(Optional, required for Windows support)* + + +Data Transfer between a Host and Device +======================================= + +USB is a *host-centric bus*, what this means is that all transfers are initiated by the host irrespective of the direction of data transfer. + +For data transfers to the device, the host issues an OUT token to notify the device of an incoming data transfer. When data has to be transferred from the device, the host issues an IN token to notify the device that it should send some data to the host. + +The USB 2.0 specification defines four endpoint or transfer types: + +* **Control Transfers:** Typically used for command and status operations, control transfers are the only transfer type with a defined USB format. +* **Bulk Transfers:** Bulk transfers are best suited for large amounts of data delivered in bursts such as file transfers to/from a storage device or the captured packet data from Cynthion to the control host. +* **Interrupt Transfers:** Interrupt transfers are a bit of a misnomer as the host needs to continuously poll the device to check if an interrupt has occurred but the principle is the same. Commonly used for peripherals that generate input events such as a keyboard or mouse. +* **Isochronous Transfers:** Finally, isochronous transfers occur continuously with a fixed periodicity. Suited for time-sensitive information such as video or audio streams they do not offer any guarantees on delivery. If a packet or frame is dropped it's is up to the host driver to decide on how to best handle it. + +By default all LUNA devices have a default implementation for two endpoints: An OUT Control Endpoint and an IN Control endpoint. These endpoints are used by the host to enumerate the device but they can also be extended to support various other class or custom vendor requests. + +We'll start by extending our control endpoints to support two vendor requests: One to set the state of the Cynthion **FPGA LEDs** and another to get the state of the Cynthion **USER BUTTON**. + + +Extend Default Control Endpoints +-------------------------------- + +To implement vendor requests, begin by adding a ``VendorRequestHandler`` to our device's control endpoint: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 12-14, 19-43, 83-84 + + from amaranth import * + from luna.usb2 import USBDevice + from usb_protocol.emitters import DeviceDescriptorCollection + + from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + MicrosoftOS10RequestHandler, + ) + from usb_protocol.emitters.descriptors.standard import get_string_descriptor + from usb_protocol.types.descriptors.microsoft10 import RegistryTypes + + from luna.gateware.stream.generator import StreamSerializer + from luna.gateware.usb.request.control import ControlRequestHandler + from luna.gateware.usb.usb2.transfer import USBInStreamInterface + + VENDOR_ID = 0x1209 # https://pid.codes/1209/ + PRODUCT_ID = 0x0001 + + class VendorRequestHandler(ControlRequestHandler): + VENDOR_SET_FPGA_LEDS = 0x01 + VENDOR_GET_USER_BUTTON = 0x02 + + def elaborate(self, platform): + m = Module() + + # shortcuts + interface: RequestHandlerInterface = self.interface + setup: SetupPacket = self.interface.setup + + # get a reference to the FPGA LEDs and USER button + fpga_leds = Cat(platform.request("led", i).o for i in range(6)) + user_button = platform.request("button_user").i + + # create a streamserializer for transmitting IN data back to the host + serializer = StreamSerializer( + domain = "usb", + stream_type = USBInStreamInterface, + data_length = 1, + max_length_width = 1, + ) + m.submodules += serializer + + return m + + class GatewareUSBDevice(Elaboratable): + + ... + + def elaborate(self, platform): + m = Module() + + # configure cynthion's clocks and reset signals + m.submodules.car = platform.clock_domain_generator() + + # request the physical interface for cynthion's TARGET C port + ulpi = platform.request("target_phy") + + # create the USB device + m.submodules.usb = usb = USBDevice(bus=ulpi) + + # create our standard descriptors and add them to the device's control endpoint + descriptors = self.create_standard_descriptors() + control_endpoint = usb.add_standard_control_endpoint( + descriptors, + avoid_blockram=True # allow dynamic string descriptors + ) + + # add microsoft os 1.0 descriptors and request handler + descriptors.add_descriptor(get_string_descriptor("MSFT100\xee"), index=0xee) + msft_descriptors = MicrosoftOS10DescriptorCollection() + with msft_descriptors.ExtendedCompatIDDescriptor() as c: + with c.Function() as f: + f.bFirstInterfaceNumber = 0 + f.compatibleID = 'WINUSB' + with msft_descriptors.ExtendedPropertiesDescriptor() as d: + with d.Property() as p: + p.dwPropertyDataType = RegistryTypes.REG_SZ + p.PropertyName = "DeviceInterfaceGUID" + p.PropertyData = "{88bae032-5a81-49f0-bc3d-a4ff138216d6}" + msft_handler = MicrosoftOS10RequestHandler(msft_descriptors, request_code=0xee) + control_endpoint.add_request_handler(msft_handler) + + # add our vendor request handler + control_endpoint.add_request_handler(VendorRequestHandler()) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + + +Vendor requests are unique to a device and are identified by the 8-bit ``bRequest`` field of the control transfer setup packet. Here we've defined two id's corresponding to setting the led states and getting the button state. + +So far our ``VendorRequestHandler`` contains references to Cynthion's **FPGA LEDs** and **USER BUTTON**, as well as a **StreamSerializer** we'll be using to send data back to the host when it asks for the **USER BUTTON** status. + + +Implement Vendor Request Handlers +--------------------------------- + +Let's implement that functionality below: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 25-70 + + class VendorRequestHandler(ControlRequestHandler): + VENDOR_SET_FPGA_LEDS = 0x01 + VENDOR_GET_USER_BUTTON = 0x02 + + def elaborate(self, platform): + m = Module() + + # Shortcuts. + interface: RequestHandlerInterface = self.interface + setup: SetupPacket = self.interface.setup + + # Grab a reference to the FPGA LEDs and USER button. + fpga_leds = Cat(platform.request("led", i).o for i in range(6)) + user_button = platform.request("button_user").i + + # Create a StreamSerializer for sending IN data back to the host + serializer = StreamSerializer( + domain = "usb", + stream_type = USBInStreamInterface, + data_length = 1, + max_length_width = 1, + ) + m.submodules += serializer + + # we've received a setup packet containing a vendor request. + with m.If(setup.type == USBRequestType.VENDOR): + # take ownership of the interface + m.d.comb += interface.claim.eq(1) + + # use a state machine to sequence our request handling + with m.FSM(domain="usb"): + with m.State("IDLE"): + with m.If(setup.received): + with m.Switch(setup.request): + with m.Case(self.VENDOR_SET_FPGA_LEDS): + m.next = "HANDLE_SET_FPGA_LEDS" + with m.Case(self.VENDOR_GET_USER_BUTTON): + m.next = "HANDLE_GET_USER_BUTTON" + with m.Default(): + m.next = "UNHANDLED" + + with m.State("UNHANDLED"): + # stall unhandled requests + with m.If(interface.status_requested | interface.data_requested): + m.d.comb += interface.handshakes_out.stall.eq(1) + m.next = "IDLE" + + with m.State("HANDLE_SET_FPGA_LEDS"): + # if we have an active data byte, set the FPGA LEDs to the payload + with m.If(interface.rx.valid & interface.rx.next): + m.d.usb += fpga_leds.eq(interface.rx.payload[0:6]) + + # once the receive is complete, respond with an ACK + with m.If(interface.rx_ready_for_response): + m.d.comb += interface.handshakes_out.ack.eq(1) + + # finally, once we reach the status stage, send a ZLP + with m.If(interface.status_requested): + m.d.comb += self.send_zlp() + m.next = "IDLE" + + with m.State("HANDLE_GET_USER_BUTTON"): + # write the state of the user button into a local data register + data = Signal(8) + m.d.comb += data[0].eq(user_button) + + # transmit our data using a built-in handler function that + # automatically advances the FSM back to the 'IDLE' state on + # completion + self.handle_simple_data_request(m, serializer, data) + + return m + + + +When handling a control request in LUNA the first thing we look at is the ``setup.type`` field of the setup packet interface. We could check for other types such as ``USBRequestType.CLASS`` or ``USBRequestType.DEVICE`` if we wanted to implement handlers for them but, in this case, we're only interested in vendor requests. + +Next, we take ownership of the interface, in order to avoid conflicting with the standard, or other registered request handlers. Then we sequence the actual request handling with an `Amaranth Finite State Machine `__, starting in the ``IDLE`` state. + +While in ``IDLE`` we wait for the ``setup.received`` signal to go high and signal the arrival of a new control request. We then parse the ``setup.request`` field to identify the next state to advance our FSM to. (We could also use the other setup packet fields such as ``wValue`` and ``wIndex`` for dispatch or as arguments but for now we're just intered in ``bRequest``.) + +The first handler of interest is ``UNHANDLED`` which simply stalls the interface in order to inform the host that it was an invalid request. + +Next is ``HANDLE_SET_FPGA_LEDS`` which needs to read the data sent with our OUT control request in order to set the fpga leds state. + +Finally, in ``HANDLE_GET_USER_BUTTON`` we will use one of the built-in LUNA helper function to respond to our IN control request with the data containing the state of the user button. + + +Test Control Endpoints +====================== + +First, remember to build and upload the device gateware to your Cynthion with: + +.. code-block :: sh + + python ./gateware-usb-device.py + +Then, open your ``test-gateware-usb-device.py`` script from the previous tutorials and add the following code to it: + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/test-gateware-usb-device-03.py + :caption: test-gateware-usb-device.py + :language: python + :linenos: + :emphasize-lines: 2, 7-8, 22-68, 78-87 + + +Run the file with: + +.. code-block :: sh + + python ./test-gateware-usb-device.py + +And, if all goes well you should see the **FPGA LEDs** on Cynthion counting in binary. If you press and release the **USER** button you should see the count reset back to zero and the following text in the terminal. + +.. code-block :: sh + + USER button is: ON + USER button is: OFF + +Job done! + +In the next part of the tutorial we'll finish up by adding IN and OUT Bulk endpoints to our device. + + +Exercises +========= + +1. Add a vendor request to retrieve the current state of the FPGA LEDs. +2. Add a vendor request that will disconnect and then re-connect your device to the USB bus. + + +More information +================ + +* Beyond Logic's `USB in a NutShell `__. +* `LUNA Documentation `__ + + +Source Code +=========== + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/gateware-usb-device-03.py + :caption: gateware-usb-device-03.py + :language: python + :linenos: + + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/test-gateware-usb-device-03.py + :caption: test-gateware-usb-device-03.py + :language: python + :linenos: diff --git a/docs/source/tutorials/gateware_usb_device_04.rst b/docs/source/tutorials/gateware_usb_device_04.rst new file mode 100644 index 00000000..2a9e8997 --- /dev/null +++ b/docs/source/tutorials/gateware_usb_device_04.rst @@ -0,0 +1,386 @@ +USB Gateware: Part 4 - Bulk Transfers +##################################### + +This series of tutorial walks through the process of implementing a complete USB device with Cynthion and `LUNA `__: + +* :doc:`/tutorials/gateware_usb_device_01` +* :doc:`/tutorials/gateware_usb_device_02` +* :doc:`/tutorials/gateware_usb_device_03` +* :doc:`/tutorials/gateware_usb_device_04` *(This tutorial)* + +The goal of this tutorial is to define Bulk Endpoints for the device we created in Part 3 that will allow us to efficiently perform larger data transfers than those allowed by Control Transfers. + + +Prerequisites +============= + + * Complete the :doc:`/tutorials/gateware_usb_device_01` tutorial. + * Complete the :doc:`/tutorials/gateware_usb_device_02` tutorial. *(Optional, required for Windows support)* + * Complete the :doc:`/tutorials/gateware_usb_device_03` tutorial. + + +Add Bulk Endpoints +================== + +While Control transfers are well suited for command and status operations they are not the best way to exchange large quantities of data. Control transfers have high per-packet protocol overhead and can only transfer packets of 8 bytes on low speed (1.5Mbps) devices and 64 bytes on full (12Mbps) and high (512Mbps) speed devices. + +On the other hand, Bulk transfers support a packet size of up to 512 bytes on high speed devices and do not require any protocol overhead. + +In the first section we'll begin by updating our device's descriptors so it can inform the host that it has bulk endpoints available. + + +Update Device Descriptors +------------------------- + +Open ``gateware-usb-device.py`` and add the highlighted lines: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 19-27, 32, 51-60 + + from amaranth import * + from luna.usb2 import USBDevice + from usb_protocol.emitters import DeviceDescriptorCollection + + from luna.gateware.usb.request.windows import ( + MicrosoftOS10DescriptorCollection, + MicrosoftOS10RequestHandler, + ) + from usb_protocol.emitters.descriptors.standard import get_string_descriptor + from usb_protocol.types.descriptors.microsoft10 import RegistryTypes + + from luna.gateware.stream.generator import StreamSerializer + from luna.gateware.usb.request.control import ControlRequestHandler + from luna.gateware.usb.request.interface import SetupPacket + from luna.gateware.usb.usb2.request import RequestHandlerInterface + from luna.gateware.usb.usb2.transfer import USBInStreamInterface + from usb_protocol.types import USBRequestType + + from luna.gateware.stream import StreamInterface + from luna.usb2 import ( + USBStreamInEndpoint, + USBStreamOutEndpoint, + ) + from usb_protocol.types import ( + USBDirection, + USBTransferType, + ) + + VENDOR_ID = 0x1209 # https://pid.codes/1209/ + PRODUCT_ID = 0x0001 + + MAX_PACKET_SIZE = 512 + + class VendorRequestHandler(ControlRequestHandler): + ... + + class GatewareUSBDevice(Elaboratable): + def create_standard_descriptors(self): + descriptors = DeviceDescriptorCollection() + + with descriptors.DeviceDescriptor() as d: + d.idVendor = VENDOR_ID + d.idProduct = PRODUCT_ID + d.iManufacturer = "Cynthion Project" + d.iProduct = "Gateware USB Device" + d.bNumConfigurations = 1 + + with descriptors.ConfigurationDescriptor() as c: + with c.InterfaceDescriptor() as i: + i.bInterfaceNumber = 0 + # EP 0x01 OUT - receives bulk data from the host + with i.EndpointDescriptor() as e: + e.bEndpointAddress = USBDirection.OUT.to_endpoint_address(0x01) + e.bmAttributes = USBTransferType.BULK + e.wMaxPacketSize = MAX_PACKET_SIZE + # EP 0x82 IN - transmits bulk data to the host + with i.EndpointDescriptor() as e: + e.bEndpointAddress = USBDirection.IN.to_endpoint_address(0x02) + e.bmAttributes = USBTransferType.BULK + e.wMaxPacketSize = MAX_PACKET_SIZE + + return descriptors + + def elaborate(self, platform): + ... + +This adds two endpoint descriptors to our default interface, each of type ``USBTransferType.BULK`` and with a ``MAX_PACKET_SIZE`` of 512. Where the endpoints differ is in their endpoint address. USB endpoint descriptors encode their direction in an 8 bit endpoint address. The first four bits encode the endpoint number, the next three bits are reserved and set to zero and the final bit encodes the direction; 0 for OUT and 1 for IN. + +This means that an OUT endpoint number of 0x01 encodes to an endpoint address of 0x01 while an IN endpoint number of ``0x02`` encodes to the address ``0x82``. (``0b0000_0010 + 0b1000_0000 = 0b1000_0010 = 0x82``) + + +Add USB Stream Endpoints +------------------------ + +Once our endpoint descriptors have been added to our device configuration we will need some gateware that will be able to respond to USB requests from the host and allow us to receive and transmit data. + +LUNA provides the ``USBStreamOutEndpoint`` and ``USBStreamInEndpoint`` modules which conform to the `Amaranth Data streams `__ interface. Simply put, streams provide a uniform mechanism for unidirectional exchange of arbitrary data between gateware modules. + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 13-23 + + ... + + class GatewareUSBDevice(Elaboratable): + def create_standard_descriptors(self): + ... + + def elaborate(self, platform): + ... + + # add the vendor request handler + control_endpoint.add_request_handler(VendorRequestHandler()) + + # create and add stream endpoints for our device's Bulk IN & OUT endpoints + ep_out = USBStreamOutEndpoint( + endpoint_number=0x01, # (EP 0x01) + max_packet_size=MAX_PACKET_SIZE, + ) + usb.add_endpoint(ep_out) + ep_in = USBStreamInEndpoint( + endpoint_number=0x02, # (EP 0x82) + max_packet_size=MAX_PACKET_SIZE + ) + usb.add_endpoint(ep_in) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + +We now have two streaming endpoints that are able to receive and transmit data between any other module that supports the Amaranth Data streams interface. + +However, before we can stream any data across these endpoints we first need to come up with a *USB Function* for each of our endpoints. In other words, what does our device actually _do_? + +This could be any data source and/or sink but for the purposes of this tutorial let's create a (very) simple storage device. + + +Define Endpoint Functions +------------------------- + +Our device's endpoint functions will be a simple streaming memory store module that will allow us to read & write data over our bulk endpoints. + +Using the OUT endpoint we can transmit a stream of data from the host to Cynthion and write into a local memory. Then, we'd like to be able to transmit a request from the host to the IN endpoint and retrieve the previously stored data. + +This means we'll need some memory we can read and write to, so let's begin by creating an `Amaranth Memory component `__ which uses the FPGA's Block RAM for storage: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 6-33, 41-57 + + ... + + class VendorRequestHandler(ControlRequestHandler): + ... + + class StreamingMemoryStore(Elaboratable): + def __init__(self, stream_out: StreamInterface, stream_in: StreamInterface): + self.stream_out = stream_out + self.stream_in = stream_in + + # high when a memory write is in process + self.write_active = Signal() + + def elaborate(self, platform): + m = Module() + + # create a memory we can use as a data source/sink for our bulk endpoints + m.submodules.ram = ram = Memory( + width = 8, + depth = MAX_PACKET_SIZE, + init = [0] * MAX_PACKET_SIZE + ) + w_port = ram.write_port(domain="usb") + r_port = ram.read_port(domain="usb") + + # set the write_active status to the write port's enable status + m.d.comb += self.write_active.eq(w_port.en) + + # shortcuts + stream_out = self.stream_out + stream_in = self.stream_in + + return m + + class GatewareUSBDevice(Elaboratable): + ... + + def elaborate(self, platform): + ... + + # create and add stream endpoints for our device's Bulk IN & OUT endpoints + ep_out = USBStreamOutEndpoint( + endpoint_number=0x01, # (EP 0x01) + max_packet_size=MAX_PACKET_SIZE, + ) + usb.add_endpoint(ep_out) + ep_in = USBStreamInEndpoint( + endpoint_number=0x02, # (EP 0x82) + max_packet_size=MAX_PACKET_SIZE + ) + usb.add_endpoint(ep_in) + + # create a simple streaming memory storage module + m.submodules.store = store = StreamingMemoryStore(ep_out.stream, ep_in.stream) + + # invalidate any data queued on ep_in when the memory performs a write operation + m.d.comb += ep_in.discard.eq(store.write_active) + + # configure the device to connect by default when plugged into a host + m.d.comb += usb.connect.eq(1) + + return m + +Great, now we have a 8-bit wide memory that's large enough to store a full high-speed transfer packet! Let's implement the logic to handle read/write requests to our memory store module. + + +Bulk OUT Endpoint Gateware +-------------------------- + +To implement our OUT endpoint's stream we'll need to read the data stream coming from the host and write each bit into our memory component. + +Let's implement this by adding the following lines: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 11-25 + + ... + + class StreamingMemoryStore(Elaboratable): + def elaborate(self, platform): + ... + + # shortcuts + stream_out = self.stream_out + stream_in = self.stream_in + + # - EP 0x01 OUT logic ------------------------------------------------ + + # let the stream know we're always ready to start reading + m.d.comb += stream_out.ready.eq(1) + + # wire the payload from the host up to our memory write port + m.d.comb += w_port.data.eq(stream_out.payload) + + # read each byte coming in on the stream and write it to memory + with m.If(stream_out.valid & stream_out.ready): + m.d.comb += w_port.en.eq(1) + m.d.usb += w_port.addr.eq(w_port.addr + 1); + with m.Else(): + m.d.comb += w_port.en.eq(0) + m.d.usb += w_port.addr.eq(0) + + return m + + ... + +When the host makes a Bulk OUT request to the device we read each byte of the data packet from the endpoint stream as it is received and then write it to a consecutive address in our memory. We'll talk more about LUNA stream interfaces in a future tutorial but all you need to know for now is that they provide a uniform interface between LUNA endpoints and other components. + +Finally, let's do the same for our IN endpoint's stream. + +Bulk IN Endpoint Gateware +------------------------- + +Add the following lines: + +.. code-block :: python + :caption: gateware-usb-device.py + :linenos: + :emphasize-lines: 11-26 + + ... + + class StreamingMemoryStore(Elaboratable): + def elaborate(self, platform): + ... + + # - EP 0x01 OUT logic ------------------------------------------------ + + ... + + # - EP 0x82 IN logic ------------------------------------------------- + + # wire the payload to the host up to our memory read port + m.d.comb += stream_in.payload.eq(r_port.data) + + # discard streamed data when memory write port is active + m.d.comb += self.ep_in.discard.eq(w_port.en) + + # when the stream is ready and the write port is not active, + # read each byte from memory and write it out to the stream + with m.If(stream_in.ready & ~w_port.en): + m.d.usb += stream_in.valid.eq(1) + m.d.usb += r_port.addr.eq(r_port.addr + 1) + with m.Else(): + m.d.usb += stream_in.valid.eq(0) + m.d.usb += r_port.addr.eq(0) + + return m + + ... + +This time, when the host makes a Bulk IN request, we write the content of each consecutive address of our memory to the stream. Let's try it out! + + +Test Bulk Endpoints +=================== + +Open up ``test-gateware-usb-device.py`` and add the following code to it: + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/test-gateware-usb-device-04.py + :caption: test-gateware-usb-device.py + :language: python + :linenos: + :emphasize-lines: 3, 11, 74-106, 124-125 + +Run the file with: + +.. code-block :: sh + + python ./test-gateware-usb-device.py + +Assuming everything is going to plan you should see two matching sets of random numbers: + +.. code-block :: sh + + OUT endpoint transmitted 512 bytes: [252, 107, 106, 56] ... [109, 175, 112, 126] + IN endpoint received 512 bytes: [252, 107, 106, 56] ... [109, 175, 112, 126] + +Congratulations, if you made it this far then you've just finished building your first complete USB Gateware Device with custom vendor request control and bulk data transfer! + + +Exercises +========= + +1. Add a vendor request to zero the memory. +2. Create a benchmark to test the speed of your device when doing Bulk IN and OUT transfers. +3. Use the contents of the memory to drive the pattern of the FPGA LEDs. +4. Move the device endpoint to ``aux_phy`` and attempt to capture the enumeration of a device plugged into a host via the ``target_phy`` port. + + +More information +================ + +* Beyond Logic's `USB in a NutShell `__. +* `LUNA Documentation `__ + + +Source Code +=========== + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/gateware-usb-device-04.py + :caption: gateware-usb-device-04.py + :language: python + :linenos: + + +.. literalinclude:: ../../../cynthion/python/examples/tutorials/test-gateware-usb-device-04.py + :caption: test-gateware-usb-device-04.py + :language: python + :linenos: