flyboy-byte / riscv-pico

riscv-pico

A Raspberry Pi Pico running real Linux. The RP2040 emulates a RISC-V CPU, SPI PSRAM stands in for system memory, and the Linux guest reaches back out to drive physical pins.

A $4 microcontroller with 264 KB of RAM and no memory management unit, hosting a 16 MB Linux machine that boots to a shell, edits files in nano, and lights an LED from a shell command.

264 KBReal RAM
16 MBEmulated RAM
1.42Guest MIPS
15.9 sTo root mount
400 MHzOverclocked
~$20Parts cost

The short story

It started as a question: could a Raspberry Pi Pico, a $4 board with about a quarter of a megabyte of RAM, run real Linux? Two open-source projects had already proved it could, with a RISC-V emulator running on the Pico itself. This project took their work, combined the best parts of both, and kept going until the Pico didn't need a computer at all.

The build on a breadboard: a Pico, two PSRAM chips on adapter boards, an SD card module, and an OLED showing live stats
The whole machine on one breadboard. The OLED reports on the computer it's plugged into: 16 MB across two chips, live speed, uptime.

Most of the build was one fight at a time. The desktop harness came first, so the software could be tested before any soldering. Then two hand-soldered memory chips, and a first boot to a Linux shell. Then Linux switching a real LED on and off. Then a stubborn crash that turned out to be power, not code. A PS/2 keyboard needed only wiring and a level converter, no code. A VGA cable from Home Depot and a small breakout board gave it a screen. Then the USB cable came out, and the Pico kept running on its own.

The last fix is typical of the whole project. nano, the text editor, worked fine over USB but was unusable on the VGA screen. The screen code only understood four of the commands nano sends. It was replaced with a proper VT102 terminal emulator, tested on a PC against recordings of real nano sessions, and worked on the real screen that same evening.

Build your own

About $20 of parts, no compiler needed. The README has the parts list, the wiring, and step-by-step flashing and first boot. The firmware and SD card image are prebuilt on the releases page.

What it is

Core 1 of the RP2040 runs a full RV32IMA interpreter. Two SPI PSRAM chips stand in for system memory, and an SD card holds the kernel and root filesystem. Linux 6.6 boots on top of that.

The part worth caring about is not that Linux boots. It is that the emulated guest can reach real hardware. Typing echo 1 > /sys/class/gpio/gpio512/value into a shell inside the emulator ends with current flowing out of a physical pin.

RISC-V LINUX GUEST — EMULATED shell BASIC Lua 5.4 nano 7.2 c4 sysinfo Linux 6.6 · nommu · RV32IMA drivers: gpio-tinyrv32 · block · console CSR instructions — the whole seam RP2040 @ 400 MHz — REAL SILICON core 1 tiny-rv32ima interpreter core 0 CSR handlers · console · OLED 2× PSRAM16 MB = system RAM microSDkernel + 60 MB rootfs GPIO pinsLEDs, buttons SSD1306live stats panel console I/OUSB · PS/2 · VGA Linux sees ordinary devices. The RP2040 does the translating underneath.
Everything the guest can do to the outside world crosses one dashed line. That seam is a handful of custom CSR registers, which is why adding a new peripheral means writing a Linux driver on one side and a handler on the other, rather than modifying the emulator core.

Where the memory comes from

This is the trick the whole project rests on. The RP2040 has 264 KB of SRAM and the guest thinks it has 16 MB, so guest memory lives on two serial PSRAM chips and the RP2040's own SRAM becomes a cache in front of them.

guest RAM flat 0 – 16 MB cache in RP2040 SRAM 264 KB, total SPI 20 MHz chip 1 · CS GP13 8 MB · 0 – 8 M chip 2 · CS GP14 8 MB · 8 – 16 M addr picks the chip a chip that fails at 50 MHz and passes at 20 is a signal-integrity problem, not a dead chip
The chip-select split is address-based, so the two chips present as one flat 16 MB space. That boundary was verified on hardware by measurement rather than inference, because a modulo that silently wraps would look exactly like working memory until something important landed above 8 MB.

The guest-to-firmware interface

Every capability the guest has beyond raw computation is a custom control register. The emulator core falls through to a handler for any register it does not recognise, which is why most of these needed no changes to the vendored emulator at all.

