OctoSPI FPGA IP Core

An OctoSPI slave IP core for Xilinx FPGAs with an AXI4-Lite bridge, paired with an Embassy/Rust async driver for the STM32U595 — a high-speed MCU ↔ FPGA link over an 8-bit single-transfer-rate interface.

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          (req/rsp bridge)
                          prefetch)

The STM32U595 drives the link as OctoSPI master using a driver built on the 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 frame with a protocol FSM and issues AXI4-Lite requests through octo_spi_axi_master. An address decoder routes each transaction to one of four BRAM slaves based on addr[17:16].

Clock domains: a 54 MHz board input feeds a PLL that generates aclk at 100 MHz for the whole FPGA design, while the OctoSPI clock itself is timing-constrained for up to 25 MHz.

Tech Stack

RTL: SystemVerilog

Toolchain: Vivado 2024.x / 2025.x

Bus: AXI4-Lite

MCU: STM32U595

Firmware: Rust + Embassy

Protocol Frame Structure

CMD
1 byte
AUX
2 bytes
ADDR
4 bytes
DUMMY
8 cycles
DATA
N bytes

All fields are transferred in 8-bit STR mode on IO[7:0]. AUX carries the pop count for READ_FIXED and is zero otherwise; ADDR is the 32-bit word-aligned FPGA address. The DUMMY cycles exist so the slave can get AXI prefetch reads in flight before the data phase starts — writes skip the phase entirely.

Command Opcodes

OpcodeHexDescription
CMD_WRITE_INCR0xCABurst write, auto-increment address
CMD_WRITE_FIXED0xFERepeated write to a fixed address (FIFO push)
CMD_READ_INCR0xBABurst read, auto-increment address
CMD_READ_FIXED0xBERepeated read from a fixed address (FIFO pop)

Module Responsibilities

ModuleFileRole
octo_spi_slaveospi/rtl/octo_spi_slave.svProtocol parser FSM, dual-buffer prefetch, write accumulator
octo_spi_axi_masterospi/rtl/octo_spi_axi_master.svAXI4-Lite request/response bridge
octo_spi_topospi/rtl/octo_spi_top.svI/O synchronizer wrapper
octospi_bram_test_topeval/rtl/octospi_bram_test_top.svBoard top: PLL, reset synchronizer, 2-to-1 AXI arbiter
axi_addr_decodereval/rtl/axi_addr_decoder.svRoutes AXI transactions to BRAM A/B/C/D via addr[17:16]
axi_lite_brameval/rtl/axi_lite_bram.svPlain synchronous BRAM (BRAM C)
axi_lite_bram_fifoeval/rtl/axi_lite_bram_fifo.svCircular BRAM with a software FIFO API (BRAM D)
bram_incr_fill_mastereval/rtl/bram_incr_fill_master.svAuto-fills BRAM A with a counter on each read request
bram_fifo_mastereval/rtl/bram_fifo_master.svHardware FIFO producer for BRAM B

Paths are relative to fpga/ in the repository.

BRAM Address Map

ExampleBRAMAXI Base AddressFunction
bram_aA0x0000_0000READ_INCR — triggers an auto counter fill on read
bram_bB0x0001_0000READ_FIXED — pops from a hardware FIFO
bram_cC0x0002_0000WRITE_INCR + READ_INCR roundtrip
bram_dD0x0003_0000WRITE_FIXED + READ_FIXED software FIFO

ILA Probe Reference

Trigger position: 3000 pre-trigger + 1096 post-trigger = 4096 total depth. Clock domain: aclk at 100 MHz. Primary trigger probe: probe7 (req_valid).

ProbeWidthSignal
probe024{state[3:0], cnt[7:0], captured_byte[7:0], driving, initial_valid, prefetch_valid, word1_done}
probe132shift_out — OctoSPI shift register contents
probe232initial_buf — first AXI read response buffer
probe332prefetch_buf — prefetch read response buffer
probe48ospi_io_in — IO pins driven by the STM32
probe58ospi_io_out — IO pins driven by the FPGA
probe65{aresetn, csn_sync, sclk_sync, sclk_rise, byte_ready}
probe734{req_valid, req_read, req_addr[31:0]} ← primary trigger
probe833{rsp_valid, rsp_rdata[31:0]} — AXI read return data

FSM State Decode (probe0[23:20])

ValueStateMeaning
0ST_IDLEWaiting for CSn assertion
1ST_CMDReceiving the CMD byte
2ST_AUXReceiving the AUX bytes
3ST_ADDRReceiving the address bytes
4ST_DUMMYDummy cycles — prefetch AXI reads issued here
5ST_DATAData read/write phase
6ST_DONEReserved in state_t; unused by the current FSM

Quick Start

1

Build the FPGA bitstream

Requires Vivado 2024.x or 2025.x. Synthesis and implementation take roughly 30 minutes.

cd fpga
make bitstream   # or: make project && make gui
2

Program the FPGA

Start hw_server first, or open the Hardware Manager in the Vivado GUI.

cd fpga
make program
3

Run the STM32 tests

Requires Rust stable with the 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

Related