A VIOS build has two halves that must match:
- the eZ80 resident (
build/vios-resident.bin), built with Make and the AgonDev toolchain; - the ESP32 firmware (
firmware.bin), built with PlatformIO. It embeds the resident it was built with.
You also build the small boot stub (build/viosboot.bin) that MOS runs from the SD card. Everything below runs on the Mac and never touches the board.
Toolchains
| Tool | Version | Where it lives | Used for |
|---|---|---|---|
| AgonDev | v0.22 | .tools/agondev/ (override with AGONDEV=...) |
eZ80 clang, assembler, linker, objcopy |
| PlatformIO | any recent pio |
~/.platformio/penv/bin/pio |
ESP32 firmware |
| espressif32 platform | 6.3.0 (pinned in platformio.ini) |
downloaded by PlatformIO | Arduino-ESP32 2.0.9, GCC 8.4.0+2021r2-patch5 |
| ESP32Time | 2.0.0 (pinned) | downloaded by PlatformIO | RTC library used by the VDP |
| Python | 3.9 or newer | .venv/ |
Build checks, tests, all host tools |
| Python packages | esptool==5.1.0, pyserial==3.5 |
tools/requirements.txt |
Flashing, serial access |
| Host C compiler | system cc |
Host tests and the native test fixtures |
None of these is installed by Make, and .tools/, .venv/ and build/ are ignored by Git. A fresh clone therefore needs this one-time setup:
# Python environment
python3 -m venv .venv
.venv/bin/pip install -r tools/requirements.txt
# AgonDev v0.22: download agondev-darwin_arm64.tar.gz from the AgonDev
# releases page (github.com/AgonPlatform/agondev) and unpack it as .tools/agondev
mkdir -p .tools
tar -xzf agondev-darwin_arm64.tar.gz -C .tools
.tools/agondev/macos_remove_quarantine.sh # macOS: clear the download quarantine
ls .tools/agondev/bin/ez80-none-elf-clang
# PlatformIO (if you do not have it): see platformio.org; the project uses
# ~/.platformio/penv/bin/pio. The first build downloads the pinned packages.
Check that the unpacked directory is really named .tools/agondev, or pass AGONDEV=/path/to/agondev to every make. Keep the pinned versions. In particular, espressif32 6.4.0 is known to break VDP mode 3 by using more memory.
The VDP sources are vendored, so no network is needed for them. The ESP firmware is built from firmware/esp32-vios/vendor/agon-vdp (breakintoprogram/agon-vdp commit 61888ab, VDP 1.04) and vendor/vdp-gl (avalonbits/vdp-gl commit 48a0b9a, 1.0.3), with VIOS changes on top. Their archive URLs and SHA-256 values are in vendor/manifest.json.
The paired build
Build the resident first, then the ESP firmware, in that order and never at the same time, with the same internal baud rate:
make test # optional, but run it before, not during
make vios-resident VIOS_INTERNAL_BAUD=1152000
make viosboot
VIOS_INTERNAL_BAUD=1152000 ~/.platformio/penv/bin/pio run -d firmware/esp32-vios -e esp32-vios
These are the reasons for the order:
- The ESP build embeds the resident. A PlatformIO pre-build script,
firmware/esp32-vios/scripts/embed_vios.py, readsbuild/vios-resident.binand compiles it into the firmware's flash as constant data. It also embeds the 46-byte native bootstrapbuild/agon-stage0.bin, the CRC stagebuild/agon-crc.binand the flash stagebuild/agon-flash.bin, whichmake vios-residentbuilds too. If any is missing or out of bounds, the ESP build stops. - Both ends must agree on the internal UART rate.
VIOS_INTERNAL_BAUDsets the eZ80-to-ESP32 link: 1152000 (default), 384000, or 115200 (historical). Make records the rate inbuild/vios-resident.baud. The embed script refuses to build if that file is missing or names a different rate than the ESP build'sVIOS_INTERNAL_BAUD. The USB link to the Mac is always 115200. - Both ends must agree on the GUI.
VIOS_GUI=1(default) builds a resident whose workbench draws through the ESP display server, paired with an ESP that boots into the GUI.VIOS_GUI=0builds the older VDU text screen. Make records the choice inbuild/vios-resident.gui, and the embed script checks it against its ownVIOS_GUIenvironment variable (default 1). make testrelinks the resident. Some host tests runmake build/vios-resident.binat the default settings. Running them while the ESP build is reading the resident can embed the wrong image.
Make forces a relink when you switch rate or GUI mode, even within the same second. macOS Make compares timestamps at one-second resolution.
To embed a different validated image, set VIOS_IMAGE=/absolute/path/image.bin for the ESP build. Its .baud and .gui sidecars must sit next to it.
What the build produces
| Output | Made by | What it is |
|---|---|---|
build/vios-resident.bin |
make vios-resident |
The eZ80 resident: a MOS-format ADL image loaded at 0x040000, stamped with its build identity. About 195 KB in 0.8.0. |
build/vios-resident.elf, .map |
same | Symbols and link map. Keep the ELF of every image you install if you plan to debug over ZDI. |
build/vios-resident.baud, .gui |
same | Sidecars with the internal rate and GUI flag. Keep them with the .bin. |
build/agon-stage0.bin, agon-crc.bin, agon-flash.bin |
same | Small eZ80 stages the ESP runs over ZDI in dev mode: RAM setup (46 bytes), whole-image CRC, flash programmer. |
build/viosboot.bin |
make viosboot |
The SD boot stub, a MOS moslet at 0x0B0000 (3 KB). |
firmware/esp32-vios/.pio/build/esp32-vios/firmware.bin |
pio run ... -e esp32-vios |
The ESP32 application, at most 0x140000 bytes (one app slot). About 830 KB (849,472 bytes) in 0.8.0. |
firmware/esp32-vios/build-manifest.json |
same, post-build | Firmware size and SHA-256, the embedded resident's path, size, SHA-256, CRC-32, baud rate and GUI flag, the native stages, pinned sources and SHA-256 of every build input. |
firmware/esp32-vios/include/vios_image.json |
same, pre-build | What was embedded (path, size, SHA-256, CRC-32, baud, GUI). |
PlatformIO also produces a bootloader and a partition table as normal intermediates. They are never installed: VIOS updates write one application slot only.
The resident build stamp
After linking, tools/vios_stamp.py writes the build's identity into the binary. kernel/vios_boot.c contains a 4-byte STMP marker followed by a zero word. The tool computes the CRC-32 of the whole binary with that word still zero, and stores the result in the word (it uses 1 if the CRC happens to be 0, because 0 means "unstamped"). Restamping gives the same value. The tool fails unless there is exactly one marker. It prints the result (the value differs for every build):
build/vios-resident.bin: build stamp 1A2B3C4D
The stamp keys the boot-module cache. On a normal boot the resident reads, checks and compiles the modules of the confirmed boot bundle, then saves the compiled result as /VIOS/CACHE_A.VIC or /VIOS/CACHE_B.VIC. Later boots restore that cache instead of compiling, which takes about 0.9 s instead of about 2.2 s for five modules. The cache is used only when its key matches the build stamp, the bundle CRC and the base dictionary. A new resident therefore never restores a cache compiled by another build: its first boot compiles and rewrites the cache, which costs about 1.4 s extra once. An image loaded straight from the ELF reads stamp 0 and never uses the cache.
Versions and releases
The version lives in one place, the VERSION file at the top of the
repository (0.8.0 for this edition). make vios-resident compiles it into
the resident, whose console greets you with VIOS v0.8 and the copyright line;
the ESP32 build writes it into the firmware's boot log and into
build-manifest.json. CHANGELOG.md lists what changed in each version.
A release is a tag vX.Y.Z on main. Pushing the tag runs the GitHub Actions
workflow .github/workflows/release.yml: it checks that the tag, VERSION
and CHANGELOG.md agree, runs the host tests, builds the resident, the boot
stub and the ESP32 firmware as a matched pair, packages them with
tools/vios_release.py, builds this manual's site, and publishes the release
with three files: vios-X.Y.Z.zip, vios-X.Y.Z-SHA256SUMS and
vios-X.Y.Z-manual.zip. The step-by-step process, and how to try a release
build without publishing it, are in
docs/RELEASING.md.
Makefile targets
make with no target builds the historical all. The targets you normally use are test, vios-resident and viosboot.
| Target | Output | Purpose |
|---|---|---|
vios-resident |
build/vios-resident.bin (+ .elf, .map, .baud, .gui), build/agon-stage0.bin, build/agon-crc.bin, build/agon-flash.bin |
The production eZ80 resident and the stages the ESP firmware embeds. |
viosboot |
build/viosboot.bin |
The SD boot stub /mos/viosboot.bin. |
test |
Runs the host test suite: .venv/bin/python -m unittest discover -s tests -v. Covers the Forth system and C reference, native target code on an eZ80 interpreter, the ESP firmware components under sanitizers, the wire codec, host tools and the harness. Needs AgonDev and a host C compiler. |
|
verify-forth-engine |
build/verification/forth-engine/ |
Assembles, links and disassembles the DIRECT and TOKEN engines and runs the native ABI probes (tools/verification/forth-engine/check.sh). |
clean |
Deletes build/. |
|
vios |
build/vios.bin |
The standalone raw Forth image from the first milestone (no SD, no multiplexer). Historical reference. |
storage-probe |
build/storage-probe.bin |
A standalone SD/FAT probe image used during storage bring-up. |
all (default) |
build/monitor.bin |
The first-day C monitor in MOS format. Historical. |
raw |
build/monitor-raw.bin |
The same monitor for the raw RAM bridge. Historical. |
esp32-probes |
build/zdi-probe.bin, build/zdi-loader.bin |
ESP32 RAM helpers for ZDI bring-up and eZ80 flash backup (tools/zdi_backup.py, tools/zdi_monitor.py). Need XTENSA_CC and esptool. |
build/zdi-instruction-probe.bin |
same | A ZDI instruction-store diagnostic helper (file target, not a phony name). |
Make variables:
| Variable | Default | Meaning |
|---|---|---|
VIOS_INTERNAL_BAUD |
1152000 |
eZ80-to-ESP32 UART rate: 115200, 384000 or 1152000. Anything else is an error. Must match the ESP build. |
VIOS_GUI |
1 |
1: workbench drawn by the ESP display server (GUI boot). 0: VDU text screen. Must match the ESP build. |
VIOS_BOOT_BURST |
32 |
Blocks the boot stub requests at a time (1 to 32). |
VIOS_BOOT_TIMEOUT |
12 |
Stub wait per reply, in units of 65,536 UART polls (12 is about 0.5 s). |
AGONDEV |
.tools/agondev |
AgonDev install directory. |
XTENSA_CC |
PlatformIO's xtensa-esp32-elf-gcc |
Compiler for the esp32-probes helpers only. |
BUILD_ID |
bringup-20261016-01 |
Build string of the historical monitor. |
PlatformIO environments in firmware/esp32-vios/platformio.ini:
| Environment | Output | Purpose |
|---|---|---|
esp32-vios |
.pio/build/esp32-vios/firmware.bin, build-manifest.json |
The normal firmware. |
esp32-vios-probe |
.pio/build/esp32-vios-probe/firmware.bin, build-manifest-probe.json |
A minimal ZDI diagnostic. It has no VGA, keyboard, audio or CPU boot; it only halts the CPU and answers USB control commands. Never install it as your normal firmware. |
The ESP build reads these environment variables: VIOS_INTERNAL_BAUD (default 1152000), VIOS_GUI (default 1) and VIOS_IMAGE (default build/vios-resident.bin).
Checking a build before you install it
firmware/esp32-vios/build-manifest.jsonmust name the resident you just built. Itsembedded_vios.sha256equalsshasum -a 256 build/vios-resident.bin. Itsinternal_baudandguimust be what you intended, andsizemust be belowapp0_limit.make testpasses on a clean tree. Run it before the paired build, not during.- After installing, the boot events end with
BOOT stub runs this firmware's imageandHEALTH flag 1(see Installing).