CSRWhat it carriesGuest sees
0x150–0x155Block device, the root filesystemroot=fe00
0x170Hibernate and snapshot—
0x180–0x183Bit-banged SPI busSPI master
0x1a0–0x1a3GPIO pin select and operation/dev/gpiochip0
0x141–0x142Second console channel, kept for the planned guest-driven status panelhvc1
Why this shape was kept The framing that stuck is that this is not "Linux boots on a Pico" so much as a paravirtualised RISC-V machine hosted by an RP2040. Linux sees standard subsystems, a real gpiochip with the GPIO_V2 uAPI rather than a bespoke character device, and the custom transport stays underneath where userspace never meets it. That is what lets stock libgpiod tools work unmodified.

Pin and wiring map

Every GPIO the Pico exposes is assigned. The four pins that look free in hw_config.h are the guest's own GPIO lines, set separately in hal_csr.h. Values below are read from those two files, not from the upstream README.

Pico HRP2040top view · USB up SD CS · GP0 1 Guest GPIO 0 · LED · GP1 2 GND 3 SD SCK · GP2 4 SD MOSI · GP3 5 SD MISO · GP4 6 Guest SPI CS · GP5 7 GND 8 Guest SPI SCK · GP6 9 Guest SPI MOSI · GP7 10 Guest SPI MISO · GP8 11 Guest GPIO 1 · GP9 12 GND 13 PSRAM SCK · GP10 14 PSRAM MOSI · GP11 15 PSRAM MISO · GP12 16 PSRAM CS 1 · GP13 17 GND 18 PSRAM CS 2 · GP14 19 Guest GPIO 2 · GP15 20 VBUS · 5 V from USB 40 VSYS · 5 V from supply 39 GND 38 3V3 EN 37 3V3 OUT · PSRAM + converter LV 36 ADC VREF 35 GP28 · OLED SDA 34 AGND 33 GP27 · PS/2 CLK 32 GP26 · PS/2 DATA 31 RUN 30 GP22 · Guest GPIO 3 29 GND 28 GP21 · OLED SCL 27 GP20 · VGA BLUE 26 GP19 · VGA GREEN 25 GP18 · VGA RED 24 GND 23 GP17 · VGA HSYNC 22 GP16 · VGA VSYNC 21
PSRAM microSD VGA OLED PS/2 Guest GPIO Guest SPI Power, ground, system
Physical pin numbers sit inside the board outline, GPIO numbers beside the function. Pin 1 is top left beside the USB connector; the right side counts upward from pin 21 at the bottom.

Device by device

DevicePico pinsWatch out for
2× PSRAMSCK GP10 · MOSI GP11 · MISO GP12
CS GP13 chip 1 · GP14 chip 2
SIO2 and SIO3 pulled high through one shared 10 kΩ. 100 nF at each chip. 20 MHz on a breadboard.
microSD moduleCS GP0 · SCK GP2
MOSI GP3 · MISO GP4
MOSI and CLK cross over between the module header and the Pico. 4 to 32 GB, FAT32 only.
SSD1306 OLEDSDA GP28 · SCL GP21VCC from the separate 3.3 V supply, not pin 36. Address 0x3C, occasionally 0x3D.
PS/2 keyboardDATA GP26 · CLK GP27
through a BSS138 converter
5 V bus. Converter HV to the keyboard's 5 V, LV to pin 36. Meter the LV outputs before connecting.
VGA, PCB_0024VSYNC GP16 · HSYNC GP17
R GP18 · G GP19 · B GP20 · GND pin 23
Use the breakout's outer 3.3 V column. Green and blue follow red as one PIO group.
Guest GPIOlines 0–3 = GP1 · GP9 · GP15 · GP22Series resistor on any LED. Line 0, physical pin 2, is the demo LED and appears as gpio512.
Guest SPICS GP5 · SCK GP6
MOSI GP7 · MISO GP8
Driven by the guest through CSRs. Not spare pins, even though they look idle.

Power and ground

Two peripherals sharing no GPIO pins still crashed the machine, because they shared a supply node. The layout below is what fixed it, and every peripheral since has followed it.

Pico 3V3 OUT pin 36 · regulated from VSYS MB102 supply 3.3 V rail MB102 supply 5 V rail quiet rail, PSRAM only switching loads kept off it keyboard inrush kept off 3V3 2× PSRAM VCC · 10 kΩ pull-ups converter LV side, about 1 mA microSD module VCC SSD1306 OLED VCC PS/2 keyboard VCC, brown wire converter HV · Pico VSYS pin 39 every ground meets at one junction at the Pico · PSRAM returns on its own GND pin
The Pico's own 3.3 V output carries almost nothing but PSRAM, so switching current from the SD card, the OLED and the keyboard never rides on the chips' supply or their pull-up reference. VGA draws no power at all; it only shares ground through the breakout.

