Using Phosphoneo: play, test, script and debug on the Neo6502
What Phosphoneo is for, in practice: playing, trying a program in one line, scripting and filming a play-through, debugging, resuming a run, testing — and what it does not measure yet.
Olimex's Neo6502 pairs a W65C02S, which runs the programs, with an RP2040 whose firmware provides everything else: display, keyboard, files, sound. Phosphoneo is our emulator of that machine, in the line of Phosphoric. It was born from a simple need: test our ports (AsteroNeo, Wolfenstein 3D, the games under way) without plugging in a board for every trial, and get the same result on every run. This article describes how we use it; what is inside fits in one section.
The video
The three-minute film: playing, trying without a screen, scripting and filming, debugging, resuming, testing — with real emulator outputs on screen. The footage comes from Phosphoneo itself; the voice is synthetic.
Why a second emulator?
Official emulator (neo) | Phosphoneo |
|---|---|
| firmware API: the official code | firmware API: the same code, linked as a library |
| home-made 6502 core, not validated | 65C02 core validated at 100% on the SingleStepTests oracle |
| timing: cycles per frame | 6.25 MHz, 109,226-cycle frame at 60 Hz, API call latency measured in co-simulation |
| few test options | headless mode, screenshots, scripted keystrokes and gamepad, traces, API log, gdb, save states |
| for playing and checking by eye | to serve as the oracle for ports, continuous integration and regression |
1. Playing: a real-time window
The simplest use: open a program in an SDL window at 60 frames per second, with keyboard, mouse, gamepad and sound. F11 toggles full screen, F12 takes a screenshot, Ctrl+F12 opens an overlay menu to load a program from the emulated USB stick, save or resume a snapshot, mute, change the scale and save the configuration.
phosphoneo --sdl --scale 3 prog.neo # fenêtre temps réel : clavier, souris, manette, son
phosphoneo --fullscreen prog.neo # plein écran 4:3
# F11 plein écran · F12 capture · Ctrl+F12 menu (charger, instantané, réglages)
2. Trying without a screen: screenshots and automatic typing
The same program can run headless for a given number of cycles. Keys are typed at a precise cycle (Enter, Escape, F1 to F8, arrows), then the screen is saved as an image or the console as text. A trial fits on one line, gives the same result every time, and drops into a test script.
phosphoneo --cycles 40000000 --type-keys '15000000:PRINT 6*7\n' \
--screenshot-text out.txt --screenshot out.ppm
A tokenised NeoBASIC program (.bas), a .neo or a binary loaded at an address (prog.bin@800) start the same way. tools/mkneo.py assembles a .neo from blocks; --storage names the folder that plays the USB stick, and --load-at loads a program from it mid-run, as the web page does.
3. Scripting a play-through, and filming it
To replay a scenario — a game, a menu, a fight — you schedule gamepad states, memory writes (teleport, set a variable) and keystrokes at precise cycles. The --record option saves picture and sound to a video file. That is how the footage in our films is shot, and how we validate a game end to end: up to 4,096 actions per run.
phosphoneo prog.neo --cycles 60000000 \
--gamepad-at 5000000:04 --gamepad-at 12000000:0 \
--poke-at 3000000:1F40=64 --record partie.avi
4. Debugging
When a program misbehaves there is plenty to look at. The API call log shows each call (group, function, parameters before and after, error code), filtered by group. The trace records each instruction with its effective address and recognises API JSRs. With the symbol file from 64tass or cc65 you can set breakpoints by name, stop before a given API call and dump the RAM, or open a terminal monitor. A gdb server is available too.
phosphoneo prog.neo --api-log api.log:3,10 --trace trace.txt
phosphoneo prog.neo --symbols prog.lbl --break-at main --tui
phosphoneo prog.neo --break-api 3,2 --break-dump ram.bin
phosphoneo prog.neo --gdb 1234 # puis : target remote :1234
5. Pausing and resuming: save states
You can freeze the emulation at a given cycle and resume it later, exactly: screen and console are identical, byte for byte, to a continuous run. Handy to skip the first three minutes of a game before testing the next part. Saving works with the same binary only, and files the program had open are reopened at their position.
phosphoneo prog.neo --save-state-at 50000000:jeu.state
phosphoneo --load-state jeu.state
6. In the browser, and with a network
The same code builds to WebAssembly: a page loads the emulator, copies a package's files into in-memory storage and starts the program. That is what Prophet's “Play” does. Programs that talk to a modem can use a software modem (Hayes and ESP-AT commands, real connections) or, in the browser, an HTTP relay. A --floppy mode finally serves a real floppy drive through the board's own file stack.
7. The tests: the reason it exists
Phosphoneo is mostly an oracle. About thirty reference screenshots are compared by fingerprint on every make test: if a change alters a pixel or a console line, you see it, and a screenshot is only re-baselined after it has been looked at. The same cases are compared with the official emulator (pixel-identical images) and replayed on the real ARM firmware in co-simulation. A project repository can reuse the screenshot script as is: AsteroNeo, our Wolfenstein 3D port and our games under way do.
8. Under the hood, briefly
Three choices explain why it can be trusted. The 65C02 processor is checked against the SingleStepTests oracle: 2,540,000 cases, state, RAM, cycles and bus accesses, 100% required. The firmware is not imitated: the API code of Trinity is compiled as is (MIT licence), with only the hardware layer written around it. And time is deterministic: 6.25 MHz, a 109,226-cycle frame at 60 Hz, a 100 Hz timer counted in emulated cycles; an API call costs 0 cycles by default, or a duration calibrated in co-simulation with --api-latency.
9. What we do not claim
Phosphoneo is a model, not the board. Not modelled: the DVI signal, USB enumeration and timing, the extension UART, I2C, SPI and GPIO; the SD card's speed is not simulated; Trinity's boot menu runs before the processor's first cycle and stays out of reach. Timing and call latency are modelled but not measured on the board: the protocol exists, it has not been run yet. A program that runs well in Phosphoneo must therefore still be tried on the board.
And the code is not public for now: this article describes the tool, it does not offer it. What you can already use are the programs it helped to tune, and the browser player that Prophet loads.
Sources & links
- Phosphoneo, the project page
- Trinity, the firmware whose API is linked
- Porting: from Astéroric to AsteroNeo (tests in Phosphoneo)
- Prophet: playing in the browser (Phosphoneo in WebAssembly)
- Phosphoric, the emulator it comes from
- SingleStepTests (processor oracle)
← Neo6502 · Neo6502 Chess → · Manoir NG → · Prophet 0.25 → · SWD → · Prophet → · AsteroNeo → · Neo6502 →