A from-scratch cooperative real-time kernel whose tasks are Malbolge VMs. It runs on the host (LED matrix rendered as terminal ASCII) and bare-metal on the Arduino UNO Q's STM32U585, where it scrolls "Hello World from Malbolge VM" forever on the on-board 13×8 charlieplexed LED matrix.
make run # host simulation (gcc): scheduler + matrix scroll as ASCII
make arm # STM32U585 firmware -> build/arm/firmware.binmake arm needs arm-none-eabi-gcc. Output: ~3.8 KB flash, ~125 KB SRAM (one
59049-word Malbolge memory). The U585's 786 KB SRAM fits up to ~6 VMs.
Each task is one mb_vm_t (registers + a 59049-word memory). The interpreter is
a step machine, so kern_schedule_pass() advances each task by a quantum of
Malbolge cycles and moves on — no per-task stacks, no asm context switch. A task
blocks when it reads input that isn't ready (/) and resumes when it arrives; it
finishes when its program halts.
src/malbolge_vm.{h,c} freestanding VM: mb_load(), mb_step(budget, io), blocks on input
src/kernel.{h,c} task control blocks + round-robin scheduler
src/display.{h,c} 13×8 framebuffer + 5×7 text scroller (loops forever)
src/font5x7.h generated 5×7 font
src/charlieplex_map.h 104 LED pin-pairs (PF0..PF10) + 180° mount mapping
src/programs.h embedded Malbolge programs (the scroll message, a cat)
host/main_host.c host entry: renders the matrix as ASCII
port/main.c firmware entry
port/board.c clock/FPU + GPIOF charlieplex driver + SysTick scan/scroll
port/startup_stm32u585.c, port/stm32u585.ld bare-metal startup + linker script
port/cmsis/ vendored CMSIS headers (ST/Arm) for a self-contained build
The UNO Q's matrix is 104 LEDs charlieplexed across PF0..PF10, mounted 180°.
Each pixel is a {high, low} pin pair (drive high HIGH, low LOW, all others
Hi-Z). A SysTick ISR (20 kHz) multiplexes one pixel per tick (~192 Hz full
frame) and paces the scroll. The pin-pair table matches the upstream Zephyr
board device tree for the UNO Q. A Malbolge task's output bytes feed the
scroller; the text loops forever even after the program halts.
Runs on the default MSIS 4 MHz system clock — no PLL setup, nothing to mis-configure. (USART1 on PB6/PB7 is the board console if you wire one up.)
Flash build/arm/firmware.bin to 0x08000000. On the UNO Q the STM32U585 is
reached over SWD; a BOOT0-low hold across write+verify+reset is required, or the
MCU comes up in the system ROM bootloader instead of your image. The on-board
LED matrix scrolling the message is the visible proof it's running.