Projects / IP Core
OctoSPI FPGA IP Core
OctoSPI slave IP core for Xilinx FPGAs with AXI4-Lite bridge and Embassy/Rust async driver for STM32U595. Enables high-speed MCU ↔ FPGA communication over an 8-bit single-transfer-rate interface.
View on GitHub ↗System Overview
STM32U595 ──OctoSPI──▶ octo_spi_slave ──▶ octo_spi_axi_master ──▶ BRAM A/B/C/D
Embassy/Rust driver Protocol FSM AXI4-Lite bridge axi_addr_decoder
(dual-buffer prefetch) (req/rsp bridge)The STM32U595 drives the OctoSPI master using the driver on top of Embassy async runtime. All IO[7:0] transfers run in 8-bit STR (single-transfer-rate) mode.
Inside the FPGA, octo_spi_slave parses the 7-state protocol FSM and issues AXI4-Lite requests via octo_spi_axi_master. An address decoder routes each transaction to one of four BRAM slaves based on addr[17:16].
Clock domain: 54 MHz board input → PLL → aclk at 100 MHz.
Protocol Frame Structure
All fields transferred in 8-bit STR mode on IO[7:0]. DUMMY cycles are used for dual-buffer AXI prefetch on read transactions.
Command Opcodes
| Opcode | Hex | Description |
|---|---|---|
| CMD_WRITE_INCR | 0xCA | Burst write, auto-increment address |
| CMD_WRITE_FIXED | 0xFE | Repeated write to fixed address (FIFO push) |
| CMD_READ_INCR | 0xBA | Burst read, auto-increment address |
| CMD_READ_FIXED | 0xBE | Repeated read from fixed address (FIFO pop) |
Module Responsibilities
| Module | File | Role |
|---|---|---|
| octo_spi_slave | ospi/rtl/octo_spi_slave.sv | Protocol parser FSM, dual-buffer prefetch, write accumulator |
| octo_spi_axi_master | ospi/rtl/octo_spi_axi_master.sv | AXI4-Lite request/response bridge |
| octo_spi_top | ospi/rtl/octo_spi_top.sv | I/O synchronizer wrapper |
| octospi_bram_test_top | eval/rtl/octospi_bram_test_top.sv | Board top: PLL, reset sync, 2-to-1 arbiter |
| axi_addr_decoder | eval/rtl/axi_addr_decoder.sv | Routes AXI transactions to BRAM A/B/C/D via addr[17:16] |
| axi_lite_bram | eval/rtl/axi_lite_bram.sv | Plain synchronous BRAM (BRAM C) |
| axi_lite_bram_fifo | eval/rtl/axi_lite_bram_fifo.sv | Circular BRAM with software FIFO API (BRAM D) |
| bram_incr_fill_master | eval/rtl/bram_incr_fill_master.sv | Auto-fills BRAM A with a counter on each read request |
| bram_fifo_master | eval/rtl/bram_fifo_master.sv | Hardware FIFO producer for BRAM B |
BRAM Address Map
| Example | BRAM | AXI Base Address | Function |
|---|---|---|---|
| bram_a | A | 0x0000_0000 | READ_INCR — triggers auto counter fill on read |
| bram_b | B | 0x0001_0000 | READ_FIXED — pops from hardware FIFO |
| bram_c | C | 0x0002_0000 | WRITE_INCR + READ_INCR roundtrip |
| bram_d | D | 0x0003_0000 | WRITE_FIXED + READ_FIXED software FIFO |
ILA Probe Reference
Trigger position: 3000 pre-trigger + 1096 post-trigger = 4096 total depth. Clock domain: aclk (100 MHz). Primary trigger probe: probe7 (req_valid).
| Probe | Width | Signal |
|---|---|---|
| probe0 | 24 | {state[3:0], cnt[7:0], captured_byte[7:0], driving, initial_valid, prefetch_valid, word1_done} |
| probe1 | 32 | shift_out — OctoSPI shift register contents |
| probe2 | 32 | initial_buf — first AXI read response buffer |
| probe3 | 32 | prefetch_buf — prefetch read response buffer |
| probe4 | 8 | ospi_io_in — IO pins driven by STM32 |
| probe5 | 8 | ospi_io_out — IO pins driven by FPGA |
| probe6 | 5 | {aresetn, csn_sync, sclk_sync, sclk_rise, byte_ready} |
| probe7 | 34 | {req_valid, req_read, req_addr[31:0]} ← primary trigger |
| probe8 | 33 | {rsp_valid, rsp_rdata[31:0]} — AXI read return data |
FSM State Decode (probe0[23:20])
| Value | State | Meaning |
|---|---|---|
| 0 | ST_IDLE | Waiting for CSn assertion |
| 1 | ST_CMD | Receiving CMD byte |
| 2 | ST_AUX | Receiving AUX bytes |
| 3 | ST_ADDR | Receiving address bytes |
| 4 | ST_DUMMY | Dummy cycles — prefetch AXI reads issued here |
| 5 | ST_DATA | Data read/write phase |
| 6 | ST_DONE | Transaction complete |
Quick Start
Build the FPGA Bitstream
Requires Vivado 2024.x or 2025.x. Synthesis + implementation takes ~30 minutes.
cd fpga make bitstream # or: make project && make gui
Program the FPGA
Start hw_server (or open Hardware Manager in Vivado GUI) first.
cd fpga make program
Run STM32 Tests
Requires Rust stable + thumbv8m.main-none-eabihf target and probe-rs ≥ 0.24.
cd stm32 export PROBE_RS_PROBE=<your-probe-serial> cargo run --example bram_a # READ_INCR auto-fill cargo run --example bram_b # READ_FIXED FIFO pop cargo run --example bram_c # WRITE+READ roundtrip cargo run --example bram_d # WRITE+READ software FIFO