Building

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, reads build/vios-resident.bin and compiles it into the firmware's flash as constant data. It also embeds the 46-byte native bootstrap build/agon-stage0.bin, the CRC stage build/agon-crc.bin and the flash stage build/agon-flash.bin, which make vios-resident builds too. If any is missing or out of bounds, the ESP build stops.
  • Both ends must agree on the internal UART rate. VIOS_INTERNAL_BAUD sets the eZ80-to-ESP32 link: 1152000 (default), 384000, or 115200 (historical). Make records the rate in build/vios-resident.baud. The embed script refuses to build if that file is missing or names a different rate than the ESP build's VIOS_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=0 builds the older VDU text screen. Make records the choice in build/vios-resident.gui, and the embed script checks it against its own VIOS_GUI environment variable (default 1).
  • make test relinks the resident. Some host tests run make build/vios-resident.bin at 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.json must name the resident you just built. Its embedded_vios.sha256 equals shasum -a 256 build/vios-resident.bin. Its internal_baud and gui must be what you intended, and size must be below app0_limit.
  • make test passes 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 image and HEALTH flag 1 (see Installing).