Gameboy Doctor: debug and fix your gameboy emulator
robertheaton.com
robertheaton.com
I'd mostly been going off of The Ultimate Game Boy Talk[0] and the pandocs[1]. In trying to quickly dig up some resources now, I found what at first glance looks like a good blog series[2], so I might take some time to squint at them soon.
If any HNer has other good resources describing it, I'd be thrilled. It's been a couple years since I've hacked on my own emulator, and I got through implementing quite a bit of the behavior. As I recall, I was able to get serial output from the test roms, which was pretty cool. I put it down after getting frustrated trying to render the screen.
[0] https://www.youtube.com/watch?v=HyzD8pNlpwI&t=2957s
[1] http://bgb.bircd.org/pandocs.htm#videodisplay
[2] https://blog.tigris.fr/2019/09/15/writing-an-emulator-the-fi...
https://github.com/nicolas-siplis/feboy
It guides you through making a Gameboy emulator step by step. When I did it I remember that something that was a bit frustrating is that you had to do things a certain way that only made sense much later when other pieces of the puzzle fell into place
Maybe I’m saying the obvious to many but the biggest “broad” lesson I learned from the project wasn’t Rust. It was, “sound is harder than video.” And I feel that’s counter intuitive. Maybe some disagree but for me there’s something about processing raw sound that’s so much harder to implement and debug. I guess graphics lends itself far easier to debugging. You can see each frame. Can’t really hear each frame.
Also I'm not sure if this is the same for Gameboy but I remember for NES implementing sound also required you to do more work in the CPU/PPU to keep track of the exact timing to make sure things started at the right time, the right data was there at the right time, etc.
Alternatively you can generate a high-rate signal and feed it into a conventional resampler to produce a 44.1/48/96 KHz output. I found that libsamplerate (https://libsndfile.github.io/libsamplerate/)'s medium preset produces audibly transparent output at 44.1 KHz and above, and should have acceptable latency on the order of 1ms (I didn't verify but you could first flush out the startup edge effect with silence, pop all output, then push an impulse followed with silence until the central peak emerges from the output). This has minimal CPU usage for a single stereo 128 KHz input stream (like in exotracker and chipsynth SFC), but I don't know if it burns excessive CPU with 1.79 MHz input.
----
My baseline expectation for production-quality emulators is to generate sound without aliasing, but the gold standard is to properly emulate the audio path as found on hardware, by feeding schematics through SPICE and/or pole-zero math to create an analytical representation of the filters, then verifying them against MDFourier tests (hardware recordings of broad-spectrum sound played by the console). Few emulators attempt to do this; according to https://bel.fi/alankila/modguide/interpolate.txt, UADE (an Amiga emulator) gets this right using a variation of the Blip_Buffer approach with longer precomputed(?) impulse responses specialized for Amiga filtering. Several chiptune tools properly model hardware filters, including the chipsynth family of audio VSTs (commercial); Dn-FamiTracker (an open-source NES composer) emulates FDS lowpass properly without aliasing, but only loosely approximates 2A03 lowpass and global highpass using blip_buffer's configurable filtering (impulse/step visualizer at https://gitlab.com/exotracker/exotracker-cpp/-/blob/rewrite-...).
If you choose to model a hardware filter using IIR filters (mathematical arithmetic based off a hardware model) instead of a large precomputed impulse response (like interpolate.txt and UADE), you'll get more accurate results if you generate audio at a high internal sampling rate, IIR-filter the audio at this high rate (ensuring the filter cutoff is well below Nyquist or half the sampling rate), then feed it into a resampler. If you use Blip_Buffer to generate 44.1 or 48 KHz directly like blip_buffer, and apply a filter with cutoff above 10 KHz or so, high frequencies will not be filtered accurately.
One interesting idea (combining blip_buffer's efficiency at handling sparse signals, and the accurate treble filtering enabled by a high intermediate filtering frequency) is running a blip_buffer-like system (with no highpass but a ~20 KHz lowpass) to downsample from a high internal rate to a fixed 128 KHz (for fixed filtering) or twice the audio rate (for efficient rational-factor downsampling), then performing hardware filtering there before downsampling using a resampler. The downside is that this stacks the latency and artifacts of both Blip_Buffer and the resampler, but if you make Blip_Buffer generate mostly-lowpassed audio and avoid generating nonlinear harmonics in filtering, you can use a faster second resampler that assumes its input is mostly lowpassed (using a narrower sinc kernel).
Yeah, it's also less big of a deal if a frame is late. When you deliver a bit of sound late, it's jarring.
Though it breaks down when game devs use exploits for special sound effects. If you only calculate during vblank then you won’t do the calculations against changing memory conditions.
Is this something you'd be interested in? It's a fun project for us but while we're at it I'd like to know if anyone'd want to play it.
That being said, this might be better built as a general purpose framework to all emucores (exodus, unicorn or mame/mess would be a good starting point target). I could see this used for testing and CI/CD, not to mention general validation and debugging.
FWIW, it's very unfortunate that the tables are laid out according to hex rather than octal, since (much like x86) the sm83 instruction set is designed around octal 2+3+3-bit bytes, and using hex significantly obfuscates the underlying structure. (It also makes the table much wider than it needs to be, which would a acceptable tradeoff if it actually corresponded with the instruction set structure, but here just adds insult to injury.)
Edit: Went digging, and almost immediately found (a hidden-by-CSS-nonsense link to) https://gbdev.io/gb-opcodes//optables/octal, which uses the more sensible layout. Still kind of disappointing that's not the default, though. (Also CSS, but at this point I'm past disappointed and into "well, what were you expecting?".)
I wanted to use a manual or reference diagrams ti write the cide from to challenge myself, instead of looking at others people's sources.
Im sure this will help me! I had began witting unit tests to test my logic, but the logic was all off anyways..
My one advice would be not to skip any parts in the implementation ("I just skip the cpu timing so I can have something on the screen quickly"), as many things are interconnected and changing things becomes very difficult later on.
All in all it's a really fun project and at least I learned a ton. My original motivation was to learn Rust, but it turns out that was the easiest part.
If you're thinking "old school" then your choices are NES, Gameboy, and 8080. Personally I wrote an 8086 emulator, to play space-invaders.
8080 is a reasonably simple system, without horribly complicated graphics or MMU addons. Interrupts were hard to debug, but otherwise it's a very well documented system with lots of directions you can go in afterwards - emulate DOS, and Hercules? Emulate CP/M? Emulate other games? Or start looking into running Windows even!
> gets told they spelt something wrong
99% of the HN population can run the diff command. It's helpful that this exists, but there is nothing complicated about what is being done.
In my case it’s mostly a fun way to learn a new programming language - an emulator is enough of a “real program” to exercise a load of different aspects of a new language and its standard library to get a feel for how it works, but also small enough that a new implementation can be done in a weekend[0]
[0] Assuming you already have the foundational knowledge - my first emulator took a week to get the CPU working and a month for the first game to be playable, only using one old and inaccurate PDF as a reference; now that I know what I’m doing, and now that I’ve found gbdev.io for more accurate docs, it’s much faster. Funnily enough I also ended up creating a tool like the one in this article, logging every action that happens in the CPU or RAM to make sure they all match up :)