Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Gameboy Doctor: debug and fix your gameboy emulator (robertheaton.com)
150 points by zdw on Dec 3, 2022 | hide | past | favorite | 43 comments



Personally, I've had more trouble understanding the PPU behavior than i did about the cpu instructions and timings.

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...


Hey there! I've been working on a Game Boy emulator since last year and found the GameRoy project, which translates the PPU's logic from SameBoy, the (AFAIK) most accurate GB emulator around. I basically copied the PPU logic into my emulator and replaced the "state" property (an integer) with enums for some extra clarity.

https://github.com/nicolas-siplis/feboy

https://github.com/rodrigodd/gameroy

https://github.com/LIJI32/SameBoy/


If you don't mind french, the project of this class that introduces OO programming was really great: https://cs108.epfl.ch/archive/18/archive.html

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


My gameboy emulator finally works fine CPU wise. The issue is making the sound work without eating up an entire core.

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.


I agree, I made the beginnings of an NES emulator a while ago and just never implemented sound. It didn't seem worth it for how much work it would be, and as you say debugging seems really hard. I remember there being a bunch of test roms folks had made for video and cpu things but not really for sound.

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.


I have an NES emulator with working sound! Except it generates sound at 1.8MHz like a real NES...whereas Windows wants something like 44.Khz. :( I was never able to figure out the best way to downsample it, but it sounds proper if I resample in an audio editing program.


blip_buffer and blip_buf are fairly decent tools for generating an output-rate signal from a high-rate signal (though I use my fork of blip_buffer at https://gitlab.com/exotracker/blip-buffer-exo and/or https://github.com/Dn-Programming-Core-Management/Dn-FamiTra...). When emulating sound chips, the blip buffer adds and subtracts bandlimited sinc impulses from an output-rate delta array, and when exporting resampled audio, it performs a running sum (transforming the impulses into steps) of the delta array and high-passes the running value when writing to the output audio array. The advantage is that you can pick very high sampling rates (like 1.79 MHz) but only burn CPU cycles each time the output level changes (unlike conventional resamplers which are O(input rate + output rate)). Unfortunately blip_buffer has a relatively low SNR of ~50 dB of aliasing rejection even at its widest impulse kernel (as measured by https://gitlab.com/exotracker/exotracker-cpp/-/tree/rewrite-..., though possibly blip_buf is better), and only performs an approximate highpass using integer bitshifts. A usage example is at https://github.com/Dn-Programming-Core-Management/Dn-FamiTra....

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).


Thanks I'll give that a shot


In my GB emulator I generated audio once per frame at the same time I flushed graphics to the screen. That's probably not a completely accurate way to do things, but it worked well enough.


Yeah that generally works and is probably how I should have gone.

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.


> I guess graphics lends itself far easier to debugging.

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.


I didn't even get to sound when I was hacking on one, so kudos! I can believe that the sound portion is harder, or at the very least less forgiving.


While there's a few EmuDevs around, and I apologize beforehand for the shameless topic hijacking, a friend and I have been thinking of making a sort of educational game that teaches you the basics of coding an NES emulator from scratch to game-playable, with progressive challenges, in JavaScript (to make it more accessible).

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.


Sounds like a fun concept, sure.


This is very cool, how does it count the ticks? It seems to just go instruction by instruction, but some instructions take multiple CPU ticks. Since this only seems to cover the CPU state for the moment I'm not sure it actually makes a difference though.


Looking at the instructions, this test doesn't seem to care about timing. I've seen NES emulator test roms with prints for state after each instruction where they did include the number of cycles. When I was writing my (poor) NES emulator, I ran my code on the test rom and would stop if it hit an instruction I didn't handle or if the states didn't match. Pretty soon I had a fairly accurate CPU emulator.


As an emudev, I think this project is great. Especially since GB development is usually the most common introductory system.

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.


This couldnt come at a better time! Debugging is very painful, because the manual i have of the CPU instruction set is full of errors!


Are you using the "Gameboy CPU manual"? If so, don't :) there's a better reference at https://github.com/gbdev/gb-opcodes (renders to this page: https://gbdev.io/gb-opcodes/optables).


> https://gbdev.io/gb-opcodes/optables

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 was! Thank you for this.

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..


If I were to write an emulator, what old system should I pick? I've never written one before and I don't know a lot about low level CPU stuff. Probably a video-game system would the most rewarding rather than some generic system/cpu like a PDP or the 8086... you could actually play a game with it in the end.


Most people will recommend the CHIP-8 system, because it is simple to handle - very simple graphics, very simple CPU, and a decent range of games to play with.

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!


Chip-8 is a great start, it was a virtual machine used for Atari 2600 style games: https://tobiasvl.github.io/blog/write-a-chip-8-emulator/ Basic but still a good first step.


Chip-8 is a great place to start. It is easier to program than the Atari 2600, and is also monochrome. And the guide mentioned is an excellent resource.


Thanks!


A GameBoy is the obvious answer here, but the IBM PC (the 5150, the original one) is another good choice if you want to have a lot of software to try it with.


Is a gameboy pretty basic? This tooling seems pretty cool.


I've been slowly working on my GB emulator for a couple of months now. There's a lot of resources around, which is good. Unfortunately there are also parts that are ambiguous and somewhat poorly documented, which has caused a lot of misery. I had to resort to looking up other people's implementations, and it's not a very satisfying way to develop.

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.


I might have to give this a try. I wrote a Game Boy emulator back in the early days of the pandemic, and while it worked well enough to play several games, it never did very well at some of the "you should have this working" Blargg tests.


Awesome idea to make this a standalone program! When I was developing an NES emulator, I basically had to do this entire process manually with another NES emulator that produced tick by tick logs, then compare it to mine with a diff. Having the last executed opcode right there is helpful too. Next time I revisit that code I may have to take a crack at making an NES Doctor.


We've got Seth Bullock and Sol Star over here selling shovels when the rest of us were panning. Smart.


Sorry to be the guy, but "Game Boy" is two separate words and always has been.


> writes an article about hacking cpu instructions beyond the capabilities of 99% of the population

> gets told they spelt something wrong


Bottom feeders have to comment but stick to what they understand and ignore the rest.


>writes an article about hacking cpu instructions beyond the capabilities of 99% of the population

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.


This is another silly correction, I never said anything about the hn population. Go nitpick somewhere else.


It's ok to be that guy, but in the future please phrase your corrections in less combative ways. The way you wrote your comment comes across as rude.


Genius.


This implies that a broad range of people would want to create their own custom gameboy emulators, which seems really weird. Isn't there a single open-source one out there that works and that people can work on together to improve? What's the benefit of having your own? Are there lots of choices to be made, so that each emulator best fulfills its creator's needs?


For people who want to get started with hobbyist emulator development, the Game Boy is the simplest "real console" that they can get started with, complete with tons of commercial games, community support, an active homebrew scene, existing emulators, etc. Even easier than that is the CHIP-8, but that's arguably too simplistic, and too limited in scope.


I’ve personally implemented 7 gameboy emulators and I’m half way through my 8th…

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 :)

( https://github.com/shish/rosettaboy )


Fun! I've been implenting my own to toy around and learn some rust, and has been a very enjoyable journey, specially the debugging part, running test roms, and trying to get all the "bugs" implemented correctly




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: