apple | ||
audio | ||
schematics | ||
sdl | ||
teensy | ||
util | ||
.gitignore | ||
bios-font.h | ||
cpu.cpp | ||
cpu.h | ||
display-bg.h | ||
filemanager.h | ||
globals.cpp | ||
globals.h | ||
lcg.cpp | ||
lcg.h | ||
Makefile | ||
mmu.h | ||
physicaldisplay.h | ||
physicalkeyboard.h | ||
physicalpaddles.h | ||
physicalprinter.h | ||
physicalspeaker.h | ||
README.md | ||
RingBuffer.cpp | ||
RingBuffer.h | ||
vm.h | ||
vmdisplay.h | ||
vmkeyboard.h |
Aiie!
Aiie! is an Apple //e emulator, written ground-up 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!!"
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 three 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)
- parallel.rom -- a 256 byte dump of the Apple Parallel Card
The MD5 sums of those files are:
- 003a780b461c96ae3e72861ed0f4d3d9 apple2e.rom
- 2020aa1413ff77fe29353f3ee72dc295 disk.rom
- 5902996f16dc78fc013f6e1db14805b3 parallel.rom
From those, the appropriate headers will be automatically generated by "make roms" (or any other target that relies on the ROMs).
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 3.6 at 180MHz. There's no need to overclock the CPU -- but it does give better video performance, all the way up to 240MHz. Do as you see fit :)
Environment and Libraries
I built this with arduino 1.8.1 and TeensyDuino 1.35.
https://www.pjrc.com/teensy/td_download.html
These libraries I'm using right from Teensy's environment: TimerOne; SPI; EEPROM; Time; Keypad.
There's an error in the Time library. On Macs, where the filesystem is case-insensitive, you'll have to do something like this:
$ mv /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/Time.h /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/_Time.h
I'm also using these libraries that don't come with TeensyDuino:
uSDFS
This has long filename support, where the Teensy SD library doesn't. It's pretty messy code, though, and I hope to abandon it eventually.
https://github.com/WMXZ-EU/uSDFS
RingBuffer
My library for dealing with ring and circular buffers. Simplistic. You'll need v1.2.0 or later.
https://github.com/JorjBauer/RingBuffer
RadioHead
This library comes with TeensyDuino, but I found the version that's with 1.35 hangs. Using the stock v1.70 seems to be fine.
http://www.airspayce.com/mikem/arduino/RadioHead
Running (on the Teensy)
The reset/menu button brings up a BIOS menu:
Resume
Reset
Cold Reboot
Drop to Monitor
Display: RGB
Debug: off
Insert/Eject Disk 1
Insert/Eject Disk 2
Volume +
Volume -
Reset
This is the same as control-reset on the actual hardware. If you want to execute the Apple //e self-test, then hold down the two joystick buttons; hit the reset/menu key; and select "Reset".
Cold Reboot
This resets much of the hardware to a default state and forces a reboot. (You can get the self-test using this, too.)
Drop to Monitor
"Drop to Monitor" tries fairly hard to get you back to a monitor prompt. Useful for debugging, probably not for much else.
Display
"Display" 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.
Debug
This has several settings:
off
Show FPS
Show mem free
Show paddles
Show PC
Show cycles
Show battery
Show time
... these are all fairly self-explanatory.
Building (on a Mac)
While this isn't the purpose of the emulator, it is functional, and is my first test target for most of the work. With MacOS 10.11.6 and Homebrew, you can build and run it like this:
$ make opencv $ ./aiie-opencv /path/to/disk.dsk
As the name implies, this requires that OpenCV is installed and in /usr/local/lib. I've done that with Homebrew like this
$ brew install opencv
"Why OpenCV?" you might ask. Well, it's just because I had code from another project lying around that directly manipulated OpenCV bitmap data. It's functional, and the Mac build is only about functional testing (for me).
Mockingboard
Mockingboard support is slowly taking shape, based on the schematic in the Apple II Documentation Project:
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 65C02, not quite complete; it supports all of the 65C02 documented opcodes but not the undocumented ones here:
http://www.oxyron.de/html/opcodes02.html
The timing of the CPU is also not quite correct. It's close, but doesn't count cycles due to page boundary crossings during branch instructions. (See the "cycle count footnotes" in cpu.cpp.)
The CPU passes the 6502 functional test from here:
https://github.com/Klaus2m5/6502_65C02_functional_tests
... which is included in binary form in the test harness (see the .h files in util/ for notes).
testharness.basic should reach "test number 240", hang for a while, and then exit.
testharness.verbose should show that it gets through 43 tests, test 240, and then loops repeatedly for a while (exiting at a somewhat arbitrary point).
testharness.extended currently fails (hanging at 0x733) because I haven't implemented the undocumented opcodes. It should get to address 0x24a8 and hang. Some day I'll finish implementing all of the undocumented opcodes :)