This repository contains my implementation of an emulator for the Intel 8080 CPU and the related hardware for the 1978 arcade game: Space Invaders.
It emulates the graphics and sound, supports save states, has an interactive debugger, has rewind functionality, and includes 600+ unit test cases.
I wrote the emulator and disassembler in C# targeting the cross-platform .NET Core runtime.
I used SDL2 and SDL2_mixer for the GUI and audio via the SDL2# wrapper.
The controls are hardcoded as:
- Insert Coin:
5
- 1 Player Start:
1
- 2 Player Start:
2
- Player 1 - Left/Right:
left arrow
/right arrow
- Player 1 - Fire:
space
- Player 2 - Left/Right:
a
/d
- Player 2 - Fire:
p
- Tilt:
t
- Break/Debug:
BREAK
/PAUSE
/9
- Install .NET Core 3.1
- Install SDL2 and SDL2_mixer
- Clone this repository
cd emulator
dotnet restore
dotnet run --
followed by the commands to pass to the CLI program
Currently there is only one command, run
:
$ dotnet run -- run --help
Usage: siemu run [arguments] [options]
Arguments:
[ROM path] The path to a directory containing the Space Invaders ROM set to load.
Options:
-?|-h|--help Show help information
-sfx|--sound-effects The path to a directory containing the WAV sound effects to be used.
-ss|--starting-ships Specify the number of ships the player starts with; 3 (default), 4, 5, or 6.
-es|--extra-ship Specify the number points needed to get an extra ship; 1000 (default) or 1500.
-l|--load-state Loads an emulator save state from the given path.
-d|--debug Run in debug mode; enables internal statistics and logs useful when debugging.
-b|--break Used with debug, will break at the given address and allow single stepping opcode execution (e.g. --break 0x0248)
-r|--rewind Used with debug, allows for single stepping in reverse to rewind opcode execution.
-a|--annotations Used with debug, a path to a text file containing memory address annotations for interactive debugging (line format: 0x1234 .... ; Annotation)
For example: dotnet run -- run ../roms --sfx ../roms --starting-ships 6
If the emulator is launched with the --debug
option, the debugger will be enabled. You can press the pause
/break
or 9
key which will stop execution and print the interactive debugger in the console.
From there you can use F1
and F2
to save and load the emulator state.
To single step over an opcode use F10
, or F5
to continue until the next breakpoint.
Breakpoints can be set via the --break
option at startup, or in the debugger by pressing F4
.
If the emulator was started with the --annotations
option, F11
can be used to toggle between the disassembler's generated psuedocode or the provided annotation file. This is used to show comments for each disassembled opcode inline in the debugger, which makes tracking down issues and/or understanding the game code easier. I collected annotations from the amazing Computer Archeology page on Space Invaders Code, and placed them at roms/annotations.txt
.
F12
is used to print the last 30 opcodes, so you can see execution history.
Finally, if --rewind
was specified at startup, F9
can be used to single step backwards over opcodes, effectively allowing you to rewind CPU state one instruction at a time. I found this to be very helpful when tracking down bugs in the CPU core.
While building the emulator I found it essential to write unit tests for each opcode and along the way. This made it much easier to track down bugs late in development.
Each opcode test contains Intel 8080 assembly code which is assembled using zasm. This assembled binary is then executed on the emulated CPU and then has assertions ran against the CPU state to verify opcode behavior.
Additionally, there is an integration test which uses a CPU test program written for the Intel 8080 CPU originally from 1980! The assembled program along with its disassembly can can be found in the intel8080.tests/CPUDiag
directory.
Emulator tests (9 test cases):
cd emulator.tests
dotnet restore
dotnet test
Intel 8080 CPU tests (605 test cases):
cd intel8080.tests
dotnet restore
dotnet test
While the disassembler is mainly used by the interactive debugger, it can be run from the command line as well:
cd disassembler
dotnet restore
dotnet run --
followed by the commands to pass to the CLI program
Currently there is only one command, disassemble
:
$ dotnet run -- disassemble --help
Usage: i8080disasm disassemble [arguments] [options]
Arguments:
[ROM path] The path to the ROM file to disassemble (or directory containing invaders.e through .h).
Options:
-?|-h|--help Show help information
-o|--output The path to the to output file.
-a|--address Include addresses in the disassembly.
-p|--pseudocode Include pseudocode in the disassembly (a comment on each line).
For exmaple: dotnet run -- disassemble ../roms -a -p -o ../roms/output.asm
I found the following resources useful in building this emulator: