# ADR-001 — MCU ↔ FPGA host interface: SPI slave (not AXI) **Status:** Accepted · **Date:** 2026-07-11 · **Owner:** QX-250 avionics **Applies to:** FPGA block B7 (host register interface), and downstream the Xpedition flight-controller board topology and the MCU selection. **Traces:** FPGA-QX250-001 §1 (MCU/FPGA partition), REQ-SYS-004. --- ## Context The FPGA is the deterministic hardware layer (I/O, timing, safety); the flight **control laws and mixing math** run on a general-purpose processor. Those two halves have to exchange data every control cycle: the processor reads the latest time-stamped IMU/baro sample from the FPGA and writes back four motor setpoints plus arm/heartbeat. Block B7 is that exchange. The open question is **what bus** connects the two, and — implied by it — **where the processor physically lives**: - **Option A — SPI slave.** The FPGA is an SPI *slave*; a **separate MCU** (e.g. STM32) on the board is the master. Classic coprocessor split. - **Option B — AXI-Lite.** A **soft CPU inside the FPGA** (RISC-V / Microchip Mi-V on PolarFire) runs the control laws; B7 is an AXI-Lite peripheral on the on-chip bus. No separate MCU. This is not a detail we can defer to "whatever's easy in RTL" — it sets the board (one big chip vs FPGA+MCU), the parts cost, the software toolchain, and the certification story. Hence this ADR. --- ## Decision **Adopt Option A — SPI slave — for v1.** The FPGA exposes a byte-addressable register file over a standard 4-wire SPI slave port (`host_sclk`, `host_cs_n`, `host_mosi`, `host_miso`). A conventional flight-controller MCU is the master. Keep Option B (AXI-Lite + soft CPU) as an explicitly-supported **Phase-2** path: B7's register map is defined bus-independently so the same map can later be re-fronted with an AXI-Lite shim without touching B1/B3/B5/B6. --- ## Rationale 1. **Clean, provable partition.** Keeping the CPU *off* the FPGA makes the hardware/software boundary a physical wire, not a shared on-chip bus. That is exactly the determinism/safety argument that justified adding an FPGA at all (FPGA-QX250-001 §1) — it stays crisp instead of blurring into a SoC. 2. **MCU independence.** SPI slave works with *any* master — STM32, the existing flight-controller ecosystem, or a bench host. We are not married to one FPGA vendor's soft-CPU toolchain to bring the system up. 3. **Simplicity of verification.** An SPI slave register file is small, and it can be **oversampled entirely in the `clk_sys` domain** — no second clock domain, consistent with the single-clock decision (FPGA-QX250-001 §8). AXI-Lite plus a soft CPU is far more RTL and a much larger verification surface for v1. 4. **Board simplicity now, options later.** FPGA + small MCU is a well-trodden two-chip layout for Xpedition. If we later want a single-chip avionics module, Option B is still open because the register map is bus-neutral. ## Consequences **Positive** - B7 is single-clock (oversampled SPI), reusing the B8 `cdc_sync2` synchronizers. - Register map (below) is the stable contract for software, for the Xpedition net names, and for the future AXI shim. - MCU can be chosen late; board carries an FPGA + MCU + the SPI link. **Negative / accepted trade-offs** - SPI throughput is bounded. At the **≤ 4 MHz** host-SPI ceiling this design targets (see constraint), a full 18-byte sensor read is ~40 µs — inside an 8 kHz (125 µs) control cycle, but this bus is not for bulk/vision data. Bulk paths, if ever needed, get their own channel. - Two chips instead of one (slightly more board area / BOM than a single SoC). - A future move to Option B is real work (add AXI shim + soft CPU + software port), just not *blocked* work. ## Constraints (binding on implementation) - **SPI mode 0** (CPOL=0, CPHA=0), **MSB first** — the common MCU default. - **Host SCLK ≤ 4 MHz.** B7 oversamples SCLK/CS/MOSI in the 48 MHz `clk_sys` domain; the ≥ 12× ratio keeps edge detection robust *and* leaves margin for the MISO read-back turnaround (synchronizer latency + register-mux settle before the master's next sampling edge). This ceiling is a spec item, not an accident — document it on the board and in the MCU driver. - **Framing:** first byte after CS-low = command `{rw, addr[6:0]}` (rw=1 read, rw=0 write); address **auto-increments** for every subsequent byte. Byte 0 read data returns the STATUS register "for free." - Register map is **byte-addressed**; 16-bit values are big-endian (H then L), matching the MPU-6000 convention already used in B3. ## Register map v1 (contract) | Addr | R/W | Name | Notes | |---|---|---|---| | 0x00 | R | WHOAMI | constant 0xC2 | | 0x01 | R | STATUS | b0 imu_init, b1 pll_locked, b2 armed, b3 data_valid(sticky) | | 0x02 | R | FAULT | fault flags (from B9/B6) | | 0x10–0x1D | R | IMU sample | AX,AY,AZ,TEMP,GX,GY,GZ — each H,L (14 bytes) | | 0x1E–0x21 | R | IMU_TS | 32-bit sample timestamp, big-endian | | 0x40–0x47 | W | MOTOR1..4 | 16-bit setpoint each; throttle in low 11 bits | | 0x48 | W | CONTROL | b0 arm_request | | 0x49 | W | HEARTBEAT | any write pulses the B9 watchdog refresh | Writing 0x47 (MOTOR4 low byte) pulses `setpoints_updated` so B1/B6 latch a coherent four-motor command. ## Revisit criteria Reopen this decision if any of: (a) a vision/EW payload needs high-bandwidth FPGA↔CPU transfer; (b) board area forces a single-chip avionics module; (c) the control laws are moved on-chip for latency. Any of these makes Option B the subject of ADR-002.