Thank You and Reflections on Implementing a Basic NES Emu

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Thank You and Reflections on Implementing a Basic NES Emu
by on (#232682)
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.
Re: Thank You and Reflections on Implementing a Basic NES Em
by on (#232686)
I've been "doing this stuff" (nesdev/snesdev with a balanced focus of emulation) since their original inception.

Your write-up, and your bulletpoints especially, are incredibly accurate; I would go as far as to argue this should be a "must-read" for anyone considering developing a (or contributing to an existing) NES emulator. The PPU (incl. sprites), followed by the APU (a subject I literally avoid as much as possible), followed by mappers are the most complicated parts; the CPU is surprisingly easy, barring some "edge cases" (ADC/SBC carry and overflow, page wrapping, bits of P during certain operations, CPU bugs/quirks (there are several open-source emulators that don't implement the 6502 JMP page-wrap bug correctly), accurate cycle counting).

Focusing on a single game (ex. Donkey Kong), or at least a very limited subset of games (read: original mapper 0 titles like Donkey Kong, Pinball, Mario Bros, Clu Clu Land) is absolutely the right approach.

The same goes for implementing a rudimentary debugger or interactive feature set that can give you insights to both CPU and PPU internals as granular (if not more so) as per-CPU-instruction. And don't be afraid of printf() debugging either!

Authors should also early on come to terms with the fact that their emulators probably are not going to run every game under the sun without substantial rewrites (ex: scanline-based rendering vs. cycle-driven), and accepting that complicated games that do a lot of PPU or timing trickery likely won't run right is perfectly OK (read: do not become obsessive over, say, some obscure game has a glitchy scanline that only 4 people care about, or make such games/titles force you into re-writing your entire emulator in such a way that it now requires extreme CPU core/speed requirements. I've maintained for a long time now that "kludge fixes" for emulation on a per-ROM/per-game basis are certainly acceptable assuming the trade-off is that you end up saving a lot of CPU thus power/energy). Likewise, I can't tell you how many times I've heard of emulator authors getting really far into their project, getting some fun mapper-centric games emulated and working, only to have someone tell them "I tried to load {some game with critical timing behaviour or a rare-ish mapper that would require them to revamp their emulator}" followed by them attempting it, getting frustrated, and losing all interest/the project failing. Some examples from early days were finding out down the road that there are mappers that use smaller than 16KB PRG banks, or 1KB CHR banks, or that many games do large sums of PRG/CHR swapping constantly, causing their emulator to perform poorly due to implementing PRG/CHR swapping using memcpy() or equivalent.

I also greatly appreciated the fact that you acknowledged ROM-based CPU instruction tests are not infallible; they're handy but you can absolutely emulate an instruction that does the wrong thing + have some tests pass. I'd also add it helps to be able to program on the 6502/NES, as you then begin to understand architecture/design rather than just relying on testing tools and the like -- better to understand how something works than rely entirely on test ROMs.

As for why there are such a plethora of people doing NES emulators: *grins smugly* It's because people often think that the system is "old" and therefore "easy". "It just looks like a bunch of cute little pixel graphics moving around, it can't be that hard!" is a common mindset. Likewise, their own judgement gets clouded by things like an unhealthy obsessive fixation on the NES/Famicom itself -- meanwhile, other (and some would argue better or more interesting/rewarding) 8 and 16-bit consoles are oft neglected, or even things like useful/helpful tooling for reverse-engineering or development. The Emulators / Under Development section speaks for itself.

Anyway, circling back: kudos to you for the write-up, and an equally-sized kudos for your personal accomplishment; you deserve a thank-you just as much as those you've thanked. *thumbs up*
Re: Thank You and Reflections on Implementing a Basic NES Em
by on (#232729)
davecom wrote:
- 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.


True that ^

Me and a friend (aLaix) spent several years developing our own NES emulator (for both learning and fun), and we were indeed inspired by how much the system is loved in this community.

Man, we even rewrote the whole memory management modules (Mappers base code) and PPU (from tile-based to cycle accurate) at least twice. We couldn't never get the needed grit to do it if it weren't for the support here. Though we haven't released it yet, it has come to a state where it can play almost every game with the exception of games that abuse the DMC IRQ.

Very talented people with diverse skilsets together to preserve that part of the history of videogames, that's just awesome.

Those thanks are very well deserved.
Re: Thank You and Reflections on Implementing a Basic NES Em
by on (#233188)
Quote:
- 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.


Right back at you Dave, if it wasn't for you I would have given up on my emulator.

My experiences on this board pretty much mirror yours, without the help and support of you and the Guru's such as @koitsu @tepples @lidnariq @WedNESday @Quietust and many others I would never have gotten very far with my emulator, and the PPU in particular.

I also have used the forum, read the Wiki, re-read the Wiki, re-read the Wiki, and re-read the Wiki!! I studied Fogleman's code, debugged with Nintaco, read every PPU document, YouTube video, and webpage I could find.

Funnily I also felt the anticlimax when I got Donkey Kong completely working, my main rush was getting the first bit of graphics on the screen that looked like Donkey Kong, after that I was happy with each success I had, but not at the same level of the first rush.

The main things I learnt was:
- It is not easy, but it is fun (mostly).
- As you said Dave, don't be afraid to ask for help
- I am both dumber than I thought I was, and smarter than I thought I was.
- Don't give up, just take a break for a few weeks (or months).

This forum rocks!!!!