Aiie!
Aiie! is an Apple //e emulator, written ground-up for the Teensy 4.1 (originally for the Teensy 3.6).
The name comes from a game I used to play on the Apple //e back around 1986 - Ali Baba and the Forty Thieves, published by Quality Software in 1981.
http://crpgaddict.blogspot.com/2013/07/game-103-ali-baba-and-forty-thieves-1981.html
When characters in the game did damage to each other, they exclaimed something like "HAH! JUST A SCRATCH!" or "AAARGH!" or "OH MA, I THINK ITS MY TIME" [sic]. One of these exclamations was "AIIYEEEEE!!"
Desktop version
As of 2026, there's a full desktop build of Aiie - but it's not here. Take a look [https://whimology.com/aiie/](on whimology.com) for the full releases.
Build log:
https://hackaday.io/project/19925-aiie-an-embedded-apple-e-emulator
Getting the ROMs
As with many emulators, you have to go get the ROMs yourself. I've got the ROMs that I dumped out of my Apple //e. You can probably get yours a lot easier.
There are two files that you'll need:
- apple2e.rom -- a 32k dump of the entire Apple //e ROM
- disk.rom -- a 256 byte dump of the DiskII controller ROM (16-sector P5)
The MD5 sums of those files are:
- 003a780b461c96ae3e72861ed0f4d3d9 apple2e.rom
- 2020aa1413ff77fe29353f3ee72dc295 disk.rom From those, the appropriate headers will be automatically generated by "make roms" (or any other target that relies on the ROMs).
The hard-disk card's ROM (hd32.rom), the mouse card's ROM (mouse.rom) and the parallel printer card's ROM (parallel.rom) are Aiie's own, written for the 65C02 in apple/hd32rom.s, apple/mouserom.s and apple/parallelrom.s. The assembled images are bundled, so nothing has to be downloaded or assembled to build. After changing a source, "make firmware" reassembles them; that needs ca65 and ld65 from the cc65 suite. The printer ROM keeps the Apple card's Control-I commands (I/M reset, K/O toggle the line feed, nH/nL and nJ/nN set the width and stop the screen echo, a control character becomes the new command character) and keeps Applesoft's cursor column in step with the printer so PRINT with commas, TAB( and HTAB line up on paper.
Building (for the Teensy)
The directory 'teensy' contains 'teensy.ino' - the Arduino development environment project file. You'll need to open that up and compile from within.
However.
I built this on a Mac, and I used a lot of symlinks because of limitations in the Arduino IDE. There's no reason that shouldn't work under Linux, but I have absolutely no idea what Windows will make of it. I would expect trouble. No, I won't accept pull requests that remove the symlinks and replace them with the bare files. Sorry.
Also, you'll have to build the ROM headers (above) with 'make roms' before you can build the Teensy .ino file.
If anyone knows how to make the Arduino development environment do any form of scripting that could be used to generate those headers, I'd gladly adopt that instead of forcing folks to run the Perl script via Makefile. And if you have a better way of dealing with subfolders of code, with the Teensy-specific code segregated as it is, I'm all ears...
I compile this with optimization set to "Faster" for the Teensy 4.1 at 528MHz - I've been successful underclocking it to increase the battery life. YMMV, the Teensy 4.1 will run up to 600MHz.
Environment and Libraries
I have built this with Arduino 1.8.13 and TeensyDuino 1.54b5 and Arduino 2.3.7 and Teensy 1.59.0.
https://www.pjrc.com/teensy/td_download.html
These libraries I'm using right from Teensy's environment: TimerOne; SPI; EEPROM; Time; Keypad; SdFat (previously called "SdFat-beta" but renamed in TeensyDuino 1.54).
You'll also need the ILI9341_t3n library from
https://github.com/KurtE/ILI9341_t3n/
As of this writing, the master branch does not work for Aiie; but the branch "dma_new_fix" is fine. I'd recommend checking out that branch if it exists.
Running (on the Teensy)
The reset/menu button (F10 on a keyboard) freezes the Apple //e and brings up the BIOS: one panel of settings in the style of the IIgs control panel. Up and Down pick a row, Left and Right change a value, Return opens a row that ends in "...", and Escape goes back (or, from the main list, resumes the //e).
The main list
- Resume: return to the running Apple //e.
- Reset the //e (warm): equivalent to pressing Ctrl-Reset on the real hardware. Hold both joystick buttons before selecting Reset to trigger the Apple //e self-test.
- Reboot the //e (cold): reboot the emulated machine without ejecting disks.
- Reboot and eject disks: full power-cycle: ejects all disks and reboots.
- Disk drive 1 / 2 and Hard drive 1 / 2: what is in each drive. Return opens a file browser; pick an image to insert it, or the "Eject" row at the top of the list to remove what is there. Type to narrow the list. Floppies: .dsk, .po, .nib and .woz (.nib images aren't heavily tested for write support). Hard drives: raw 32MB .img/.hdv files, or .2mg.
- Cards..., Display..., Paddles..., Network...: screens of their own, below.
- Volume: the speaker volume (0 to 15).
- CPU speed: Half (511.5 kHz), 1x (1.023 MHz), 2x (2.046 MHz) and 4x (4.092 MHz); the desktop build goes on to 8x, 16x, 128x and 256x.
- Advanced...: drop to the Apple //e monitor prompt (useful for debugging); the debug overlay, which cycles through off, FPS, memory free, paddles, PC, cycles, battery, time, disk and network; and the snapshot (see "Suspend and Restore" below). On the Teensy this is also where the firmware is updated from an AIIE.HEX on the SD card.
- About Aiie: version and build information.
Cards
Assigns cards to slots 1 through 7, or 0 to remove a card. Type a digit to put the highlighted card in that slot directly, or step through the slots with Left and Right. If two cards conflict (same slot), the other card is moved to the lowest free slot. Slot 3 only suits the Uthernet, since the //e's own 80-column firmware lives there.
- Disk II: default slot 6.
- Parallel printer: default slot 1.
- Hard disk: default slot 7.
- Mouse: off by default; works in any slot but 3.
- Mockingboard: default slot 4.
- Uthernet II: off by default.
- RamWorks: the size of the auxiliary memory expansion, in megabytes, or "not installed".
- Reset to defaults: restores the default slot assignments.
Card changes are saved and persist across power cycles. A change cannot take effect in the running machine (the //e's boot scan has already run), so once you change a slot, Resume is disabled until you Reset or Reboot.
Display
- Display type: cycles through four video modes: RGB, NTSC-like, B&W, and Mono.
- Video7 color text: whether the machine has an RGB card with the Video7 color-text extensions (see video7.md).
- Luminance cutoff: the brightness at which a B&W pixel is lit; Left/Right step it by one, - and + by sixteen.
Display modes
"Display type" has four values, and they're only really implemented for text and hi-res modes (not for lo-res modes). To describe them, I have to talk about the details of the Apple II display system.
In hires modes, the Apple II can only display certain colors in certain horizontal pixel columns. Because of how the composite video out works, the color "carries over" from one pixel to its neighbor; multiple pixels turned on in a row makes them all white. Which means that, if you're trying to display a picture in hires mode, you get color artifacts on the edges of white areas.
The Apple Color Composite Monitor had a button on it that turned on "Monochrome" mode, with the finer resolution necessary to display the pixels without the color cast. So its two display modes would be the ones I call "NTSC-like" and "Black and White."
There are two other video modes. The "RGB" mode (the default, because it's my preference) shows the color pixels as they're actually drawn in to memory. That means there can't be a solid field of, for example, orange; there can only be vertical stripes of orange with black between them.
The last mode is "Monochrome" which looks like the original "Monitor II", a black-and-green display.
Paddles
Flips either paddle axis, and plots the live paddle position in a target so you can check them.
Network
The Uthernet II card's slot, the virtual subnet the NAT hands the Apple, and the inbound ports to forward to it. On the Teensy this is also where the WiFi name and password go, with a Connect row and a live link status; on the desktop the emulator rides the host's own connection, so there is nothing to join, and a port offset lets it open privileged ports without root.
The Teensy uses a ESP-01 for network access. The ESP-01 is limited to 4 active connections - that's a combination of inbound and outbound. If you set 3 different ports for inbound traffic (which are necessary because the Apple might be using raw sockets on the Uthernet, thus we have no way to tell the ESP-01 about its incoming traffic without this configuration) you only have 1 socket available for outbound traffic. If you set four listening ports, you won't be able to connect any outbound sessions at all.
There are a couple problems with the ESP-01 as-wired in the Aiie R9 PCB. I designed the PCB many years before actually wiring up the ESP-01 so it's not surprising that I didn't quite have it right. You'll need to add a 470uF and 0.1uF capacitor (10v) right at the ESP-01's power pins; and 10uF (10v) on the 3.3v regulator output. The device does not like the ESP-01 being turned on after you've booted the Aiie - it doesn't cause damage, but will reboot the machine.
I'm unlikely to build a R10 PCB unless I get specific requests for it. Networking support on an Apple //e emulator is so esoteric - nobody's been beating down my door asking for it. But I do have other thoughts of what I might do with the core work from this project, for a new hardware build in future...
Suspend and Restore
The Teensy can be fully suspended and restored -- including what disks are inserted. It's a full VM hibernation. It currently writes to a file named "suspend.vm" in the root of the MicroSD card. (I would like to be able to select from multiple suspend/restore files eventually. It wouldn't be terribly hard; it's just that the BIOS interface is very limited.)
Building (on a Mac)
This wasn't the initial purpose of this emulator, but I've built on to it as the first test target for most functionality over the past 9 years. The SDL build works with MacOS 10.11.6 and Homebrew or newer, and you can build and run it like this:
$ make sdl
$ ./aiie-sdl /path/to/disk.dsk
As the name implies, this requires that SDL is installed and in /usr/local/lib. I've done that with Homebrew like this
$ brew install sdl2
When running, F10 enters the BIOS.
Building (on Linux)
Years ago I experimented with Aiie running under a handmade OS on a Raspberry Pi Zero W; the hardware is decent, and cheap. I just don't want Linux in the way. So I built JOSS (see my Hackaday page about JOSS).
Well, performance under JOSS is poor, so I built a Linux framebuffer wrapper for Aiie so that I can do performance testing on the Zero W, directly between JOSS and Linux.
$ make linuxfb
$ ./linuxfb
There's no guarantee that this will still build; I don't have that environment any longer, and haven't tried to maintain it. I will probably purge it from the master branch in the future and it can live on in the git history.
Building for the web or electron
You'll find a good number of #ifdefs related to EMSCRIPTEN in the code, but no WASM build here.
In 2026 I've needed a web build of this for a demonstration. Unrelated, I've been building a number of games in Electron for cross-platform support. It's not packaged here - it shares a framework with the games I've written - but the executables are available on their own site if you want to run this as a standalone application. And stay tuned, I've got a web presence for an upcoming (fall 2026) talk.
Mockingboard
The original Mockingboard ("Mockingboard A") is fully supported. By default it is installed in Slot 4, but this can be changed (or disabled) from the Cards tab in the BIOS. Both speaker audio and Mockingboard audio are mixed together in the single audio output.
VM
The virtual machine architecture is broken in half - the virtual and physical pieces. There's the root VM object (vm.h), which ties together the MMU, virtual keyboard, and virtual display.
Then there are the physical interfaces, which aren't as well organized. They exist as globals in globals.cpp:
FileManager *g_filemanager = NULL;
PhysicalDisplay *g_display = NULL;
PhysicalKeyboard *g_keyboard = NULL;
PhysicalSpeaker *g_speaker = NULL;
PhysicalPaddles *g_paddles = NULL;
There are the two globals that point to the VM and the virtual CPU:
Cpu *g_cpu = NULL;
VM *g_vm = NULL;
And there are two global configuration values that probably belong in some sort of Prefs class:
int16_t g_volume;
uint8_t g_displayType;
CPU
The CPU is a full and complete 65C02. It supports all of the 65C02 documented and undocumented opcodes:
http://www.oxyron.de/html/opcodes02.html
The timing of the CPU is close to, but not quite, correct. It doesn't count cycles due to page boundary crossings during branch instructions, for example. (See the "cycle count footnotes" in cpu.cpp.)
The CPU passes all of the the 6502 tests from here, including the undocumented ADC and SBC handling of Decimal mode and the overflow flag:
https://github.com/Klaus2m5/6502_65C02_functional_tests
Doing a
$ make test
will build the test harness and execute the three tests that encompass all others. (There are more tests in the tests/ directory - only 3 of them are truly unique.) Two of the tests should emit
All tests successful!
while the third says
Test complete. Result: passed
Caveats
This requires TeensyDuino 1.54 beta 5 or later for SdFat long file name support and raw USB keyboard scancode support (see Environment and Libraries above).
Audio runs with its own internal resampler (WSOLA-based) which should keep it sounding reasonable, even if you tell Aiie to run at a different clock speed.
The LinuxFB build is currently unmaintained and definitely broken.
Disk write protection isn't implemented.
If you don't have an SD card inserted when you turn on Aiie, you can't insert one and use it without power cycling. (The card driver is only initialized on hardware startup.)