Deep dive · FPGA gateware

The FPGA gateware layer

Under the onboard flight software sits a deterministic FPGA gateware layer — the "when" to the software's "what." Eight SystemVerilog blocks guarantee jitter-free IMU sampling, four bit-exact DShot600 motor streams, closed-loop motor-RPM decode, and a hardware failsafe the software cannot override. Written vendor-neutral for Microchip PolarFire, verified, and ready for Siemens Questa.

FPGA: 8 SystemVerilog blocks Verify: Questa (Siemens EDA) Formal safety SVA Target: Microchip PolarFire

The partition

Software owns what. The FPGA owns when.

Control laws are math that changes with tuning and mission — they belong in software. Sub-microsecond timing, four synchronized motor waveforms and a fail-safe that must hold even if the software hangs are hardware guarantees — they belong in an FPGA. Splitting them cleanly is what makes the vehicle both flexible and safe.

Software — the "what"

The onboard flight software — control laws, mixing and mode/failsafe logic — validated end-to-end in the Amesim 6-DOF model (position hold, gust rejection, waypoint mission). It sets the motor targets; it never touches the wire timing.

FPGA — the "when"

A single-clock (48 MHz) gateware layer that samples the IMU on an exact 8 kHz beat, emits four bit-exact DShot600 streams, timestamps every sample, and enforces a hardware arm/failsafe that software cannot override. Written vendor-neutral for Microchip PolarFire.

FPGA · Siemens EDA

The deterministic gateware layer

Eight SystemVerilog blocks, all on a single 48 MHz clock so DShot600 lands on exact integer cycle counts (bit = 80, T1H = 60, T0H = 30 — zero timing rounding). Each block ships with a self-checking testbench; because Questa wasn't yet installed when they were written, each was also pre-validated against a cycle-accurate Python model before being marked ready.

BlockFunctionVerification
B1DShot600 TX ×4 → DSHOT_M1..M4 (bit-exact serializer + CRC)TB rebuilds each frame from pin HIGH-widths · Python model, 7 vectors PASS
B3IMU SPI — MPU-6000 (mode-3, 8 MHz; config writes + 14-byte burst read)TB vs. an MPU-6000 bus-functional model · edge-level Python model PASS
B5Sample-sync — 8 kHz IMU / 50 Hz baro / 1 µs ticks + 32-bit timestampTB checks tick periods + timestamp latch
B7Host register file — SPI slave (the MCU↔FPGA bus)MCU-side SPI-master BFM exercises the map · Python protocol model, 13 checks PASS
B6+B9Safety interlock + watchdog — hardware arm/failsafe, LED/buzzerFSM TB, 14 checks + invariant · two concurrent formal SVA properties
B8Clock / reset / PLL — 48 MHz clk_sys, sync'd resetTB checks period, lock-gated release, 2-flop CDC latency
B2eRPM decode — bidirectional-DShot GCR / CRC decode datapath (closed-loop motor RPM)GCR/CRC round-trip model, 16 vectors + reject PASS
B4Barometer I2C — DPS310 (400 kHz open-drain, config + pressure/temp read)byte/ACK sequence PASS · ⚠ bring-up (I2C bit-phase to confirm in sim)
The safety property is formal. The interlock proves two assertions with Questa formal, not just simulation: !armed ⇒ all four motor outputs = 0, and any motor hot ⇒ armed. Motors are forced disarmed unless the vehicle is armed and the host heartbeat is fresh and the RC link is valid — a guarantee the flight software cannot override.

The seam — HW ↔ SW

One SPI register interface (ADR-001)

The MCU talks to the FPGA over a plain SPI-slave register file — deliberately not AXI + a soft-CPU — for a clean physical HW/SW split, MCU-vendor independence and a small verification surface (ADR-001). Software writes motor setpoints and reads sensor data + timestamps; the FPGA answers within a single clock.

AddressRegisterDir
0x00WHOAMI (returns 0xC2)read
0x01 / 0x02STATUS / FAULTread
0x10–0x1DIMU sample (accel / temp / gyro, ×7 hi/lo)read
0x1E–0x21Sample timestamp (32-bit)read
0x40–0x47Motor 1–4 setpointswrite
0x48CONTROL (bit 0 = arm)write
0x49HEARTBEAT (feeds the watchdog)write
Honest scope. The RTL and testbenches are complete and every block passes its cycle-accurate Python pre-validation. Questa waveform / transcript screenshots are pending the tool install — the first action when Siemens Questa lands is vsim -c -do run_all.do, which regresses all eight blocks (each ending RESULT: PASS; B4 baro is still in bring-up). This page will get the real in-tool captures then — the same discipline as the Amesim plots.

Where it sits in the E/E design: the FPGA slots between the flight controller and the ESC, driving the existing DSHOT_M1..M4 nets and reading the IMU/baro buses. Adding it as its own device to the AI-generated Capital schematic — so the compute layer joins the same electrical thread — is the next electrical step (today's Capital design carries the ESC, dual flight controllers and power rails, not yet the FPGA).

The code

Real RTL you can read

This isn't pseudocode — it's the actual SystemVerilog the agent wrote: self-documenting, vendor-neutral, and verified. Two excerpts below; every block's full source is one click away.

safety_interlock.sv · blocks B6 + B9 View full file ↗
// The hardware safety gate. Motor outputs are forced to DShot "0" unless the
// system is ARMED — and it can only be ARMED while ALL of: the MCU arm-request,
// a fresh MCU heartbeat, and a valid RC link. Lose any one and it disarms
// instantly and latches a lockout (no silent re-arm on a flickering link).
//
// SAFETY INVARIANT (formal-proven):  (armed == 0) -> (all motors == 0)
// The motor gate is combinational from `armed`, so it holds by construction:

assign motor_out1 = armed ? motor_in1 : 16'd0;
assign motor_out2 = armed ? motor_in2 : 16'd0;
assign motor_out3 = armed ? motor_in3 : 16'd0;
assign motor_out4 = armed ? motor_in4 : 16'd0;

The entire hardware failsafe is those four lines: motors can be non-zero only while armed — and the arm FSM above them sets armed only when the MCU, the heartbeat and the RC link all agree. That's why the safety property is provable, not merely tested.

dshot600_tx.sv · block B1 View full file ↗
// Deterministic DShot600 serializer for one BLHeli_32 ESC channel.
// Frame = 16 bits, MSB-first: [15:5] throttle · [4] telemetry · [3:0] CRC.
// Each bit is one BIT period, held HIGH for T1H (a "1") or T0H (a "0").
// DShot600:  T = 1667 ns,  T1H = 1250 ns,  T0H = 625 ns.
// At 48 MHz these land on EXACT integer cycle counts — zero timing error:

localparam int BIT_TICKS = CLK_HZ / 600_000;      // 80  -> 1666.7 ns
localparam int T1H_TICKS = (BIT_TICKS * 3) / 4;   // 60  -> 1250 ns
localparam int T0H_TICKS = (BIT_TICKS * 3) / 8;   // 30  -> 625 ns

Choosing a 48 MHz clock makes the DShot600 pulse widths fall on exact cycle counts — no rounding, no jitter. This is the determinism the FPGA exists to give.

Browse the whole gateware. All 14 SystemVerilog sources (8 functional blocks + support), plus the host-interface decision record and the run guide: dshot600_tx_x4 · imu_spi · spi_slave_regfile · erpm_decode · baro_i2c · sample_sync · clk_reset · README · ADR-001