Follow-up to the AtomVM research session. This is the first code pass — directory layout, runtime glue, platform shim, build flow. Nothing has been built or run end-to-end. The point of this session was to commit a project skeleton with real source files that map onto AtomVM’s actual platform interface, so the next session is just “make cmake happy” instead of “decide where every file goes.”
What landed
projects/68_atomvm_port/ now has:
src/top.sv— verbatim copy of P64. No RTL edits.runtime/start.S— reset glue: stack, BSS clear, mtvec to a panic stub, call intomain(). Same shape as P43.runtime/link.ld— 8 MiB code/data/heap region; AVM blobs at0x00800000(lib.avm) and0x00C00000(main.avm). Linker symbolsLIB_AVM_ADDR/MAIN_AVM_ADDRgive the C side fixed addresses to point AtomVM’s avmpack reader at.port/p68_libc.c— newlib syscall stubs. Real implementations for_write(UART loop),_sbrk(bump allocator over linker- defined heap window),_gettimeofday(mtime / 25),_exit(write to MMIO_HALT). Stubs for the rest.port/p68_sys.c— AtomVM platform interface. Minimum surface forglobalcontext_run:sys_init_platform/sys_free_platform,sys_monotonic_time*,sys_time,sys_poll_events(busy loop onmtime),sys_signal(no-op), listener and select-event stubs, AVMPack file-loader stubs (we only support the in-memory path),sys_create_portreturning NULL,nif_collection_init_alletc. no-ops.app/main.c— direct port ofsrc/platforms/rp2/src/main.cwith the Pico-SDK includes ripped out, the LIB_AVM/MAIN_AVM addresses retargeted to our linker symbols, and the abort-on-failure path retargeted to MMIO_HALT.erlang/hello.erl— six-line Erlang module.start/0callserlang:display/1. Picked overio:format/1deliberately —io:formatlives in atomvmlib and we want the smallest possible smoke test before pulling that in.test/Makefile— build orchestration. Pinned AtomVM commit7b2821593d9e8022efc1b84919b71d3fcd01cb82(HEAD on 2026-05-04). Targets:vendor-fetch,libatomvm-config,libatomvm-build,beam,packbeam,all,verilator-run.README.md— what each file is, what works, what doesn’t, what’s still TODO.
What does not work yet
PASS / FAIL / NOT RUN labels:
| step | status |
|---|---|
| Directory + file layout | PASS |
runtime/, port/, app/ source written | PASS |
vendor-fetch (clones AtomVM at pinned commit) | NOT RUN |
libatomvm-build (cmake + ninja) | NOT RUN |
beam (erlc hello.erl) | NOT RUN — no erlc on this devshell |
packbeam | NOT RUN |
| Bare-metal ELF link | NOT RUN |
| Boot blob assembly | NOT RUN |
| Sim shows AtomVM banner over UART | NOT RUN |
The test/Makefile is written under the assumption that:
riscv64-elf-gcc(the newlib toolchain, not the Linux multilib our other projects use) is on PATH.cmake,ninja,erlc, and the AtomVM-builtpackbeambinary are on PATH.
Neither is true on this nix devshell yet. flake.nix will need
pkgs.pkgsCross.riscv64-embedded.buildPackages.gcc, pkgs.cmake,
pkgs.ninja, and pkgs.erlang added — left as a TODO for the
user / next session.
The thing I expect to bite us first
The cmake invocation. AtomVM’s main CMakeLists.txt and its
platform CMakeLists for rp2 jointly force a bunch of HAVE_*
feature flags off (mkfifo, getcwd, pread, pwrite, ftruncate, …)
because newlib declares them but doesn’t implement them. Our
test/Makefile cmake invocation does not force any of those.
First config pass will likely fail with “feature test compiled
but won’t link.” We’ll need to mirror the rp2 forcing list, or
factor it into a CMake toolchain file.
Second-most-likely problem: the riscv32-unknown-elf triple.
nixpkgs pkgsCross.riscv64-embedded builds a riscv64-none-elf-
prefix, not riscv64-elf-; our P43 Makefile uses riscv64-elf-gcc
which works on this host because of an mise-installed toolchain.
Need to verify and standardize.
Decision items not made yet
lib.avm: shipping atomvmlib alongside our application meansio:format/1works, but it’s ~200 KB of additional packbeam to load. For astart() -> erlang:display("...").smoke test we don’t need it. Left optional in the link.ld + Makefile.- Heap size: linker script gives roughly 6-7 MiB for newlib’s malloc. AtomVM’s per-process heap defaults aren’t huge; this should be plenty for hello-world but is undersized for any benchmark with real allocation pressure. Revisit when we actually want fib(35).
- Trap handling:
runtime/start.Sinstalls a panic-and-halt trap handler. Erlang code shouldn’t trap in normal operation, but the moment we want preemption (timer ticks driving the scheduler) we’ll need a real one. Out of scope for hello-world.
Next session
In rough order:
- Update
flake.nixto addpkgs.erlangand the bare-metal RV32 newlib toolchain. Re-runmake check-tools. Update00_environment/TOOLCHAIN_REPORT.md. make vendor-fetch && make libatomvm-config. Capture cmake output. Iterate onHAVE_*feature flags until configure succeeds.make libatomvm-build. Capture link errors against newlib; the rp2 platform’s special-cases should be a guide.make beam && make packbeam. Confirm the magic bytes match whatavmpack_is_validexpects.- Try
make all. Expect undefined refs at link time, fix inport/p68_sys.corport/p68_libc.c. make verilator-run. First UART line should be the AtomVM banner. If we get to “Found startup beam: hello” we’ve already won.
What just happened?
Wrote a project skeleton that’s the right shape for the next
session to push on. The platform shim mirrors AtomVM’s rp2/sys.c
function-by-function, with everything that depends on Pico SDK or
multi-hart hardware replaced by a stub or our chip’s MMIO. None of
it compiles yet — the AtomVM source tree isn’t checked out, the
toolchain isn’t in the devshell, and we haven’t tried cmake. But
the layout is committed, so the next pass is a debugging exercise
on a known-shape problem instead of a design exercise.