STM32MP2 bare-metal bring-up: a blogger's path to PCIe with an FPGA

The author, a longtime pairer of STM32 microcontrollers with FPGAs, describes an experiment with the STM32MP2, using an STM32MP257F-EV1 development board. The post is long overdue: the author began it around December 2025, then accidentally found a silicon bug with security impacts and had to stay quiet until ST reviewed it and put an advisory out. The post does not describe the bug; it says only that a separate blog post from the author's work will cover it.

The motivation is a pin problem. The author's earlier STM32-plus-FPGA designs reached close to a Gbps of Ethernet bandwidth from the MCU to an FPGA-side RGMII MAC, but the STM32H7 FMC needs 30+ pins if you want all the chip selects and address lines, and they are scattered around the package (the post shows the TFBGA240 package of the H750 as an example, and says the TFBGA216 is no better). The STM32H735 with an external FPGA over FMC came close but is nearly impossible to route on a low-cost 4-layer batch-fab board. H75x parts in a 0.8mm package are doable, but they have a lower max CPU speed (480 vs 550 MHz) and still burn a lot of pins. The author's dream MCU, which the author says does not exist, would be a 1 GHz Cortex-M7 or M85, probably on TSMC 28LP, with a megabyte of NOR flash or ReRAM, a megabyte of SRAM, an RGMII MAC, FPGA-style I/O banks and an AXI Chip2Chip SERDES block that needs only two differential pairs. Cortex-R parts with PCIe were considered, but they are a big jump from the familiar STM32 peripheral set, and many such parts lack crypto acceleration or hide its docs, which the author calls an absolute dealbreaker.

The STM32MP2 looked like the closest match: a full applications processor with dual Cortex-A35, auxiliary M33 and M0+ cores, a GPU/NPU the author may play with one day, and a 5 Gbps SERDES with PIPE interface that can be configured as PCIe gen1/2 x1 or USB 3 (a register bit muxes them). It keeps familiar STM32 peripherals, including the same AES-GCM and TRNG blocks the author already had drivers for. PCIe would potentially allow easy memory mapping of an FPGA from firmware over only two differential pairs. The catch is that the chip was designed to run Linux, and vendor BSPs bring u-boot, TrustZone and more. The author wanted a big microcontroller running a simple RTOS or a while(1) event loop.

The experiment's goals: build with upstream aarch64 and arm g++ and binutils, with no vendor SDKs or HALs; run code on the A35s and the M33; use no external DDR (to keep board complexity near MCU level), so all code and data live in on-die SRAM; skip TrustZone, u-boot and other baggage, running a simple event loop in EL3 from the first stage boot loader (FSBL) flash image; and get PCIe working to an FPGA under those constraints.

M33 bring-up came first. The author added a linker script and a header with peripheral addresses to the stm32-cpp library, built an LED blinky for the Cortex-M33 and loaded it over JTAG. Booting it from flash took more digging: the internal SRAM is mapped at four different physical addresses with different TrustZone settings, and the boot ROM lets you boot from only one. The author wrote a tool that adds the FSBL headers to a flat binary, plus a flasher utility that runs from SRAM and writes an image to flash. Image rules for FSBL-M: code goes in RETRAM at the boot alias 0x0e08_0000; stack and initial globals go in RETRAM on the D-side alias 0x3008_0000; the two must not overlap physically (code at 0x0e08_4141 means no data at 0x3008_4141), which takes linker script gymnastics; and there are two image slots in flash, primary at offset 0x0000_0000 and fallback at 0x0004_0000. The firmware booted in M33-TD mode, where the boot ROM hands control to the M33 right after loading the image and the M33 has full TrustZone privileges. The author chose it over A35-TD to stage the bring-up rather than make the first SoC contact also the first bare-metal Cortex-A.

The M33 bare-metal checklist, all doable in C/C++ apart from CPU configuration such as interrupts or the FPU: enable the VDDIO monitor for VDDIO1 to 4 and tell the PWR they are valid (otherwise most IOs are unavailable); optionally enable writes to backup SRAM; set SYSCFG.VDERAMCR to use the video encoder RAM as processor memory; enable all the SRAMs in the RCC (SYSRAM, SRAM1, SRAM2, backup SRAM, VDERAM, LPSRAM1 to 3); set the RISAB SECCFGR settings so EL3 secure code can reach SRAM1/2 and VDERAM; initialize the clock tree; and enable and invalidate the M33's L1 I-side and D-side caches.

The A35 was next, and it had to come before PCIe because the M33's memory map did not allow it to reach the PCIe BAR region. The author had avoided Cortex-A parts before because of the setup overhead. Starting the A35 took reading third-party library code, since the datasheet did not make it obvious: check that PWR.CPU1D1SR shows STANDBY; send a SEV via EXTI1.RPR3; reset the CPU via RCC.C1RSTCSETR; set EXTI1.SWIER3; wait for HOLD_BOOT in PWR.CPU1D1SR; then release the CPU by setting RCC.CPUBOOTCR to 2. The A35 image (FSBL-A) lives in flash at offset 0x0008_0000 for primary and 0x000c_0000 for fallback, and launches in parallel on both cores in aarch64 mode (a fuse can be blown to boot aarch32 instead). The image must be in flash: the author found no good way for the M33 to drop an image into SYSRAM for the A35, because releasing the A35 makes the boot ROM re-run, check the strap pins and load from the specified location, which is SPI NOR flash in the author's setup.

