A Game Boy emulation library in Ada
This is a proof of concept for an interpreting emulator developed in Ada. It's meant to test the language's suitability for a project of this kind, the performance of generated code, and cross-language interaction. It's not meant to be the go-to emulator for simply playing Game Boy games.
This project started as an homage to my university's Computer Architecture department. I thoroughly enjoyed their courses, in which I went from not knowing binary to fully understanding how a modern CPU works, all the way down to the logic-gate level. This inspired me to write an emulator, and while looking for a balance between simplicity, size, and software-catalog popularity, I set my sights on the venerable Game Boy.
Why Ada? I learned to program in Ada, and I always felt that it was a more reliable candidate for native code compilation than the likes of C. The precise and platform-independent representation clauses the language offers, which allow precise definition of memory-mapped hardware, were also a factor in the decision. Also, while such emulators exist in plenty of other languages, I suspect this is the first one written in Ada.
gade is the emulator core library. It can be consumed by different front ends and by the test harness used by integration tests.
Today, the main consumers are:
gade-sdl: sibling SDL front end project.gade-testd: command-driven harness binary used bypytestintegration tests.
The architecture is intentionally set up so new front ends can be added without changing emulator core behavior (for example, a native macOS Swift front end via Objective-C bindings).
flowchart LR
A[gade<br/>Ada emulator core library]
subgraph Frontends["Front ends / hosts"]
B[gade-sdl<br/>Easily portable SDL2 app]
C[gade-testd<br/>Test harness process]
D[Possible alternate frontends such as Swift MacOS app via Objective-C bindings]
H[libgade_cpp_interface_check<br/>C++ interface compilation check]
end
subgraph CPP["C++ interface layer"]
G[gade-interfaces-c<br/>Ada C ABI bridge]
I[libgade.hpp<br/>C++ wrapper API]
end
subgraph Testing["Automated test layer"]
F[pytest integration tests]
end
B --> A
C --> A
H --> I
D --> I
I --> G
G --> A
F -->|line protocol| C
From gade/, using Alire, you can build the library with:
alr buildFor information on how to build and run the tests, see the tests README.
Install gnatformat with alr install gnatformat.
Ada sources can be formatted locally with:
./scripts/format-ada.shTo run the same Ada format check before each commit, install
pre-commit with:
python3 -m pip install -r requirements-dev.txt
pre-commit installThe hook formats only staged Ada files. The checked-in
./scripts/check-ada-format.sh script still performs the full project-level
check used by CI. The pre-commit hook formats staged Ada files in place, so if
it rewrites files you should git add them and re-run the commit.
gade exposes three GPR scenario variables through alire.toml:
GADE_BUILD_MODE:debugenables safety/debug checks;releaseenables optimized runtime settingsGADE_LIBRARY_TYPE:relocatablefor shared builds,staticfor static linking,static-picfor static PIC artifactsGADE_COVERAGE:false|true(enables--coverageinstrumentation)
- Full CPU emulation (Passes Blargg's CPU instruction tests)
- Plain, MBC1, MBC2, MBC3 cartridge types with saves and MBC3 RTC support
- Joypad
- Timer
- Background Layer
- Window Layer
- Sprites
- Mid scanline rendering
- Audio
- Performance optimizations (GPU rendering)
- Performance optimizations (CPU interpretation/Interrupt handling)
- Support for more cartridge types