-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Cynthion Gateware Tutorials #197
base: main
Are you sure you want to change the base?
Add Cynthion Gateware Tutorials #197
Conversation
ae6a2c3
to
f06f505
Compare
dacfe7f
to
743597d
Compare
96ea10f
to
ccef718
Compare
ccef718
to
2361f74
Compare
2361f74
to
c67e981
Compare
descriptors = self.create_standard_descriptors() | ||
control_endpoint = usb.add_standard_control_endpoint( | ||
descriptors, | ||
avoid_blockram=True # allow dynamic string descriptors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not really the reason. The avoid_blockram
option is enabled because the blockram handler does not support non-contiguous indices for string descriptors, and the Microsoft one needs to be at 0xee
. We considered implementing an internal mapping to handle these cases.
# 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest a simpler example here with a SyncFIFO
. Note it also blocks the stream when the FIFO is full:
m.submodules.mem = mem = DomainRenamer("usb")(fifo.SyncFIFO(width=8, depth=MAX_PACKET_SIZE))
...
m.d.comb += mem.w_data.eq(stream_out.payload)
m.d.comb += mem.w_en.eq(stream_out.valid)
m.d.comb += stream_out.ready.eq(mem.w_rdy)
m.d.comb += stream_in.payload.eq(mem.r_data)
m.d.comb += stream_in.valid.eq(mem.r_rdy)
m.d.comb += mem.r_en.eq(stream_in.ready)
Since Amaranth HDL v0.5.0, you can do this instead:
wiring.connect(m, stream_out, mem.w_stream)
wiring.connect(m, mem.r_stream, stream_in)
half_freq: int = int(60e6 // 2) | ||
timer: Signal(25) = Signal(range(half_freq + 1)) | ||
|
||
with m.If(timer == half_freq): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the correct thing would be to check for timer == half_freq - 1
. This is adding an extra cycle so the blinking period is not 500 ms (but really close!). For that reason, timer
could just be Signal(range(half_freq))
.
|
||
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 <https://amaranth-lang.org/docs/amaranth/latest/stdlib/stream.html>`__ interface. Simply put, streams provide a uniform mechanism for unidirectional exchange of arbitrary data between gateware modules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which conform to the
Amaranth Data streams <https://amaranth-lang.org/docs/amaranth/latest/stdlib/stream.html>
__ interface
This is still not true, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great! 😃 Added some comments/suggestions.
This PR adds
threetwofive Gateware tutorials of increasing complexity to the documentation:NOTE: In the interests of releasing some tutorials sooner rather than later I'll be adding the USB Audio Class Device tutorial in a separate PR.
Rendered: https://cynthion--197.org.readthedocs.build/en/197/
Depends on: