An emulator for the Zilog Z80 CPU and hardware specific to the 1980 arcade game: Pac-Man. It will also run Ms. Pac-Man, as well as any other homebrew ROMs that target the Pac-Man hardware.
This is based on the Intel 8080 CPU core from my Space Invaders emulator.
It emulates the graphics and sound, supports save states, has an interactive debugger, has reverse-stepping functionality, and includes 8000+ unit test cases.
Above: my emulator running Pac-Man, Ms. Pac-Man, and a homebrew Matrix-effect ROM.
I wrote the emulator and disassembler in C# targeting the cross-platform .NET Core runtime. It runs as a desktop app on Windows/Linux/macOS as well as a UWP app on the Xbox One game console.
I used SDL2 for the GUI and audio via the SDL2# wrapper. The Xbox One version uses the same code wrapped in a UWP app wrapper, which is based on this starter project.
The keyboard mapping is hardcoded as:
Action | Keyboard Key |
---|---|
Insert Coin | 5 or 6 |
Start (1 Player) | 1 |
Start (2 Players) | 2 |
Service Credit | 3 |
Rack Advance | 7 |
Test Switch | 8 |
Player 1 Movement | Arrow Keys |
Player 2 Movement | W /S /A /D |
Break/Debug | BREAK / PAUSE / F3 |
It also supports using an Xbox-style game controller (both on desktop as well as when running on the Xbox One):
Action | Keyboard Key |
---|---|
Movement | Left Stick or D-Pad |
Insert Coin | Y |
Start (1 Player) | A |
Start (2 Players) | X |
The project layout is as follows:
Directory | Target Framework | Description |
---|---|---|
disassembler |
netstandard2.0 |
Used for disassembling code in the debugger |
assembler |
N/A | Z80 assembler (zasm); used to assemble unit tests cases written in Z80 assembly |
z80 |
netstandard2.0 |
Z80 CPU emulator |
z80.tests |
net6.0 |
Unit tests for the z80 library project |
emulator |
netstandard2.0 |
The emulation code and Pac-Man hardware (minus the CPU core), platform "glue" code (SDL) |
emulator.cli |
net6.0 |
CLI application; used to launch the app on a desktop platform (Windows/Linux/macOS) |
emulator.uwp |
UAP |
Universal Windows application for Xbox One (or Windows 10) |
emulator.tests |
net6.0 |
Unit tests for the emulator library project |
- Clone this repository
- Install .NET Core 3.1
- Install SDL2
- macOS:
brew install SDL2 && brew link sdl2
- On M1/arm64 macOS, may also need to do:
sudo ln -s /opt/homebrew/lib/libSDL2.dylib /usr/local/lib/
- On Windows, download binaries and place
SDL2.dll
in theemulator.cli
directory
- macOS:
cd emulator.cli
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: pacemu run [arguments] [options]
Arguments:
[ROM path] The path to a directory containing the ROM set to load.
Options:
-?|-h|--help Show help information
-rs|--rom-set The name of an alternative ROM set and/or PCB configuration to use; pacman or mspacman; defaults to pacman
-dw|--dip-switches The path to a JSON file containing DIP switch settings; defaults to dip-switches.json in CWD.
-l|--load-state Loads an emulator save state from the given path.
-sc|--skip-checksums Allow running a ROM with invalid checksums.
-wr|--writable-rom Allow memory writes to the ROM address space.
-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)
-rvs|--reverse-step 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, to run Pac-Man: dotnet run -- run ../roms/pacman
Or, to run Ms. Pac-Man: dotnet run -- run ../roms/mspacman --rom-set mspacman
Game settings (such as number of lives and difficulty) can be adjusted by editing the dip-switches.json
file.
Homebrew ROMs that target the Pac-Man hardware can be run by leaving the --rom-set
set to the default pacman
value. You may also need to use a few extra switches depending on the ROM. e.g. dotnet run -- run ../roms/homebrew --skip-checksums --writable-rom
- Install Visual Studio 2019 with the following components
Universal Windows Platform development
Windows 10 SDK 10.0.18362.0
- Setup Xbox One in developer mode
- Use DevMode activation app
- Settings > System > Console Info > then quickly press LB, RB, LT, RT to enable
- Clone this repository
- Open
emulator.uwp/pac-man-emulator-uwp.sln
in Visual Studio - Set project configuration to
Debug x64
- Set signing certificate
- Project Properties > Application > Package Manifest > Packaging
- Setup for remote deploy to Xbox
- Project Properties > Debug > Start Options > Authentication Mode:
Universal (Unencrypted Protocol)
- Enter IP address of Xbox
- Project Properties > Debug > Start Options > Authentication Mode:
- Place ROMs in
emulator.uwp/roms
- See readme for more details
- Click the green play button labeled
Remote Machine
to deploy and start running/debugging
If the emulator is launched with the --debug
option, the debugger will be enabled. You can press the pause
/break
or F3
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. You can find an excellent annotated disassembly here.
F12
is used to print the last 50 opcodes, so you can see execution history.
Finally, if --reverse-step
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 Z80 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 Zilog Z80 CPU. This test program executes instructions and then performs checksums against memory that were taken from real hardware. The assembled program along with its disassembly can can be found in the z80.tests/ZEX
directory.
Zilog Z80 CPU tests (8000+ test cases):
cd z80.tests
dotnet restore
dotnet test
Finally, there are test cases for the Pac-Man specific hardware such as the video hardware which renders a screen of tiles and sprites in several different palettes and then compares them against the reference images:
Emulator tests:
cd emulator.tests
dotnet restore
dotnet test
I found the following resources useful in building this emulator:
- Zilog Z80 CPU
- Z80 Instruction Exerciser
- Pac-Man Hardware
- Misc