Reviewed-on: #4
comodore-iec-emu — IEC listener PoC
A proof-of-concept Commodore IEC listener device on a Raspberry Pi Zero 2 W. It behaves like a printer (default device address 4): it never talks back, it only listens, and it debug-prints in real time everything the C64 sends.
The timing-critical IEC handshake (ATN ack, per-bit sampling, EOI) runs in a
Linux kernel module; a Python userspace program decodes and pretty-prints.
See _plans/poc-listener-printer-PLAN.md
for the full design and _research/ for the grounding research.
Layout
kernel/ # the kernel module (real-time half) — builds on the Pi
iec_listener.c ISR, IRQ-off bit loop, listener state machine, /dev/iec0
iec_listener.h shared struct iec_record + ioctls (mirrors device.py)
iec_lines.h logical line layer, direct BCM register access (hot path)
iec_timing.h handshake timing constants
Makefile out-of-tree build + load/unload/overlay targets
dts/ device-tree overlay (pin reservation / pulls)
iecpoc/ # Python userspace (everything else) — host-testable
device.py /dev/iec0 record stream (wire format, replay)
decode.py command-byte + event decoder
petscii.py PETSCII -> display glyphs
log.py the annotated trace formatter (PLAN §8)
main.py CLI (--address, --raw, --logfile, --replay)
tests/ # host-runnable, no Pi/kernel needed
docs/ # wiring.md, kernel-notes.md
Userspace (Phase 0 — works on any host)
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/pytest # run the tests
.venv/bin/python -m tests.fixtures # (re)generate the capture
.venv/bin/python -m iecpoc.main --replay tests/data/hello_world_session.bin
On the Pi, point it at the live device instead:
.venv/bin/python -m iecpoc.main --address 4 # reads /dev/iec0
Example trace (the OPEN 1,4 / PRINT# / CLOSE session, abbreviated):
[IDLE] waiting for ATN
[ATN] asserted -> DATA low (ack)
[CMD] $24 LISTEN 4 (addressed: ME)
[CMD] $60 DATA SA=0
[ATN] released -> LISTENER
[DATA] $48 'H'
...
[DATA] $0D <CR> <EOI>
[ATN] released -> not addressed -> IDLE
Kernel module (on the Pi)
sudo apt install raspberrypi-kernel-headers
cd kernel && make
sudo insmod iec_listener.ko address=4 # creates /dev/iec0
sudo rmmod iec_listener # releases DATA on unload
See docs/wiring.md for the level-shifter circuit and the
pre-C64 bring-up checklist, and docs/kernel-notes.md
for the resolved kernel real-time decisions.
Running the frontend on the Pi
A tagged build publishes a release zip that bundles everything needed to run on a Pi without building anything:
comodore-iec-emu/
selftest.sh one-shot wiring self-test (no C64 connected)
launch.sh load module → run iecpoc → unload
pyproject.toml iecpoc packaging metadata
README.md
iecpoc/ the Python frontend
modules/ iec_listener_<kernel_version>.ko, one per supported kernel
Unzip it on the Pi and run launch.sh. It installs the frontend into a local
.venv, detects the running kernel and picks the matching .ko (by vermagic,
not filename), insmods it, runs iecpoc, and always rmmods on exit — so
the Pi is left exactly as before:
unzip comodore-iec-emu-*.zip && cd comodore-iec-emu
sudo ./selftest.sh # first: check the wiring (want 0x1F)
sudo ./launch.sh # then: load + run the frontend (address 4)
sudo ./launch.sh --address 8 # listen as device 8
sudo ./launch.sh -- --raw # forward extra args (after --) to iecpoc
The module is picked automatically; pass --ko PATH only to force a specific
file. launch.sh needs python3 plus python3-venv (and network on first run
for the build backend). See kernel/README.md for the
self-test details and the per-kernel module selection.
Status
- Phase 0 (userspace + tests): complete, runs on any host.
- Phase 1 (kernel command phase): skeleton present; needs Pi bring-up.
- Phase 2 (data-phase reception): the real experiment; tune timing on real hardware.
See PLAN.md §9 for the phased milestones.