{josuah.net} | {panoramix-labs.fr}
(DIR) • {josuah.net}
(DIR) • {panoramix-labs.fr}
{git} | {cv} | {links} | {quotes} | {ascii} | {tgtimes} | {gopher} | {mail}
(DIR) • {git}
(BIN) • {cv}
(DIR) • {links}
(DIR) • {quotes}
(DIR) • {ascii}
(HTM) • {tgtimes}
(DIR) • {gopher}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FPGA ←SPI→ MCU: Crossing Clock Domains
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
For my SDR project, I want to combine a RP2040 and an ICE40.
It looks good on paper
──────────────────────
Both a are widely available board and cover a lot of features together.
The RP2040 features would be expensive to do in an FPGA:
Fast Dual-Core MCU:
┊ More than enough to keep-up with the FPGA.
Plenty of RAM:
┊ Enough to organize complex applications.
USB support:
┊ It would spend a lot of gates to get that done on the FPGA.
PIO peripherals:
┊ Tiny programmable state machine for handling simple extra protocols: Handy
┊ for handling everything that does not fit the FPGA, or for custom
┊ interfaces.
An FPGA complements quite well what the RP2040 lacks:
More peripherals for the RP2040:
┊ By writing Wishbone peripherals on the FPGA, and writing an SPI-to-Wishbone
┊ bridge. It permits to implement peripherals on the FPGA, and write drivers
┊ on the RP2040 for them.
DSP front-end for RP2040:
┊ The FPGA can be placed as a front-end for the MCU: Receiving signals from
┊ the sensors. Then converting them onto an easy-to parse digital signal.
┊ Then transmitting them over a protocol the RP2040 likes.
Let's see if we can make that work in practice...
First challenge: Clock Domain Crossing
──────────────────────────────────────
SPI comes with its own clock signal. It is convenient and reliable to use it as
clock for the SPI core: `@(posedge spi_clk)` in Verilog instead of the
`@(posedge wb_clk_i)` clock used by the rest. This means we have now two clock
(HTM) domains and need to {plan cooperation between them}.
This introduce me to the famous topic of Clock Domain Crossing: taking data
(HTM) from one {clock domain} to another.
Recommandations often encountered is to use a handshake protocol. I will stick
to the simplest implementation I can come-up with and see if it works well in
practice.
Handshake protocol for the Wishbone Clock Domain
────────────────────────────────────────────────
Simple handshake protocol for crossing clock domain.
• The source module sending the data to another clock domain writes to
`handshake_ack` (and reads `handshake_req`).
• The destination module receiving data from another clock domain writes to
`handshake_req` (and reads `handshake_ack`).
┊ : : : : : : : : : : : :
┊ __:_______________:_______________:______________
┊ handshake_data __X_______________X_______________X______________
┊ : _______________: : : : __________
┊ handshake_req ______/ : : : \_______________/ : :
┊ : : : :_______________: : : : :__
┊ handshake_ack ______________/ : : : \_______________/
┊ : : : : : : : : : : : :
┊ (1) (2) (3) (4) (1) (2) (3) (4) (1) (2) (3) (4)
• When the source has data to transfer, it first asserts `handshake_data` to
the data to transfer (1) then invert `handshake_req` (2).
• Once the destination notices it, it copies `handshake_data` to a local
register (3) then sets `handshake_ack` to the same value as `handshake_req`
(4).
Wire protocol
─────────────
The Wishbone protocol uses many more wires than SPI for communicating, so a
wire protocol for encoding Wishbone over another transport is required.
(HTM) Here is what I came-up with, inspired by {spibone}.
Wishbone read transaction:
┊ MCU W000SSSS AAAAAAAA :::::::: :::::::: :::::::: :::::::: :::::::: ::::::::
┊ │ ├──┘ ├──────┘
┊ FPGA │:::│::: │::::::: 11111111 00000000 DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
┊ │ │ │ ├──────┘ ├──────┘ ├─────────────────────────────────┘
┊ WE SEL ADR WAIT ACK DAT
Wishbone write transaction:
┊ MCU W000SSSS AAAAAAAA DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD :::::::: ::::::::
┊ │ ├──┘ ├──────┘ ├─────────────────────────────────┘
┊ FPGA │:::│::: │::::::: │::::::: :::::::: :::::::: :::::::: 11111111 00000000
┊ │ │ │ │ ├──────┘ ├──────┘
┊ WE SEL ADR DAT WAIT ACK
Signals
───────
While far from battle-tested, this seems to work at least a little:
(IMG) {{signals shown in gtkwave}}
This is the state of the signals on the FPGA, with the clocks in red, the I/O
signals in yellow, and the others in green, with the global state in violet.
(DIR) The I/O block shows the clock from the MCU (or rather here, {simulation}), that
are captured by the `rx{}` block as packets in `handshake_data` (`8'h8F`,
`8'h00`, `8'h12`, ...),
These are then decoded by the state machine shown in `state`, and finally sent
to the bus.
We can recognize the data payload `32'h12345678` sent packet per packet on
`spi_sdi`.
Links
─────
(HTM) • {http://fpgacpu.ca/fpga/handshake.html}
(HTM) • {http://www.sunburst-design.com/papers/CummingsSNUG2008Boston_CDC.pdf}
(HTM) • {https://zipcpu.com/blog/2018/07/06/afifo.html}
(HTM) • {https://www.fpga4fun.com/CrossClockDomain.html}
(HTM) • {https://en.wikipedia.org/wiki/Metastability_%28electronics%29#Synchronous_circuits}
(HTM) • {https://www.eevblog.com/forum/microcontrollers/interface-fpga-and-microcontroller/}
(HTM) • {https://github.com/xobs/spibone}
(HTM) • {https://www.eevblog.com/forum/fpga/a-simple-clock-domain-crossing-(cdc)-strategy/}
(HTM) • {https://github.com/xobs/spibone}