Skip to content

Maria Jose Gavilan11/06/2025

Booting Erlang in 16 MB – A New Milestone for GRiSP Nano

GRiSP Nano

Last Monday (2 June) at Code BEAM Light Stockholm Peer opened his presentation with the question Can the BEAM fit into 16 MB? Two days later, the GRiSP Nano prototype answered with an Erlang shell prompt. That success rests on work we’ve carried out since mid-2024.

Alt text

The 16 MB Hardware Budget

Alt text

GRiSP Nano pairs an STM32U5 Cortex-M33 (3 MB internal SRAM) with 16 MB of OctoSPI DRAM. A micro-SD slot handles storage; 4 PMOD™ connectors expose SPI, I²C, and UART. Micro-USB provides console access. USB-C, with OTG support, can act as a programmable host or client. Power can be supplied through either port, and in addition, from an energy harvester.

We initially planned for 32 MB. But after the PCBs were made, a CPU erratum came to light. We had to search for a pin-compatible RAM chip with the right characteristics to keep things running. That’s how we landed at 16 MB. The constraint wasn’t planned, but it shaped the result.

ONLY 16 MB! This is fine 🔥🐶 Zero chill: embedded limits, Erlang charm, and the joy of doing more with less.

Closing the Gap

Alt text

By conference day the prototype could load most of OTP but stopped short of the prompt. The blocker was Unicode🚫. Two days later, we applied a targeted patch:

  • Added a stub module unicode_util_rtems.erl that replaces the standard unicode_util on RTEMS.
  • Adjusted the Makefile to use that stub.
  • 🔗 Patch Link

With Unicode disabled, NANO finally booted on 4 June. We were so close! ⚡️🌈

erlang
Eshell V16.0
1> ls().
.Spotlight-V100   erl_inetrc   etc
grisp.ini         manifest     nano
ok

⛏️ How We Carved Out Enough Memory ⛏️

  • Baseline: first boot managed only 7 modules with 7.4 MB free; the loader crashed soon after.
  • Crypto out + aggressive compile/link flags: removing crypto (temporarily) and building OTP with -Os -fomit-frame-pointer -ffunction-sections -fdata-sections -Wl,--gc-sections raised free memory to 8.5 MB, allowing 45 additional modules to load.
  • Strip BEAM files: running beam_lib:strip_release/1 saved a further 16 kB and squeezed in 2 more modules.
  • RTEMS tweaks + internal RAM + per-module GC: a small slice of CPU internal RAM plus a forced garbage-collection after each module bumped free space to 9.4 MB and reduced the BEAM binary to 4.1 MB, gaining 25 more modules.
  • GC sanity check: disabling the per-module GC let us load 2 modules more than step 4 with essentially the same free-memory figure.
  • Allocator surgery & deeper RTEMS/libbsd optimisations: enabling ll_alloc with custom flags, adding allocator debugging and disabling per-thread allocators pushed us to 10.2 MB free with a 2.95 MB BEAM binary (small enough to fit in internal RAM or flash!).

With those six steps complete, Unicode🚫 remained the sole blocker. Its lookup tables required an additional allocation of roughly 500 kB, which exhausted the remaining free block. Replacing those tables with the stub module unicode_util_rtems.erl removed that final obstacle and the system reached the Erlang shell prompt.

What's Next

Reaching the prompt is definitely a milestone, however, not the finish line.

Our roadmap:

  • Relocate selected code into internal RAM or Flash, and then, make internal RAM fully usable.
  • Ship kernel & stdlib “light” variants.
  • Add energy-aware boot logic for harvested-power scenarios.
  • Launch a Kickstarter to fund the first production batch later this year.
  • Develop a Unicode-light build that keeps common UTF-8 support without the full 500 kB tables.

We’ll keep sharing logs, benchmarks, and lessons learned. If running Erlang in a 16 MB footprint sparks ideas, join the Erlang Coomunity #grisp Slack channel or ⭐️ the repo, your feedback helps shape what comes next.

🔗 More details at GRiSP.org or reach out directly if you have questions.