{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}