For some reason I wanted to make emulator for a really long time. There’s something entrancing about this idea that you need to write code to emulate original hardware. It’s quite hard project but it’s certainly fun and rewarding, if you are filling confident enough then definitely try making one!
I’m currently working on Game Boy emulator written in Kotlin using libgdx (cross platform game framework) and VisUI (my UI toolkit). In current state it has fully working CPU emulation (passes cpu_instrs
and instr_timing
tests), has integrated debugger and simple VRAM viewer. I’ll be focusing on GPU emulation now. Entire project is open source on GitHub so you can check it out here. In this post I would like to focus on general points and observations I made during making this project. I’ll focus more on technical details in next post.
How do I make emulator
Basically you need to write software equivalent of all original hardware parts, certainly biggest of which would be CPU. You start by collecting information about platform you want to emulate - in case of Game Boy it’s quite well documented so this wasn’t hard. I found CPU manual, opcodes tables, official Nintendo development manual. There is also a lot of forum posts (and StackOverflow questions) from people who attempted this task. Then you write CPU emulation, and then implement more and more components: memory controllers, timers, GPU, input, serial port, audio processing unit and so on.
You may be wondering what knowledge do you need to have to make emulator. Do you need a lot of background in low level programming? Not really, you don’t even have to know how to make games on the platform you want to emulate. While it certainly helps that you know even the basic of Assembler etc., it is not needed and with enough self-denial you can learn things while you are making your emulator. That said this is not a project that you should make using unknown language, libraries and tools - stick with what you know. I kind of ignored this by using new language - Kotlin however it is easy enough to pick up if you know Java, and has pretty much the same tooling (IDE, build system) so it wasn’t that bad.
Choosing platform to emulate
Try not to pick up too complicated platform or you simply won’t be able to make it. I’ve often seen Chip-8 recommended as starting emulator, it is quite simple and should give you nice overview on how to start creating more complicated emulators. You can also be like me and go directly for something more complicated like the Game Boy. I’ve chosen it mainly because of my sentiment for this console - I still own working Game Boy Pocket with few games.
Tips
Get test ROMs
You will need to test your emulator somehow, of course you can use standard game ROMs but those won’t test it comprehensively. In case of Game Boy it’s very easy - there are test ROMs that were made to specifically test various part of hardware, CPU, timings, sound and so on. They don’t even need an display to run, they output information to serial port which is easy to emulate and some of them write test result in specific position in RAM. From those ROMs my emulator currently passes cpu_instrs
and instr_timing
tests. cpu_instrs
tests almost every op code of CPU, instr_timing
tests well.. instruction timing. And if you haven’t know timing is important when emulating something.
Don’t leave stuff half working
In Game Boy there is this thing called Memory Bank Controller (MBC). CPU addressing space is limited, if game is huge and it can’t fit into 32 KB, MBC is used to allow access to other ROM banks by switching available ROM bank
at specific memory address. When running cpu_instrs
my emulator was stuck in loop, infinitely running next tests even though there are only 11 test sections. Turned out it wasn’t CPU issue and anything like that but unfinished implementation of MBC1 which did not allowed to switch ROM bank. It resulted in the same code being executed over and over. Lesson? Don’t do that or at least try not to, this can lead to very unexpected issues which may took much time to debug.
Create debugger
This is a must, if you want to efficiently find issues you will need a debugger. Even the simple one is fine, if you can pause execution and step through instructions it will be great help. I’ve observed interesting thing, Run to Line
function was more helpful that breakpoints, so much that I haven’t even finished implementing breakpoints. You can see my debugger here. It is showing currently executed op codes, stack, CPU registers and flags status. You can also go to entered memory address and by right clicking instruction run emulator until that instruction is reached. I’ll leave details how exactly debugger is interacting with emulator for another article.
Use multiple info sources
I already mentioned this but it is really important, Game Boy is well documented but I was quite surprised to find so many discrepancies, seriously:
- This is wrong - opcode E2 and F2 is 1 byte long, not 2. This single thing caused
instr_timing
to loop infinitely and took few hours to debug. I found out that only source that you can trust for instruction length and timing is theinstr_timing
test itself. You can get valid values from it’s source code. That site also get few timings for CB instruction wrong if I remember correctly but that is easy enough to find withinstr_timing
and not so devastating as wrong instruction length. - Some errors I found in CPU manual:
- Timing for conditional instruction are completely wrong, it ignores the fact that the timing is different depending on whether action was taken or not.
- HALT procedure description skips very important thing that interrupt will always wake up CPU, even if Interrupt Master Enable (IME) flag is not set.
- Some instruction are missing details on how they affect CPU flags, instead you get:
Flags affected: H: Set or reset according to operation
- good luck figuring that out.
Use another working emulator
You can use another already working emulator with it’s debugger to test if your emulator is behaving (more or less) the same. Sure you may want not to do that, thinking it will make it too easy (it won’t). This can be somewhat compared to testing on real hardware although made much much easier.
That’s it for the introduction, my tweet gained some attention so I’ll probably write another post explaining more technical details of my emulator.