I'm writing to extend a big thank you to the kind and patient people on this forum who took the time to share their knowledge with aspiring emu devs like myself. I appreciate all of the help you offered me and others as we dove into this esoteric and idiosyncratic world.
I started an NES emulator project in C with SDL on macOS in January 2017 with the simple goal of being able to play Donkey Kong. The only purpose of writing the emulator was for my own learning. And I did learn a lot—about the 6502, the NES, and I got a big refresher on some computer architecture concepts from over a decade ago in college.
Tonight, I achieved my original goal. I was able to play through a game of Donkey Kong on my simple, hackish NES emulator (without sound, I haven't done the APU). It was a bit anticlimactic, and more just surprising that it worked after my first proper attempt of doing joypad input. A much bigger achievement felt like the first time the PPU displayed anything correctly.
It may sound like this took 2 years, but it didn't really. I would work for a week, and then not touch the code again for 6 months. When I look at my commit history on GitHub (https://github.com/davecom/DDNES), there were a total of about 33 days that I actually made commits. Of course that doesn't include a lot of time reading documentation and this forum. But all in all, I think I could've probably gotten to this point in 1 month of continuous work in my spare time.
But I should be clear—I was not a purist about writing this very basic and simple emulator. I did look at other people's code when I got stuck. In particular, my PPU background rendering is largely a port of Michael Fogleman's NES emulator from Go to C (https://github.com/fogleman/nes). The hardest part of the project for me by far was understanding how the PPU works. I read a lot of documentation and ultimately porting Michael's code in retrospect may have been a mistake. For my simple goals, his cycle accurate emulator was overkill and probably more complicated than I needed. A simple scanline approach would probably have been a smarter idea given my goals. I did most of the rest of the emulator (CPU, ROM loading, Sprites, Memory Layout, Joypad input) from scratch, looking at various other emulators when I got absolutely stuck somewhere. And of course asking for help in these forums. Overall people in this forum were kind and helpful at every turn. I probably could not have done it in 33 days without you. Thank you again.
Here's some advice I have for other people doing this as a learning project with no goal beyond rendering something like Donkey Kong and that don't care much about accuracy in your learning project:
- Go for scanline rendering instead of cycle accurate rendering; that is unless you want to deal with a bunch of random internal latches and registers that you probably don't care about as far as your learning experience goes.
- If you do end up going for cycle accurate rendering, think of a scanline as your y coordinate and a cycle as your x coordinate. It's not actually that simple, but that idea will get you a long way when you're lost and first thinking about how to display stuff.
- Interrupts are actually really important to get anything displayed at all; I didn't realize this when I started. NMI is critical.
- I found the PPU so intimidating that just starting on it caused one of my 6 month pauses. I think it's probably best to start by writing a debugger that will output anything from the nametables or just a single frame. That would've been much more motivating than how I muddled through it.
- Sometimes what looks like an error in your PPU is actually an error in your CPU; I was overconfident that my CPU was working correctly; It passed nestest as far as I could see but my automated tests were not very good and I ended up comparing line by line manually; I should've spent the time to make better automated tests
- I spent a lot of time looking for a good "overall tutorial." I've read a lot of NES documentation. IMHO there IS NO GOOD SINGLE NES tutorial. There are so many incomplete ones that end at the start of the PPU. There are so many docs that are either too technical or not technical enough. There are few that strike the right balance and there is no single step-by-step NES tutorial from start to finish that holds your hand in the way that there are for the Gameboy and CHIP-8. This is surprising given how popular writing an NES emulator is as a project. Skimming soups-to-nuts Gameboy tutorials is actually pretty helpful given the lack of NES equivalents.
- The docs in the nesdev Wiki are probably the most comprehensive and definitive, but they usually need to be accompanied by reading a layman's terms forum post, looking at an actual emulator's code, or another document to give you the big picture overview.
- Consistently test one simple game until it works; My relentless pursuit of Donkey Kong, fixing bugs in it, even when other games didn't play at all helped me a lot in getting to this point and not getting distracted.
- Most importantly don't be afraid to come to this forum to get help. If I hadn't formed a kind of camaraderie here with another user, @iOSBrett, who was about at the same stage on his Mac based emulator as I was, I probably would've never felt inspired to finish the project.
My experience coming into this project was a decent knowledge of C, although I don't program in pure C regularly, and having already done a CHIP-8 emulator in Swift. All in all it was a good learning experience and probably worth the time. I may still add some more missing sprite features, implement the APU, and maybe another mapper or two. I'm not sure. But I've achieved my original goal and there is some satisfaction in that. I'm probably going to leave the world of NES emulators after this, but who knows what emulation project this will lead me to next. Hopefully something with a simple bitmapped graphics system and less memory fetch modes...
PS If you're developing on Mac, your best emulator with a debugger to help you in testing is probably Nintaco.
I started an NES emulator project in C with SDL on macOS in January 2017 with the simple goal of being able to play Donkey Kong. The only purpose of writing the emulator was for my own learning. And I did learn a lot—about the 6502, the NES, and I got a big refresher on some computer architecture concepts from over a decade ago in college.
Tonight, I achieved my original goal. I was able to play through a game of Donkey Kong on my simple, hackish NES emulator (without sound, I haven't done the APU). It was a bit anticlimactic, and more just surprising that it worked after my first proper attempt of doing joypad input. A much bigger achievement felt like the first time the PPU displayed anything correctly.
It may sound like this took 2 years, but it didn't really. I would work for a week, and then not touch the code again for 6 months. When I look at my commit history on GitHub (https://github.com/davecom/DDNES), there were a total of about 33 days that I actually made commits. Of course that doesn't include a lot of time reading documentation and this forum. But all in all, I think I could've probably gotten to this point in 1 month of continuous work in my spare time.
But I should be clear—I was not a purist about writing this very basic and simple emulator. I did look at other people's code when I got stuck. In particular, my PPU background rendering is largely a port of Michael Fogleman's NES emulator from Go to C (https://github.com/fogleman/nes). The hardest part of the project for me by far was understanding how the PPU works. I read a lot of documentation and ultimately porting Michael's code in retrospect may have been a mistake. For my simple goals, his cycle accurate emulator was overkill and probably more complicated than I needed. A simple scanline approach would probably have been a smarter idea given my goals. I did most of the rest of the emulator (CPU, ROM loading, Sprites, Memory Layout, Joypad input) from scratch, looking at various other emulators when I got absolutely stuck somewhere. And of course asking for help in these forums. Overall people in this forum were kind and helpful at every turn. I probably could not have done it in 33 days without you. Thank you again.
Here's some advice I have for other people doing this as a learning project with no goal beyond rendering something like Donkey Kong and that don't care much about accuracy in your learning project:
- Go for scanline rendering instead of cycle accurate rendering; that is unless you want to deal with a bunch of random internal latches and registers that you probably don't care about as far as your learning experience goes.
- If you do end up going for cycle accurate rendering, think of a scanline as your y coordinate and a cycle as your x coordinate. It's not actually that simple, but that idea will get you a long way when you're lost and first thinking about how to display stuff.
- Interrupts are actually really important to get anything displayed at all; I didn't realize this when I started. NMI is critical.
- I found the PPU so intimidating that just starting on it caused one of my 6 month pauses. I think it's probably best to start by writing a debugger that will output anything from the nametables or just a single frame. That would've been much more motivating than how I muddled through it.
- Sometimes what looks like an error in your PPU is actually an error in your CPU; I was overconfident that my CPU was working correctly; It passed nestest as far as I could see but my automated tests were not very good and I ended up comparing line by line manually; I should've spent the time to make better automated tests
- I spent a lot of time looking for a good "overall tutorial." I've read a lot of NES documentation. IMHO there IS NO GOOD SINGLE NES tutorial. There are so many incomplete ones that end at the start of the PPU. There are so many docs that are either too technical or not technical enough. There are few that strike the right balance and there is no single step-by-step NES tutorial from start to finish that holds your hand in the way that there are for the Gameboy and CHIP-8. This is surprising given how popular writing an NES emulator is as a project. Skimming soups-to-nuts Gameboy tutorials is actually pretty helpful given the lack of NES equivalents.
- The docs in the nesdev Wiki are probably the most comprehensive and definitive, but they usually need to be accompanied by reading a layman's terms forum post, looking at an actual emulator's code, or another document to give you the big picture overview.
- Consistently test one simple game until it works; My relentless pursuit of Donkey Kong, fixing bugs in it, even when other games didn't play at all helped me a lot in getting to this point and not getting distracted.
- Most importantly don't be afraid to come to this forum to get help. If I hadn't formed a kind of camaraderie here with another user, @iOSBrett, who was about at the same stage on his Mac based emulator as I was, I probably would've never felt inspired to finish the project.
My experience coming into this project was a decent knowledge of C, although I don't program in pure C regularly, and having already done a CHIP-8 emulator in Swift. All in all it was a good learning experience and probably worth the time. I may still add some more missing sprite features, implement the APU, and maybe another mapper or two. I'm not sure. But I've achieved my original goal and there is some satisfaction in that. I'm probably going to leave the world of NES emulators after this, but who knows what emulation project this will lead me to next. Hopefully something with a simple bitmapped graphics system and less memory fetch modes...
PS If you're developing on Mac, your best emulator with a debugger to help you in testing is probably Nintaco.