Status

What is verified on real silicon, against what merely compiles.

StateCapabilityEvidence
WORKINGLinux boots on a real PicoShell in under a minute, root mounted at 15.9 s
WORKINGGPIO from Linux to real pinsCharacter device and sysfs both. Verified lighting an LED
WORKINGReal softwareTiny BASIC, Lua 5.4.7, GNU nano 7.2, sysinfo, and minesweeper on the VGA screen
WORKINGCustom kernel driversBlock, GPIO, second console. Real drivers, not shims
WORKINGDesktop harnessSame kernel boots on a PC in about a second
WORKINGSSD1306 status panelLive MIPS, uptime and RAM at 2 Hz
WORKINGPS/2 keyboard consoleVerified 2026-09-11. Typed into nano on the hardware
WORKINGVGA consoleThrough a PCB_0024 breakout. No firmware change
WORKINGnano on the VGA screenVerified 2026-09-16. Firmware v6 rewrote the VGA terminal as a VT102 and sizes the console to 53×30
WORKINGFully standaloneOwn keyboard, screen and power. A file edited, saved and read back with no PC attached
WORKINGProgramming on the PicoVerified 2026-09-16: Lua with sleep, GPIO from Lua and the shell, c4 compiling and running C, BASIC's BYE, and minesweeper
BY CHOICENetworking on the PicoRemoved from the kernel to free about 1 MB of RAM. Still in the harness networking image
PENDINGRP2350 boardsBuilds clean for all four targets. Never flashed
BY CHOICEI²C, PWM and ADC for the guestScoped on paper and deliberately not built

How it got here

Roughly four weeks from a vendored checkout to a standalone machine with its own keyboard and screen. The dates are the order things were actually proven, not planned.

2026-08-15Desktop harness boots to a shell. The real emulator core compiled against stand-in hardware headers, so every later change could be tested without a Pico.
2026-08-15Cross-compiler built and proven. Compile, inject into the rootfs, boot, run. Roughly two hours of build for a toolchain that has been reused ever since.
2026-08-16GNU nano runs. A real full-screen editor, which meant terminal handling and the C library were genuinely correct rather than approximately correct.
2026-08-25Both PSRAM chips graded good. The firmware's own JEDEC ID check turned out to be the fastest way to grade hand-soldered parts.
2026-08-27First Linux boot on real hardware. Two chips wired at once for 16 MB, SD card attached, shell on the Pico itself. nano ran and saved a file.
2026-08-29GPIO end to end. A shell command inside the emulator lit a physical LED, through a custom CSR, the emulator, and an RP2040 pin.
2026-08-29A power bug found and fixed. The OLED destabilised the PSRAM bus through a shared 3V3 node. Resolved with a separate supply and a single-point ground tie, which has been the template for every peripheral since.
2026-09-11PS/2 keyboard works. First real keyboard input to the emulated machine, with no firmware change needed. The driver was already compiled in and feeding the shared input queue.
2026-09-14VGA works, and the machine stands alone. Six jumpers to a breakout board, first picture on the first boot. Then USB unplugged, power from its own supply, and a file written in nano, saved and read back with no computer anywhere.
2026-09-16Programs written on the machine run on the machine. The no-network kernel booted on hardware; Lua blinked an LED and minesweeper played on the VGA screen. The same test showed nano was unusable over VGA: the terminal code understood four escape sequences. It was rewritten as a VT102 that same day, and nano worked on the real screen that evening.

What's next

The project is on hold, and it counts as done for now. Ideas for whenever it picks back up:

Explicitly not on the list: turning this into a framework, a build system, or a general-purpose emulator project. It is a bring-up and porting workbench and the scope fence is written into the repo.

Standing on

This repo forks and glues; the hard work is upstream.

ProjectWhat it gave this repo
tvlad1234/pico-rv32imaThe fork point, actively maintained, plus the VGA and PS/2 console code
tvlad1234/tiny-rv32imaThe emulator core everything runs on
ElectroBoy404NotFound/pico-linuxThe multi-chip PSRAM approach
cnlohr/mini-rv32imaThe original RV32IMA interpreter all of the above descend from
xhackerustc/uc-rv32imaThe cache implementation this actually runs
rswier/c4 via tvlad1234/c4The C compiler that runs on the machine itself

Star their repositories, not this one.