Pokémon Emerald runs natively on a $6 RP2350 microcontroller — no emulator
A developer recompiled the Pokémon Emerald decompilation from ARMv4T to Cortex-M33 and reimplemented the GBA's video hardware in software on the second core of an RP2350. Full game, 60 fps HDMI output, saves to flash.

Running a full Game Boy Advance game on a $6 microcontroller without an emulator sounds like a party trick. Matt Deeds' pokeemerald-rp2350 makes it real: Pokémon Emerald, recompiled from the pret decompilation's ARMv4T to Cortex-M33, running natively on a WeAct Studio Core2350B (RP2350B, 16 MB QSPI flash). The entire game — code, graphics, maps, music — is an 11.7 MB image executed in place from flash.
How it works
The port piggybacks on tripplyons/pokeemerald-wasm, which had already fenced every dependency on real GBA hardware behind #if WASM. Reusing those seams as #if WASM || RP2350 meant the de-hardwaring work was already done; this port only had to add the MCU-specific half.
The GBA memory map survives. The game still writes DISPCNT, VRAM, OAM and palette RAM at their original addresses — those are just plain SRAM now. Live RAM is ~378 KB of the RP2350's 520 KB. ROM lives in QSPI flash via XIP, with the game's 0x08000000 base remapped to 0x10000000.
The PPU is a software rasteriser on core 1, ported from the WASM host's web/app.js and validated byte-exact against it. The first working version ran at 909 ms/frame; per-frame palette LUTs, per-scanline window masks, 8-pixel tile spans, and incremental affine stepping brought that to 60 fps — about 45× faster. Specialised inner loops then cut typical scenes to ~12 ms.
Scanout needs no CPU at all. A DMA control-block ring feeds the HSTX serialiser a full 525-line frame with the CPU never touching the re-arm path. The interrupt is advisory. This is what makes the display immune to the multi-millisecond stalls that flash writes cause during saves — and it took killing an ~0.8-blink-per-minute HDMI dropout to get there.
Status and limitations
Playable start to finish as far as it has been tested: boots, plays the intro, starts a new game, walks the overworld, battles, and saves. Every scene holds a locked 60 fps except the OBJ-window intro cinematic. The honest list of what's missing:
- Audio is incomplete. The m4a engine is ported and music plays, but DPCM and reverse-playback instruments are silent stubs. Mixing is coupled to the frame rate, so any scene below 60 fps underruns.
- No link cable or wireless. The RFU and multiboot paths are stubbed. Trading, battling, and the Mystery Gift download-code path do not work.
- The RTC is a dead cartridge clock. Time reads as zero and the in-game clock-set screen reports "clock is stopped." Berry growth and other time-of-day events are affected.
- The OBJ-window intro cinematic runs at 20–25 ms/frame, below 60 fps. It is the one scene with no fast path.
Build and validation
The build is straightforward: make tools && make modern, then a few scripts to generate sound assets and build the game, then CMake/Ninja for the Pico SDK project. The repo also builds standalone bring-up targets (hstx_test, psram_test, i2s_test, display_test, ppu_display_test, emerald_hwtest) used to validate each subsystem on silicon before integration.
The WASM build is deliberately retained as the PPU reference. rp2350/ppu_validate.sh runs the game in a browser, dumps GBA memory plus reference frames from the JavaScript rasteriser, renders the same state with rp2350/ppu.c, and pixel-diffs the two. That harness is the only reason the PPU can be called byte-exact, and it caught real bugs during every optimisation pass.
The rp2350/ directory is original work under MIT; the rest is the pret decompilation with no license from this project. No ROM is required or included.
The GBA memory map survives. The game still writes DISPCNT, VRAM, OAM and palette RAM at their original addresses — those are just plain SRAM now.
Source: GitHub