Game Boy emulators are always great for educational or experimental projects, because the Game Boy platform is surprisingly simple, which leads to a very quick payoff, and yet very "real" in that the Game Boy was a great commercial success with thousands of software titles available, so you have plenty of software available to try in your project and actually make use of that payoff.
One example to illustrate how simple the Game Boy is: There is no operating system at all, only a 256 byte boot ROM whose sole purpose is to display the Nintendo logo from the cartridge and halt if it does not checksum correctly (for both cartridge integrity and legal reasons).
This entertaining 33c3 talk manages to describe the Game Boy hardware practically in its entirety (and with some previously unknown details, actually): https://www.youtube.com/watch?v=HyzD8pNlpwI
The Game Boy is only simple in the early stages of emulator development.
You find quickly that it is full of excruciatingly complex edge cases that aren't documented, and aren't well understood by anyone.
We're still making significant progress to this day on it.
And even in the early stages, the Z80-like Sharp LR35902 is quite a hassle. If you want an educational project, consider an NES emulator instead. Still has lots of edge cases, but the documentation and reverse engineering work is far ahead of the Game Boy.
Sorry, I should have clarified: The Game Boy is very simple in concept and on a sufficiently high level. But this is exactly what allows you to get payoff quickly, and why I like it for educational and experimental projects. Especially earlier games like Tetris don't significantly rely on many edge cases and implementation details, so even a quick and dirty implementation runs them reasonably well.
Once you want to do "serious" emulation that runs a wide variety of games and accurately reproduces them in every spect, things become much less simple and might also lead you to completely abandon initial approaches, e.g. because your straightforward but non-cycle accurate CPU main loop doesn't cut it anymore.
Compare this however with a more modern system that has an MMU, where you have a long, long road of busywork ahead before you can even get past any reasonable definition of "booting", whereas for the Game Boy even a shoddy first draft implementation might at least get you into a game's initial menu screen, completely with working buttons and all.
And strictly spoken, a conceptually more complex system will have more edge cases and implementation details to deal with, albeit I think that it often also means that those implementation details are less exploited, as developers stay on a higher level and are less trying to squeeze out every last bit of the "simplistic" hardware.
And the simple front with the fractal complexity below it also make this more fun and educational: You can start off easy while still getting good results, and go arbitrarily deep in perfecting your emulator, choosing from an apparently still unexhausted variety of interesting problems to solve.
Your links are a good example, I love reading about the complex implementation details in emulators of superficially simple hardware.
For an educational project however, wouldn't an opaque platform with broken-ass documentation and edge cases that make you hate yourself be more representative of real software dev in general? ;)
Eh, the NES is more complicated IMO. To start off there's the weird clock scheme that there's like 3 CPU cycles for every 5 PPU cycles (and there are games that depend on partial results). There's the greater combinatorial complexity of everyone and their mother making custom ROM boards. The PPU is just more complex, IMO. Etc. Etc.
As far as capabilities go, both chips are remarkably similar, with the Gameboy supporting an extra background. They both do a handful of sprites and tiled backgrounds with attributes for things like palette selection. However, the NES has one difference that throws a wrench in things. Unlike the Gameboy, which has VRAM in the console, the NES reads all of its graphics data from the cartridge directly.
The NES's PPU is simple in operation, but many cartridge types (MMC3 and MMC5 in particular) relied on the precise behavior of the PPU's reads and writes to time some of their extra features, often something like a scanline counter. This makes the complete system much more difficult to emulate, even though the base features of the console are straightforward to grasp.
What makes the NES such an attractive target for a first emulator is that, due to this quirk and the system's popularity, its hardware is just about perfectly understood. Take a look at the Nesdev Wiki's article on rendering, which has an extremely thorough breakdown of the timing, including the bugs in the sprite rendering subsystem and how to correctly emulate them:
I would think the thing that would make the NES unsuitable would actually be the very large number of mapper chips for the platform. The NES seems less like one platform and more like tens of them at that point. AFAIK the Gameboy didn't really have this proliferation of chips that manipulated the system so directly.
At the very least, you might be able to badly emulate most gameboy games, but a mapperless NES emulator is only going to even be able to try to run the first generation of games from the console.
So you can cover about 95% of the library (and 99% of the games people want to play) with about 20 or so mappers. From there it can be a slog to support them all, but conceptually mappers are really very simple. Almost all of them are just registers that let you page in chunks of memory.
The most advanced mapper is the MMC5, that one can take some doing. And the VRC7's OPL audio is also a nightmare (yet used only for one game.) But beyond that, it's all really simple. Usually around 10-30 lines of code per mapper. Mostly very well documented, with the exception of how mappers detect scanlines (important for accurate emulation, not so much for full compatibility. You can just cheat and give them the PPU scanline counter.)
But, maybe I'm biased. I've emulated thirteen systems so far, and the NES was one of my favorites to work on due to the documentation and general simplicity. I know the rabbit hole goes on forever even with the NES, though.
This one walks you through writing an emulator for the classic Space Invaders game: http://emulator101.com/
But really, if you've had a computer architectures class (or do equivalent research on your own), it's doable to just start looking up info about a specific system, and digging down as necessary.
This is definitely true. The gameboy does have memory mapped cartridges to deal with, but they don't hook quite so deeply into the system; they're much more straightforward to implement, and for simpler games you can ignore the mapper entirely.
The NES is a different beast; nametables _alone_ will give an early NES author headaches, and they're incredibly important to get right, otherwise games won't be able to scroll properly. Even simpler games like Mario and Metroid rely on basic nametable support being there. In this case I think it's the limitations of the NES itself that led to the mappers doing so much more externally; game authors had no other choice if they wanted to pull off advanced tricks. This trend continued with the Super Nintendo; remember the SuperFX chip? It almost turns the Super Nintendo into a dumb terminal at that point, generating entire screens worth of graphics in the gamepak directly.
> In this case I think it's the limitations of the NES itself that led to the mappers doing so much more externally
Agreed. The Game Boy's ability to generate interrupts at various times in the scanline, and access to the current scanline number, make timing things with vanilla hardware much easier on the Game Boy than the NES.
From the first link : "But then why does this game work well in inaccurate emulators? That’s also a bit of a mystery, but it’s possibly because their timings are just wrong enough that things do not end up cascading the same way."
If it's that then my hat goes off to the devs who find it, timing issues are second to none in difficulty.
Coincidentally, the "Practice of object-oriented programming" course in EPFL has as a project this semester to build a Game Boy emulator in Java. You can have a look here (https://cs108.epfl.ch/). Although it's in French, google translate does a decent job.
Heh, should have read the article before writing my comment, the author mentions the same and even links to the Ultimate Game Boy talk as well. I hope I was able to add some detail.
I just wanted to write I'm really happy that this post showed up on the HN front page. It's been more than a year since I wrote the post and from the time perspective I think it was the most rewarding project I've ever done. It doesn't matter that there's a plenty of more mature, accurate, user-friendly emulators - the "journey" was a reward in itself. If you're thinking about creating your own emulator, I highly recommend it.
I wrote my own GBC emu about a year after you, and an NES emulator, working on and off, from about 2008-2014, doing bits at a time. It's amazingly gratifying when more and more games start working in it.
I'm going into Game Boy Advance now. Even just efficiently decoding ARM opcodes is an adventure on its own. Different timings and bus widths in different parts of the memory map....but a large enough map that you don't have to worry about banking at all. Although I suspect that it's fast enough that it might start making sense to look at dynarec/jit techniques.
I'm reminded of the days I was writing a Z-machine emulator in Haskell [1], ostensibly as a uni class project but also as a way to teach myself Haskell and have fun; I had the infrastructure in place and was trying to run Zork, implementing opcodes, one by one, when finally I saw the familiar message "You are standing in an open field west of a white house, with a boarded front door. There is a small mailbox here." The sense of accomplishment was rewarding and elating.
"[...] every time I had a few minutes during the day and basically every evening I felt an irresistable urge to move the emulation a bit forward [...]"
Ahh, memories of the good old days... before I got married...
"[...] if I have 5 minutes of a spare time, I was trying to fix this weird GPU or sound bug. I guess it’s not too healthy (especially if you have a family) [...]"
Oh, crap, this guy apparently has family, too! Shame on me! :-)
Always impressed by people who complete things. I've had terrible luck actually FINISHING a project. I guess maybe it's ADD... or my interest in rather boring device drivers and the like. I've been able to modify large projects with many thousands of lines, but when it comes to write one from scratch it really is a lot of work. Props!
I've noticed that sometimes when I code for fun there is some tricky puzzle to solve. Once I've solved the puzzle, the fun is gone, and actually finishing the project is a lot more like work than fun.
I think I'd probably finish a lot more projects if I wasn't a professional software developer -- I don't want to work in my free time as well.
What has helped me is to get better about goal setting. For me this means having some large goal that is easily associated with personal desire. Then decompose that into smaller goals. These smaller ones are one step removed from the base desire, and as such need a little effort to figure out how to attach the goal to desire.
I keep breaking down the goals until they are a size that I can accomplish in a day. Often that's too small a timeline but that's what I aim for.
At each step of the break down, I make an effort to attach that goal to personal desire. Whether the desire is related to the overarching desire for the top goal doesn't matter so much, but it does have to have a desire component.
Sometimes the desire attached to a goal is to end the day feeling like I have made progress. That's it. The whole desire for that goal is in the achievement.
And frankly, achievement is a huge motivator for me. If I feel like I am spinning my wheels, it makes me what to stop and do something that isn't wasting my time.
Breaking the goals down into smaller goals that work on shorter timelines allows you to get that dopamine hit from achievement. You can end the day feeling pleased and relaxed. And you can wake up excited that you have an achievable goal to work on for that day.
"Once I've solved the puzzle, the fun is gone, and actually finishing the project is a lot more like work than fun."
Simple life hack: Next time you're planning on starting a project like that, declare that the goal in advance.
If you're finishing projects at work, you know you could finish, if you wanted to, but whether you "finish" a project should be subject to cost/benefits analysis just like anything else. Your $COOL_PROJECT isn't a failure if it doesn't attain 15,000 stars on GitHub, unless you chose to make that a goal.
I was thinking about that in response to the other reply below. I think it's definitely based on value; at some point you realize that you'd be happier doing something else. At work, you don't have the same calculation because it has to be done and personal rewards are not the only motivation.
I've also found that how intellectually rewarding my day job is (and how much agency I have) has a direct affect on my hobby development. I have a lot of creative freedom and interesting projects at work right now so I care less about coding in my spare time. I only have so much free intellectual energy to devote to something.
This seems really common. I categorize 'finishing' as a whole other skill/category than programming/hacking/building stuff. Recognizing that the last 10% is the hardest part is daunting and not as fun a lot of the time, but the reward and payoff can be worth it.
Yup, that's true that finishing a project is more than programming skills. You would have to do a lot of boring work on the way. For me, the last 10% of the work which contains more polishing to what you have built so far and making it ready is the hardest part.
But somehow I have successfully done that for my project https://github.com/joyread/server when I launched v1.0 and it is indeed rewarding.
I think the thing I struggle with the most is coming up with that initial idea to actually START a new project. On top of that, any project substantial enough to capture my interest is also way too large in scope for me to actually have the motivation to finish.
One thing that sometimes helps is starting a couple projects in parallel. That way, if you get tired of one, you can switch to the other. Eventually one might take hold, and the other gets abandoned.
Being able to focus on one thing for long amount of uninterrupted time can work wonders. Put everything else aside and just work on one task. I’ve noticed that the most impressive engineers have this trait.
I have only "finished" 1 project enough for re-use (publishing) when I didn't have an audience. The docs, examples, build, etc. took as long as the actual code.
When I'm doing things for myself, thinking about solving it and then actually solving it is like doing it twice, and I'm already bored. Once I prove (or disprove) my notion, hunch, whatever, I generally move on to the next shiny object.
One thing I think people discount about this is the subconscious cost/benefit analysis. When something is an interesting puzzle to solve, the benefit is really high and the cost of a POC version is really low. Once it comes time to turn that POC into a workable product the cost/benefit changes. It loses all the value it had for being interesting and the cost shoots way up. It's easier to not have this problem when the lost value is replaced by money.
I think most projects where a person is trying to learn something such that it builds on something else are often not understood by a person who is not on the same path. In the end, the tapestry formed is only understood by the one doing the journey and then in hindsight it all makes perfect sense.
I'm actually cleaning up my GitHub repos at this very moment. So much basic stuff not really useful to anyone, things that other people clearly have done many times better. Old messy code, new messy code .... but to me they're experiences and represent invaluable learning that I've done.
I look at some repo that is a mess, but I remember making widget X work and how great that felt.
When someone says they created something I always think about what they must have learned to do that something, and not so much about the actual something.
I get a bit frustrated when folks critique something about "Yeah but this other thing" and so forth. For me that's almost never the point...
Kinda-related, but a few months back I started working on a Web wrapper[0] for a specific GameBoy ROM, Little Sound Dj (LSDj.) See it in action here: [1]
I was able to get the project up and running using a third-party JavaScript emulator[2], but the library was basically abandoned years ago and the sound in modern browsers is very... odd. Unfortunately, sound is crucially important to music creation software like LSDj.
I'm also mystified that there doesn't seem to be a .SAV format that's cross-compatible between emulators, which is required for basically the entire purpose of the app. (Users would work on a project online on any device, then transfer the .SAV to real hardware for recording the tune.)
If anyone has advice on these topics I'd love to talk!
Re: Sound quality: I wonder if you might have more success compiling a more well-known C (or similar) based emulator down to webasm and running it that way.
> So why did I spend all this time trying to write it? I treated it as a programming riddle [...] Maybe because of these results I got quite addicted to the project itself [...] I guess it’s not too healthy (especially if you have a family), but it also doesn’t happen that often.
I can empathize, I think lots of us have GH profiles littered with projects we've hit real finish lines on but may not be useful to others. But I actually think it's healthy, especially if you have a family (assuming it's only your spare time). It provides sanity and brain maintenance simultaneously. Even better if you have an employer that doesn't track your every hour and recognizes the value of these kinds of things on down time.
> Right now I don’t have any obvious ways to move the app forward, so the addiction dissipated ;)
In "science projects" like these, you pay in time to recieve experience. By day I do Javascript at work, but my work on a passion project resulted in a wealth of OpenGL and C++ experience. Enough that when I was tapped to do work on our C backend, I hit the ground running quick.
Doing it 1.5 months is impressive, it took me almost 6! [1]
I agree with the post that it was a very rewarding (if frustrating) project. But once you see that Nintendo logo come down the screen for the first time it's almost like magic.
This might seem like a roundabout way to start, but I'd recommend Code by Charles Petzold[0].
It starts out with just wires, switches, and relays, and as the book progresses he goes through the process of building up a simple CPU and RAM one step at a time. The book even walks you through coming up with opcodes and assembly language.
Even if you already know this stuff, I found the book was helpful in developing an intuitive feel for how everything works and fits together.
After reading it, you'd probably have a good mental model of how you'd want to approach writing an emulator.
The Nand to Tetris courses and their accompanying textbook would probably be helpful here too[1][2].
You could try writing a homebrew game for an old system. That's a pretty good way to learn things.
Once you start on the emulator, the first thing you'll do is emulate the CPU, which can be done as a big switch statement inside of a loop. Knowing how to code each instruction is easy when you have an instruction listing handy.
Here's a quick example of how I would write an emulator, with 2 opcodes already implemented (6502 assembly): https://pastebin.com/raw/PhCEqh35
When I tested this in the distant past, I found that all the compilers I tried generated exactly the same assembly for both loops.
I've seen it said that "The most optimizing compiler is the most normalizing compiler." [1] The infinite loop structure is pretty easy to normalize.
The only real difference that I saw was that some compiler front ends would produce warnings that the condition in the while loop was always true, where they seemed to assume the empty for must be intentional.
I've been looking into writing an emulator for a while now. There are some excellent references for specific systems, but one of the best sources I've found is here:
In addition to having source code for several different emulators, they've written a ~150 page document about emulator development. It's easily the best resource I've seen on the subject.
Chip8 is a good one to cut your teeth on. Very simple, good examples, does not take too long, but once you've done it, its much easier to see how more complex ones could work.
Well I've already gotten simple 2D games working in bare-metal ARM with OLED and TFT displays, it's more the emulation aspect of things that is intimidating.
ARM Cortex-M cores are also a bit different from the Application-class processors used in boards like the Raspberry Pi. They are closer to an Arduino than a "real computer".
Haha, no worries - embedded stuff sometimes feels so confusing that I think I misunderstand myself most of the time.
Yeah, I've wanted to learn more about the ESP32 - it seems like people have done some really cool stuff with it and it looks like you get a lot of power and connectivity.
But I'll bet you can empathize with not having enough hours in the day!
The upside is that the Raspberry Pi has good documentation.
The downside is that you'll need to emulate 4 cores plus a GPU. Guest software (the stuff being emulated) may fail if the cores aren't decently fast. You may also need to do USB, audio, wireless LAN, and Ethernet.
Oh, cool! Thanks for that link, the STM32F4 is pretty close to the F0/L0/L4 series that I've been learning about; I was actually thinking of using an L082KZ to start with because it has a lot of Flash, built-in EEPROM, a simple M0+ core, and is easy to hand-solder.
Cool idea using VGA as the output; was that hard, or could you just use the DACs normally? I was looking into parallel LCD interfaces for awhile - some F4 cores have a 24-bit RGB LCD peripheral - but they look complicated and I'm still puttering around with SPI OLED/TFT displays.
learn assembly, learn how it maps to opcodes. learn microelectronics and learn about cpu, registers, memory layout, cpu flags, etc. it becomes obvious.
i wrote a 6800 emulator just from having this background knowledge and no reference in my college years. I did it because I didn't want to go to the labs early saturday morning to fight over the few 6800 hardware. I could write my code, run, debug, show up just as the lab is ending and hand in my work and go home.
I started with the MOS 6502, the core of the NES, Atari 2600, C64, and a bunch of others. I wrote it in order to build a really simple NES emulator, and I found the chip super approachable and easy to deal with--there just isn't much to it. Documentation is easy to find, too.
Agreed. If you have 6502 nostalgia, there are some really good tests, and you can basically just keep fixing test failures until they all go away, and you'll have a working chip emulation.
This blog made my morning. Started reading about this Gameboy emulator - really cool project. Then read through a few of his other posts including the one about resurrecting a Macintosh Plus: http://blog.rekawek.eu/2016/12/08/mac-plus/
Hoping he'll post more soon.
> It was also very addicting - every time I had a few minutes during the day and basically every evening I felt an irresistable urge to move the emulation a bit forward - fix this strange GPU bug, pass one more compatibility test or implement one more missing feature.
This resonated strongly with me. I did a study of architectures in college where i built a bunch of emulators of real and made up chips and fiddling with the details of an emulator is incredibly engrossing.
"First I implemented all the Gameboy CPU opcodes. It’s not exactly the Z80, but it’s pretty close" - The CPU is close to 8080 which the Z80 is derived from, so hence the Z80 similarity. However all the main Z80 enhancements are missing. A+B-B=A
The Gameboy CPU looks more like a stripped-down Z80 to me, it doesn't have the ED, DD and FD extended instruction ranges of the Z80 (most of those aren't that useful anyway), and the shadow register set is also missing, but the complete CB instruction range exists (bit twiddling and additional shift/rotate instructions). Also the relative jump instructions of the Z80 are there which are redundant NOPs in the 8080 instruction set.
I think the missing IX and IY are definitive Z80 instructions. The 'Ultimate Gameboy talk' has a nice Venn diagram and they tend to agree that the GB is missing the 'interesting' instructions from the Z80. However the added shifts etc from Z80 are quite useful. But on the continuum between 8080 to Z80 the GB80 is closer to 8080
I love emulators like this person too. My dream has been to write a TI-85 emulator. I know there is a few out there, but none for the Mac. I used that calculator a ton in HS, and that's where my love for the Z80 came from.
Most hackers worth a shit know the answer to the question without even any context.
It's like a lust to solve a problem. Idk maybe it IS add, I'm there too, but sometimes there are software problems that just completely captivate me in a way that nothing else does.
Time melts away, and I'm just typing.
Y'all know that feeling, that 6am,"oh shit I work in three hours but I was hacking on this project all night and it almost works if I could just get this thing over here to- Oh, work, real world.... Right."
That mental mode of concentration is elusive and indeed a bit habit forming. There are moments where I'll be washing my car or cleaning the house and I have that "I should be in vim right now" thought and the world just seems grayer and dumber until I survive past it and sit back at my keyboard again.
Said like that, that is weirdly analogous to when I used to drink.
Is programming a drug? Are some addictions healthy?
It's funny how incredibly highly the money and success narrative is emphasized in the news, and the drug-like high aspect of the work is rarely mentioned. The truth is that a lot of us would do it (and do do it) for free, and the high demand aspect of it is to a large extent just a blessing on the side.
I started for the glory. Refurbishing keygens and writing ddos perl scripts for irssi.
But the more I learned, the bigger the problems I want to solve, (and create, in some cases) and that drove me to become more dedicated to the discipline of it all.
I'm blessed to have the opportunity to do contracts or work a 9-5 with my skillset, and I can basically live whatever lifestyle I can afford that way.
But yeah, even if I was getting paid 1/5 of what I have now, as long as I can spend my days browsing through disassembly, outsmarting someone, I'd still show up.
I don't think this is specific to programming, but generalizes to any kind of problem solving (as you write in your second sentence).
I was once taking a shower when I cried out in joy as I had just figured out how to shave off an extra fraction of a micron from the side of a large state machine (we had a last minute change order and it hogged a bunch of area on the chip). I grabbed some clothes and jumped into my car (didn't think to rinse the shampoo out of my hair) and floored it down the freeway to the office (VPN? hah!)
One example to illustrate how simple the Game Boy is: There is no operating system at all, only a 256 byte boot ROM whose sole purpose is to display the Nintendo logo from the cartridge and halt if it does not checksum correctly (for both cartridge integrity and legal reasons).
This entertaining 33c3 talk manages to describe the Game Boy hardware practically in its entirety (and with some previously unknown details, actually): https://www.youtube.com/watch?v=HyzD8pNlpwI