Image layout notes: it loads to the boot/secure SYSRAM alias 0x0e00_0000; it begins with a jump to the reset vector, then padding so the exception table sits on a 2 kB boundary as hardware requires (that space is currently wasted, though most low-level init could fit there); the exception table is repeated four times, with 128 bytes per handler, though handlers can jump to external code, and the author prefers the Cortex-M NVIC; for early testing every handler is an infinite loop; registers are undefined at startup, so they are all cleared, as are exception status registers; and FPU and NEON setup is needed only if used, which the author's startup code does not yet do because it only does integer work. The text available ends mid-sentence in this startup section, so the post's later results are not covered here. The author says the code is on GitHub.

Key facts

  • The author is running an STM32MP257F-EV1 board as a bare-metal 'very fast MCU': upstream aarch64/arm g++ and binutils only, no vendor SDKs or HALs, no external DDR, no TrustZone or u-boot, with the goal of PCIe to an FPGA.
  • The motivation: the STM32H7 FMC needs 30+ pins for all chip selects and address lines, while the MP2's 5 Gbps SERDES can be configured as PCIe gen1/2 x1 or USB 3 and could link an FPGA over two differential pairs.
  • M33 bring-up is done: JTAG blinky, then booting from flash via FSBL headers, with code at the RETRAM boot alias 0x0e08_0000, stack and globals at 0x3008_0000, and primary and fallback slots at flash offsets 0x0000_0000 and 0x0004_0000.
  • The A35 must be started from an FSBL-A image in flash (offsets 0x0008_0000 and 0x000c_0000), because releasing the core via RCC.CPUBOOTCR re-runs the boot ROM; the image runs on both cores in aarch64 mode.
  • The author accidentally found a silicon bug with security impacts and held the post until ST put an advisory out; the bug itself is not described in the available text.

Why it matters

The post shows what it takes to treat a Linux-class applications processor as a plain microcontroller, with no vendor BSP. For embedded engineers who pair MCUs with FPGAs, the appeal is pin count: the STM32H7 FMC needs 30+ pins and is nearly impossible to route on a 4-layer batch-fab board, while a PCIe link would need only two differential pairs. The author says the ideal part, a 1 GHz Cortex-M7 or M85 with an AXI Chip2Chip SERDES, does not exist, so the MP2 is a stand-in. The post also notes that a silicon bug with security impacts was found along the way and that ST has issued an advisory.

Who it affects

Mainly embedded and FPGA hobbyists and engineers who already use STM32 parts and want more compute or a fast link to an FPGA without designing a DDR memory system. It is also relevant to anyone using the STM32MP2 line, given the mention of a security-relevant silicon bug and an ST advisory. The post does not say which parts are affected.

How to use it

The post is a practical recipe for bare-metal bring-up. For the M33: build a flat binary, add the STM32MP2 FSBL headers, place code at the RETRAM boot alias 0x0e08_0000 and stack and globals at 0x3008_0000 without physical overlap, and flash to offset 0x0000_0000 (fallback 0x0004_0000). Then enable the VDDIO monitors, the SRAMs, the RISAB settings, the clock tree and the caches. For the A35: follow the six-step sequence (check STANDBY, SEV via EXTI1.RPR3, reset via RCC.C1RSTCSETR, set EXTI1.SWIER3, wait for HOLD_BOOT, set RCC.CPUBOOTCR to 2), and put the FSBL-A image in flash at 0x0008_0000 with a 2 kB-aligned exception table. The author says the code is on GitHub and builds on the stm32-cpp library. No price for the chip or the board is given.

How solid is it

This is a first-person engineering write-up by a hardware blogger who lays out concrete register names, addresses and steps from hands-on work. The M33 bring-up, including booting from flash, is reported as working. The visible text is truncated mid-sentence in the A35 startup section, so whether PCIe to an FPGA worked is not in the available text. No benchmarks or power figures are given for the MP2, and the author's name is not stated in the visible text.

Risks and caveats

The PCIe-to-FPGA goal is stated as a hope ('potentially'), not a result in the available text. The A35 path requires the image in flash and a full boot-ROM pass, so the M33 cannot simply hand over code. The author's current A35 startup code does not yet initialize the FPU or NEON, since only integer work is done, and the GPU/NPU is untested. The silicon bug is not described in the available text, and no advisory number, CVE or affected parts are given, so readers need ST's advisory and the author's separate post for details.

“So I decided to see if I could convince the MP2 that it was, in fact, just a very fast MCU with some oddball peripherals like PCIe and LPDDR PHYs that garden-variety MCUs don’t have.”

— The post's author, on the goal of the experiment