An introduction

Zero Board Computer

Working printf, fopen, and a real filesystem
on any CPU — real, emulated, or yet to be designed.

On day one. Before any hardware exists.

First, the name

Why “Zero Board Computer”?

A Single Board Computer — Raspberry Pi, BeagleBone — is a whole computer on one circuit board.

A Zero Board Computer needs zero boards.

The board is nowhere

  • The peripherals aren't on a board — they're borrowed from a host.
  • The only “hardware” the guest sees is a tiny memory window.
  • It's the working computer you have before any board exists.

A console, a filesystem, a clock — and nothing to bring up.

The six-week tax

Every new chip, board, or prototype starts the same way:

  • Can't debug firmware until the serial port works.
  • Can't test the filesystem until the flash driver works.
  • Can't validate the timer until you can read the clock back.

The first six weeks go to fighting your way to a working printf.

Not the product. Not the silicon. Just trying to see what your code is doing — redone by every team, on every project, for decades.

What is semihosting?

Borrow the host's I/O instead of building your own.

The program on the target doesn't open files or print characters itself — it asks a host machine (your laptop, the emulator, a debugger) to do it on its behalf.

ARM did this in 1993

It works — but it assumes:

  • only ARM chips,
  • only with a hardware debug probe attached,
  • only via ARM-specific trap instructions a debugger intercepts.

All three constraints come from how the target signals the host: trap instructions.

Replace the trap with a
32‑byte memory‑mapped device.

If the CPU can read and write memory — and every CPU can, by definition — it works.

What that buys you

No special instructions to define.
No debug probe to attach.
No privileged execution mode required.

The doorbell handshake

  1. Guest builds a small message in its own RAM.
  2. Writes the buffer address into RIFF_PTR.
  3. Writes any value to DOORBELL — the signal.
  4. Host does the real I/O, writes the response back into the same buffer.
  5. Host sets RESPONSE_READY; guest polls, then reads its answer.

The whole device: 32 bytes

OffsetRegisterMeaning
0x00SIGNATUREASCII "SEMIHOST" — how guests find it
0x08RIFF_PTR16 bytes: address of the message buffer
0x18DOORBELLwrite any value → trigger
0x19STATUSbitmask: TIMER / RESPONSE_READY / PROTO_ERROR
0x1AERROR_CODEprotocol errors, without touching guest RAM

The message is RIFF

A tagged container — the same family as WAV and AVI files.


RIFF  ....  SEMI                 <- container, form type "SEMI"
  CNFG  int_size ptr_size endian  <- guest declares its architecture
  CALL  opcode = 0x05 (SYS_WRITE) <- the request
    PARM  fd = 1
    DATA  "Hello, world!\n"
    PARM  length = 14
  

Host overwrites CALL with a RETN (result + errno), in place.

One subtlety: endianness

RIFF headers

Always little-endian — that's RIFF's own rule.

Payload values

The guest's declared endianness, from CNFG.

The container is fixed. The contents speak the guest's language.

The thesis

Width neutrality is the product

The guest declares its int size, pointer size, and byte order.

The host honors what the guest says.

Same protocol, different sizes

8-bit 6502

int = 2 · ptr = 2 · little-endian

64-bit, big-endian

int = 4 · ptr = 8 · big-endian

They speak the same wire protocol. They just fill in different fields.

Why it matters, concretely

A 16-bit guest can stream sequentially through a file larger than its own address space.

That's a core use case — not an edge case.

Standing on ARM's shoulders

OpName
0x01SYS_OPEN
0x05SYS_WRITE
0x06SYS_READ
0x0ASYS_SEEK
0x18SYS_EXIT
  • ZBC keeps ARM semihosting's operation numbers.
  • picolibc and newlib ports work nearly unchanged.
  • Only the transport changed: traps → memory. The vocabulary is the same.
It's not one wire

One binary, many hosts

The invariant is the guest-facing API, not the wire underneath it.

Build once against zbc_call() — the same binary runs across very different machines, each with its own transport.

Three ways to reach the host

RIFF device — the 32-byte doorbell peripheral. MAME, a patched QEMU, real silicon.
virtio — the guest carries drivers. Stock QEMU, unmodified. (next slides)
native trap — ARM-style semihosting traps. Build-time option, where the target supports it.

Runs on stock QEMU — today

No ZBC device, no QEMU patch. The guest brings the drivers:

virtio-9p

File operations, via QEMU's built-in 9P file server. No external daemon.

virtio-console

Console I/O, on the same virtqueue core.


-fsdev local,id=fs0,path=$SHARE,security_model=none \
-device virtio-9p-device,fsdev=fs0,mount_tag=zbc
  

If it speaks virtio, it works

Any machine exposing modern virtio-mmio — the library scans for it at startup.

QEMU machineArchvirtio-mmio window
virtARM / AArch640x0a000000
virtRISC-V (rv32 & rv64)0x10001000
microvmx860xfeb00000

Or QEMU's own trap semihosting

On ARM, AArch64, RISC-V, MIPS, m68k, Xtensa, a build-time shim routes everything through QEMU's -semihosting.

  • Covers the entire operation set with zero guest driver code.
  • ~10 instructions per architecture — opcode + pointer in registers, then the trap.
  • Build-time only: a trap on a non-semihosting host faults, so it's never auto-probed.

This is not a sketch

Two reference hosts, proven equal

C90 (zero heap, statically verified) and C++17 — byte-for-byte conformance-tested against each other on every CI run.

200+ machines in MAME

Drop a binary into an emulated 6502, Z80, or 68000 and it reads files off your laptop. No driver work at all.

Tested at both extremes

One test binary, two emulators, many transports

The same program runs on MAME's 6502 & i386 (RIFF) and QEMU's RISC-V, ARM, AArch64 & x86 microvm (virtio) — live, every push.

Hardened

Continuous RIFF-parser fuzzing, ASan/UBSan, optional seccomp sandbox, directory-jail backends.

Is this for you?

Bringing up a new board

Add 32 bytes of decode to your bus. Your firmware has printf, fopen, time() before the board is back from the factory.

Hobbyist with an old chip

Soldered a 6502 to perfboard? Give it a real filesystem this afternoon — the same binary runs on the physical board later.

Especially: a new chip program

Firmware starts day one against the simulator. Silicon arrival becomes an integration milestone, not a starting gun.

A quarter of calendar time recovered. Nothing proprietary introduced.

The wire format is the contract

  • One spec file is the single source of truth, byte for byte.
  • Future hosts — an FPGA core, a vendor's emulator — conform to the same format.
  • A binary written today still runs twenty years from now against any conforming host.

Semihosting stops being a per-platform reinvention.

Try it this afternoon

Vintage chip, in MAME

mame -listfull zbc*
mame zbcz80 -quik hello.bin
      
Modern target, in stock QEMU

qemu-system-riscv32 -machine virt \
  -device virtio-9p-device,...
      
  • Docs & spec: johnwbyrd.github.io/zbc · Wiki: zeroboardcomputer.com
  • License: MIT · client is C90, never calls malloc

If it can load and store, it can run ZBC.