Hello guys,
I have been interested in developing a NES emulator for quite some time and I was wondering if anyone would be willing to give me steps to follow to create my own? I have a lot of programming experience, so I am hoping I should be fine in that regards.
Any help is greatly appreciated!
Thanks
Creating an Emulator
Moderator: Moderators
There's no real definative steps that I can give you.... but as for some general advise:
- Get as much documentation and as many references as you need. I would start with NEStech (old and has minor errors, but still overall pretty decent), and obelisk. Try to get as good of an understanding as to how the NES works as you can.
- Build a simplistic tracer. The thing about making an emu is it's a lot of 'behind-the-scenes' work. So you'll end up writing pages and pages and pages of code without being able to test any of it -- and the when you finally do test it, it'll either work great, or (more likely) will fail horribly. What's more, the emu output often offers little or no signal as to where the problem is; running a game and just getting black screen could be caused by any one of a thousand problems. Having a custom tracer that shows what the game is doing, and even potentially laying out exactly where the game is locking up or crashing is invaluable. Not only when you're starting out with the emu, but also later when you start adding mapper support.
- Keep some potential future problems in mind when designing your emu structure. You don't want to get blindsided by something you forgot to take into account. Most things can be worked in... but some stuff can be a really big pain if you don't plan for them ahead of time. A handful of things you may want to remember:
+ There are 3 PPU cycles to 1 CPU cycle on NTSC... but 3.2 PPU cycles to 1 CPU cycle on PAL! So don't stick to a strict 3:1 ratio if you plan on ever adding PAL support.
+ Mappers can (and will) do some very strange things... including:
* put PRG-ROM at $6000-7FFF (don't always assume this will be RAM)
* change mirroring in funky ways -- specifically, using CHR-ROM as a nametable!
* have both CHR-ROM and CHR-RAM swapped in at the same time.
That's not counting all the crazy tricks MMC5 does -- but most of MMC5's stuff shouldn't be all that hard to work in somehow, so I wouldn't worry about it.
That's about all I can think of. Don't be afraid to ask questions ^^. Good luck!
- Get as much documentation and as many references as you need. I would start with NEStech (old and has minor errors, but still overall pretty decent), and obelisk. Try to get as good of an understanding as to how the NES works as you can.
- Build a simplistic tracer. The thing about making an emu is it's a lot of 'behind-the-scenes' work. So you'll end up writing pages and pages and pages of code without being able to test any of it -- and the when you finally do test it, it'll either work great, or (more likely) will fail horribly. What's more, the emu output often offers little or no signal as to where the problem is; running a game and just getting black screen could be caused by any one of a thousand problems. Having a custom tracer that shows what the game is doing, and even potentially laying out exactly where the game is locking up or crashing is invaluable. Not only when you're starting out with the emu, but also later when you start adding mapper support.
- Keep some potential future problems in mind when designing your emu structure. You don't want to get blindsided by something you forgot to take into account. Most things can be worked in... but some stuff can be a really big pain if you don't plan for them ahead of time. A handful of things you may want to remember:
+ There are 3 PPU cycles to 1 CPU cycle on NTSC... but 3.2 PPU cycles to 1 CPU cycle on PAL! So don't stick to a strict 3:1 ratio if you plan on ever adding PAL support.
+ Mappers can (and will) do some very strange things... including:
* put PRG-ROM at $6000-7FFF (don't always assume this will be RAM)
* change mirroring in funky ways -- specifically, using CHR-ROM as a nametable!
* have both CHR-ROM and CHR-RAM swapped in at the same time.
That's not counting all the crazy tricks MMC5 does -- but most of MMC5's stuff shouldn't be all that hard to work in somehow, so I wouldn't worry about it.
That's about all I can think of. Don't be afraid to ask questions ^^. Good luck!
-
NotTheCommonDose
- Posts: 523
- Joined: Thu Jun 29, 2006 7:44 pm
- Location: lolz!
Well my motivation for creating an emulator is primarily for personal reasons. There are several sophisticated NES emulators already developed and freely distributed. I was hoping to learn more about the methodology of creating an emulator. I want to learn about how other architectures ( besides X86) function and how I could convert one to the other. I also want to broaden my programming experience etc. Fortunately for me, creating an emulator could help me with all these desires.
I think #nesdev is on EFnet -- although I don't go in there much. When I have, the discussion always seemed to be kevtris and Q going on about crazy hardware stuff that was way over my head (shows how long it's been since I've gone in there -- Q has been gone for quite a while now)
I regularly idle/talk in #rom-hacking on espernet. The general discussion there varies (seems to be about just about everything but ROM hacking). I think I'm the only person there who's done any kind of emu dev -- although there are several other programmers and general smart people (but there's no shortage of that in #nesdev)
I regularly idle/talk in #rom-hacking on espernet. The general discussion there varies (seems to be about just about everything but ROM hacking). I think I'm the only person there who's done any kind of emu dev -- although there are several other programmers and general smart people (but there's no shortage of that in #nesdev)
Few questions please.
1. Why is mirroring needed. If I understand it right then there are places in memory that are exact copy of the $0000 - $0800 area. Is it right?
2. When I write an emulator can I use the standard algorithem (loop and exec the opcodes), or I should implement something spacial for ppu and the chip that handales the sound?
3. Can someone explain the bank switching?
4. In general, how do I manipulate the memory? Can you give an example of implementation? (I assume it is not as simple as array of bytes
)
Thank you.
2. When I write an emulator can I use the standard algorithem (loop and exec the opcodes), or I should implement something spacial for ppu and the chip that handales the sound?
3. Can someone explain the bank switching?
4. In general, how do I manipulate the memory? Can you give an example of implementation? (I assume it is not as simple as array of bytes
Thank you.
Re: Few questions please.
Mirroring exists when there's more address space designated than there is physical memory. For RAM mirroring, this happens because there is 8k addresses and only 2k of actual memory. So what happens is that the high bits of the address simply go unused, and the same byte of RAM appears at four seperate addresses.alkhimey wrote:1. Why is mirroring needed. If I understand it right then there are places in memory that are exact copy of the $0000 - $0800 area. Is it right?
RAM mirroring is easily accomplished by ANDing the address with a value:
Code: Select all
byte read_ram(word address)
{
return RAM[ address & 0x7FF ];
}
There are several ways to approach this. I break the emu up into sections (CPU, PPU, APU, mapper) and assign each of them a timestamp. I run the CPU first (the opcode loop you describe), and when it does something that affects another system (like say, writes to a PPU reg) I run the PPU up to the current CPU timestamp to "sync it up".2. When I write an emulator can I use the standard algorithem (loop and exec the opcodes), or I should implement something spacial for ppu and the chip that handales the sound?
I wrote this doc recently:3. Can someone explain the bank switching?
http://www.romhacking.net/docs/353/
Also, this might help too:
http://www.romhacking.net/docs/362/
I keep one buffer for system RAM, one for on-cartridge PRG-RAM, one for CHR-ROM, one for CHR-RAM, and one for PRG-ROM. When loading the ROM, I load PRG/CHR ROM into the appropriate buffers. I also keep 16 function pointers for CPU reading, and 16 for CPU writing (one for each 4k of addressing space)... and any time the CPU reads or writes a byte (once every CPU cycle) I have it call the appropriate function.4. In general, how do I manipulate the memory? Can you give an example of implementation? (I assume it is not as simple as array of bytes :roll: )
ie:
readfunc[0] would read RAM ($0xxx)
readfunc[1] would read RAM ($1xxx)
readfunc[2] would read PPU Regs ($2xxx)
etc
that way reading can be done with:
value = readfunc[ address >> 12 ]( address );
and it will be mapped to the desired area.
For PRG/CHR swapping and NT Mirroring, I keep a series of pointers (one pointer represents a 4k bank of PRG, or a 1k bank of CHR, or a 1k nametable), and to swap PRG/CHR or change mirroring modes, I simply change what those pointers point to -- it's a lot easier to change a pointer than it is to copy large chunks of memory every time the game swaps (which is often several times per frame).