-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add gateware usb device tutorial
- Loading branch information
Showing
14 changed files
with
2,329 additions
and
0 deletions.
There are no files selected for viewing
69 changes: 69 additions & 0 deletions
69
cynthion/python/examples/tutorials/gateware-usb-device-01.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# This file is part of Cynthion. | ||
# | ||
# Copyright (c) 2024 Great Scott Gadgets <[email protected]> | ||
# 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) |
102 changes: 102 additions & 0 deletions
102
cynthion/python/examples/tutorials/gateware-usb-device-02.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# This file is part of Cynthion. | ||
# | ||
# Copyright (c) 2024 Great Scott Gadgets <[email protected]> | ||
# 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) |
177 changes: 177 additions & 0 deletions
177
cynthion/python/examples/tutorials/gateware-usb-device-03.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# This file is part of Cynthion. | ||
# | ||
# Copyright (c) 2024 Great Scott Gadgets <[email protected]> | ||
# 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) |
Oops, something went wrong.