Robert Heaton

Software Engineer /
One-track lover / Down a two-way lane

Gameboy Doctor: debug and fix your gameboy emulator

03 Dec 2022

Are you building a Gameboy emulator? Are you stuck? Are you failing Blargg’s test ROMs and can’t work out why?

Gameboy Doctor can help! (GitHub link)

What is Gameboy Doctor?

Gameboy Doctor is a tool that compares your emulator to an example emulator that passes Blargg’s test ROMs. It finds the exact tick where your emulator’s state diverges from the example, helping you isolate and fix your bugs. You don’t need to have implemented an LCD in order to use it, and you don’t even have to be able to successfully get any kind of pass/fail message back from Blargg! All you need is a minimally functional CPU and motherboard.

Requirements

Just Python3, no third-party libraries.

How do I use Gameboy Doctor?

0. Clone the repo

The tool is available on GitHub - clone it using git.

1. Choose a test ROM

Choose a cpu_instrs individual test ROM (these are currently the only ones supported by Gameboy Doctor - see below)

2. Make 2 tweaks to your emulator

You’ll need to make 2 changes to the internal workings of your emulator. They’ll probably take about 20 minutes to do, but they’ll save you hours and days of aimless debugging. The changes are:

  • Initialize the CPU’s state to the state it should have immediately after executing the boot ROM:
Register Value
A 0x01
F 0xB0 (or CH-Z if managing flags individually)
B 0x00
C 0x13
D 0x00
E 0xD8
H 0x01
L 0x4D
SP 0xFFFE
PC 0x0100
  • Hardcode your LCD (or your motherboard’s memory map if you haven’t implemented an LCD yet) to return 0x90 when the LY register is read (memory location 0xFF44). This is what I did when generating my example logs, because returning a constant prevent spurious log divergences.

3. Log the state of your CPU

Next, update your emulator to write the state of the CPU after each operation to a logfile. Use a new line for each tick, and use the following format for each state (replace the example numbers with your CPU’s values):

A:00 F:11 B:22 C:33 D:44 E:55 H:66 L:77 SP:8888 PC:9999 PCMEM:AA,BB,CC,DD

All of the values between A and PC are the hex-encoded values of the corresponding registers. The final value (PCMEM) is the 4 bytes stored in the memory locations near PC (ie. the values at pc,pc+1,pc+2,pc+3).

Run your emulator and get a log file. You can kill the program at any point - Gameboy Doctor will tell you if your log file is correct but ends before the test ROM has finished its assertions. If you pass the test then your emulator will display the word “Passed” on the LCD, and write the bytes for the word “Passed” to the serial output. However, you don’t need to pass or even finish the tests in order to use Gameboy Doctor.

4. Feed your logfile to Gameboy Doctor

Once you have your logfile, feed it into Gameboy Doctor like so:

./gameboy-doctor /path/to/your/logfile $ROM_TYPE $ROM_NUMBER

For example, to check the 3rd cpu_instrs ROM:

./gameboy-doctor /path/to/your/logfile cpu_instrs 3

On windows you may need to invoke the Python interpreter directly:

python3 gameboy-doctor /path/to/your/logfile cpu_instrs 3

Gameboy Doctor will tell you how you’re doing and give suggestions on bugfixes. For example:

$ ./gameboy-doctor ../my-emulator/logs/3.log cpu_instrs 3
============== ERROR ==============

Mismatch in CPU state at line 9997:

MINE:   A:3E F:C--- B:01 C:07 D:C9 E:BA H:49 L:BB SP:FFFE PC:0208 PCMEM:1C,20,FB,14
YOURS:  A:3D F:C--- B:01 C:07 D:C9 E:BA H:49 L:BB SP:FFFE PC:0208 PCMEM:1C,20,FB,14

The CPU state before this (at line 9996) was:

	      A:3E F:10 B:01 C:07 D:C9 E:BA H:49 L:BB SP:FFFE PC:0207 PCMEM:12,1C,20,FB

The last operation executed (in between lines 9996 and 9997) was:

	      0x12 LD (DE) A

Perhaps the problem is with this opcode, or with your interrupt handling?

Eventually you’ll hopefully see:

$ ./gameboy-doctor ../my-emulator/logs/3.log cpu_instrs 3
============== SUCCESS ==============

Your log file matched mine for all 1066160 lines - you passed the test ROM!

Future Work

Gameboy Doctor currently only supports Blargg’s cpu_instrs test ROMs because these are the most useful for initial debugging. It should be relatively easy to support other test ROMs, although small timing differences that don’t affect the successful running of the emulator may cause divergences in CPU states between otherwise well-functioning emulators.

Let me know if you find Gameboy Doctor useful and I’ll work on expanding the ROMs and emulators it supports.

Acknowledgements

This tool was inspired by GitHub user wheremyfoodat.

Subscribe to my new work on programming, security, and a few other topics. Published a few times a month.
Follow me on Twitter ➜ RSS ➜