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