Status, all against real hardware:
- Linux — fully tested. Build, flash, and remote reboot-to-bootloader all confirmed working.
- Windows — fully tested. Build, flash, and remote reboot-to-bootloader all confirmed working.
- macOS — fully tested. Build, flash, and remote reboot-to-bootloader all confirmed working, on Apple silicon. No driver or permission setup is needed for any of it.
Step-by-step instructions for each platform, using that platform's standard package manager so there's nothing to download by hand:
This is a fork of FPGAwars/apio, maintained for use in a logic design class. The one substantive change from upstream is a new pico target (below); everything else about apio — commands, project files, the ICE40/ECP5/GOWIN/Xilinx architectures — is unmodified.
The motivation: the class previously required an upduino3.1 FPGA board. To cut costs for students, this fork lets a project instead target a Raspberry Pi Pico, which runs a software interpretation of the same design instead of a synthesized bitstream. This is not a substitute for real FPGA hardware in general — it exists specifically so students in this class can do the same Verilog exercises on a $4 board instead of a $20+ one.
Setting board = pico in a project's apio.ini makes apio build/apio upload target a Raspberry Pi Pico (RP2040) instead of an FPGA:
-
The Pico board, RP2040 target, and uploader definitions are bundled with this fork and automatically layered over Apio's installed definitions. Pico projects do not need their own
boards.jsonc,fpgas.jsonc, orprogrammers.jsoncfiles. -
apio buildasks Yosys's CXXRTL backend to compile the selected Verilog top module into C++, adds a thin PCF-driven GPIO wrapper, and compiles it againstpico-sdkwithcmake/arm-none-eabi-g++into a.uf2. Using Yosys's maintained backend avoids a narrow, hand-written list of supported internal cells, so ordinary inferred memories, variable indexing, and multi-module designs are handled by Yosys itself. -
The generated firmware loops as fast as it can (no fixed rate — simple designs run far faster than 1kHz, complex ones slower), each iteration reading all input pins, evaluating combinational logic, checking for a clock edge, committing register updates, and writing all output pins — the same settle-then-commit model an event-driven Verilog simulator uses, just running continuously on real I/O instead of stepping through simulation time.
-
Pin mapping uses the same
.pcffile format apio already uses for ice40 boards, just reinterpreting the pin number as an RP2040 GPIO instead of an FPGA package pin. Mapping a port to the reserved pin number-1(INTERNAL_PIN) gets it a signal with no physical GPIO attached at all — forclk, this means a genuine 1kHz square wave generated by a hardware timer interrupt, so a design can be tested with no external clock source and without spending one of the Pico's comparatively scarce GPIOs on it. -
apio uploadflashes over USB. The very first flash of a given board needs the standard manual step (hold BOOTSEL while plugging in USB), since a blank board has no code running yet to talk to. After that, every upload is a normal one-command flash with no physical button press (confirmed on Linux and macOS). It tries three things in order, stopping at the first that works: copying the.uf2onto the bootloader'sRPI-RP2volume, if a board is already sitting in BOOTSEL;picotool load -f, which can reboot a running board into its bootloader on its own; and finally a small listener built into the generated firmware, which reboots the board into its bootloader when it receives a byte over the board's USB serial port. The middle step needs a udev rule on Linux (or a Zadig-installed WinUSB driver on Windows) that students are unlikely to have — without it, it fails instantly and silently and the firmware listener does the job instead. macOS needs no such setup: picotool works there out of the box, and does every upload on its own in about two seconds. -
The toolchain (
arm-none-eabi-gcc,cmake,ninja,picotool,pico-sdk,tinyusb) is installed byapio packages install, fetched directly from each project's own official releases (ARM/xPack, Kitware, Ninja, Raspberry Pi, and hathach respectively) rather than an apio-hosted mirror. Nothing is ever downloaded on its own — commands likeapio buildreport what's missing instead of fetching it mid-build.The one exception is the examples package, which
apio packages installdeliberately does not install. It is marked on-demand and is downloaded only byapio examples fetch, the single command that cannot do its job without it. Upstream instead refreshed the examples as a side effect of unrelated commands.apio examples listandapio boardsreport zero examples until something has fetched one, rather than reaching for the network. TinyUSB is a separate package because the pico-sdk release tarball ships without its git submodules, leaving its bundledlib/tinyusbempty — and the SDK reacts to that by warning and silently disabling USB support, which yields firmware that runs correctly but never enumerates over USB, breaking the remote reboot-to-bootloader.apio buildnow fails outright rather than producing such a binary.
See experimental/pico-hello/ for a minimal working example project.
Standard apio workflow, from within a project directory containing an apio.ini:
apio build # synthesize/compile
apio upload # flash the board
For the pico target, try the included example first:
cd experimental/pico-hello
apio build
apio upload
The first apio upload to a given physical board needs it held in BOOTSEL mode (hold the BOOTSEL button while plugging in USB) — a factory-fresh board has no code running yet to receive the normal reboot signal. Every apio upload after that is hands-off.