Several programs at once
A 6502 has only 64 KiB of memory and no protection. Yet Eliott65 runs up to eight programs on it, even those that never hand control back. Here is how, step by step.
On this page
Step 1: two programs, two memories (v0.13)
The PIO + DMA bus computes every address from a base. Switching programs is therefore simply a matter of changing the base: the 6502 finds itself in another 64 KiB block of the RP2040. Nothing is copied, not a single byte is lost. The RP2040’s memory had room for two such blocks.
To hand over control, a program calls e65_ceder: as with hibernation, the routine stores its
registers in its own bytes, then the kernel stops the 6502, changes the base and restarts it in the
other program.
Only one program has the screen and keyboard: it is said to be in the foreground. The others run in the background.
The ghost letters bug
During testing, two letters, “PR”, kept reappearing on the first program’s screen, in a place where it never wrote. Analysis showed that they were exactly the bytes being written by the other program.
The cause: the W65C02S still executes a few cycles after being asked to reset. At the moment of switching, it was already in the new program’s memory; it read there that the call had finished, left its loop and wrote into the wrong memory. The fix: put the 6502 into RESET and give it about 70 cycles in the old program’s memory, while it is only reading, and only then change memory.
Result: 168,000 switches without a single corrupted byte in either program.
Step 2: interrupting a selfish program (v0.14)
“Cooperative” multitasking assumes that each program hands over control from time to time. What if it never does? It has to be possible to interrupt it: that is pre-emptive multitasking.
The 6502 has a non-maskable interrupt, the NMI. But handling it requires code in the 6502’s
memory, and Eliott65 does not want to leave anything there. The trick: the kernel borrows 16
bytes ($FFE8–$FFF7) for the duration of the interrupt. It places a tiny handler there that pushes
registers A, X and Y (the 6502 has already pushed PC and P), notes the stack pointer, then gives the
bytes back before switching programs. The whole state of the interrupted program is on its own
stack.
On the board, first time round: a program checking its registers on every pass went through 6,129 pre-emptions and about 2.5 million checks, without a single error.
Step 3: up to 8 programs (v0.17)
Two 64 KiB blocks means two programs. Going further would have required translating addresses on every cycle (an MMU in the bus): when measured, that approach cost 34% of the speed. It was set aside in favour of another idea: swapping pages at switching time.
- The 6502’s memory is divided into 16 pages of 4 KiB.
- Waiting programs keep their pages in a pool of 30 frames of 4 KiB (31 up to v0.54: the last one now holds the shared page of zeros, which gave 4 KiB back to the firmware).
- At switching time, the non-zero pages of the active program go out, and those of the next one come in.
- A page left at zero costs nothing: it occupies no frame.
A typical program uses only 3 pages (zero page and screen, code, vectors): that leaves room for many others. The limit is set at 8 processes.
| Measurement, 5 programs at once | Value |
|---|---|
| page swapping at switching time | 307 µs |
| 6502 speed | 6.89 MHz (−5% compared with a single program) |
| register errors after 12,308 switches | 0 |
What the user saw
- A horizontal bar moving down the screen: during the swap, the video was reading memory that was being rewritten. The video now reads the foreground screen through its own page table, switched before the rewrite.
- Lost keystrokes: they were only passed to the program in a case that never arose for the shell. They are now passed when the program enters the window, and in order.
- A test program that copied itself “as long as there were not two of it” filled the machine with copies of itself: unintentional proof that eight really do fit.
Step 4: sleeping instead of yielding (v0.18)
A shell waiting for a key has nothing to do, yet it was handing over control hundreds of times a second, and each time cost a page swap. It now sleeps: it is no longer chosen until a key arrives for it or a timeout expires. A key for a sleeping foreground program wakes it immediately.
Measured effect: while waiting, 4 switches in 10 seconds instead of about 130 per second.
The shell
SHELL.E65 starts on its own if it is on the USB stick. It is a scrolling terminal, in 40 × 30 colour
or, since v0.57, in 80 × 25 with the Hercules mode’s text:
| Command | Effect |
|---|---|
NOM | runs the program NOM.E65 in the foreground |
NOM & | runs it in the background |
PS | lists the processes |
FG n | brings process n to the foreground |
KILL n | terminates process n |
DIR | lists the USB stick, page by page |
MODE 40, MODE 80 | switches to 40 × 30 colour or to 80 × 25 |
CLS, HELP | clears the screen, displays the help |
The commands have been in English since v0.57.1, at the user’s request (HELP, FG and KILL
replace AIDE, PP and TUER); the messages remain in French.
Ctrl+Tab, handled by the kernel, passes the screen and keyboard to the next
program. A hibernated machine can also be restarted in the background: ETAT.HIB &.

A flaw found later
While measuring the graphics (v0.40), a program that made system calls back to back left the kernel unable to pre-empt it: on every attempt, the kernel found a call in progress, gave up, and tried again. The order of operations was corrected (freeze, check the gate, then count the pages). See Tiles and sprites.