First steps in writing an emulator
Moderator: Moderators
First steps in writing an emulator
Hey guys, I'm new here. Glad I found the place
I've been interested in emulators since I found out they existed. I downloaded a N64 emulator and started playing old N64 games from my childhood. The N64 was my favorite, and the nostalgia of playing those games again was great
I thought it would be interesting to write an emulator. Yesterday I started an NES emulator. I've been doing a LOT of reading, and a bit of programming.
I'm using C++. So far, I wrote a NES class that holds all the other components. I made a CPU class, and started a cpuCycle() function, which has switch statement for the opcodes. I plan to write a function for each opcode, and a function for each addressing mode. That way, I'd have a lot less functions compared to if I wrote a function for each unique opcode with addressing mode. That should work, right? I also wrote a Memory class, which doesn't do much besides have methods for reading and writing. I believe I understand mirroring, but it seems strange. When you write to an address on the CPU memory, it gets mirrored to the corresponding mirror addresses. Can I program this explicitly into the CPU Memory class? Can anything change how the CPU memory is mirrored? I also wrote a ROM loader. It reads the header, and loads the 1 or 2 program banks into the CPU PRG memory. I'm only going to worry about ROMs with 1 or 2 program banks, until I get it running.
So now, my NES reads the ROM and loads the program banks into memory. Now what? I'm a little confused. The program counter starts at 0, right? Which corresponds to the very first byte in the CPU memory? What gets loaded there when the ROM loads? Or does the program counter start at the lower PRG bank?
I know, it's quite a few questions. But I'm finding it hard to find answers to simple questions like these. Trust me, I've been doing a LOT of reading. I'd like to have the CPU memory set up first, so I can start programming the opcodes. That should be my next step, right? Or are there other things I have to get set up first? Could someone give me a basic step-by-step order I should program things in? Some help would be GREATLY appreciated. Thanks
I've been interested in emulators since I found out they existed. I downloaded a N64 emulator and started playing old N64 games from my childhood. The N64 was my favorite, and the nostalgia of playing those games again was great
I thought it would be interesting to write an emulator. Yesterday I started an NES emulator. I've been doing a LOT of reading, and a bit of programming.
I'm using C++. So far, I wrote a NES class that holds all the other components. I made a CPU class, and started a cpuCycle() function, which has switch statement for the opcodes. I plan to write a function for each opcode, and a function for each addressing mode. That way, I'd have a lot less functions compared to if I wrote a function for each unique opcode with addressing mode. That should work, right? I also wrote a Memory class, which doesn't do much besides have methods for reading and writing. I believe I understand mirroring, but it seems strange. When you write to an address on the CPU memory, it gets mirrored to the corresponding mirror addresses. Can I program this explicitly into the CPU Memory class? Can anything change how the CPU memory is mirrored? I also wrote a ROM loader. It reads the header, and loads the 1 or 2 program banks into the CPU PRG memory. I'm only going to worry about ROMs with 1 or 2 program banks, until I get it running.
So now, my NES reads the ROM and loads the program banks into memory. Now what? I'm a little confused. The program counter starts at 0, right? Which corresponds to the very first byte in the CPU memory? What gets loaded there when the ROM loads? Or does the program counter start at the lower PRG bank?
I know, it's quite a few questions. But I'm finding it hard to find answers to simple questions like these. Trust me, I've been doing a LOT of reading. I'd like to have the CPU memory set up first, so I can start programming the opcodes. That should be my next step, right? Or are there other things I have to get set up first? Could someone give me a basic step-by-step order I should program things in? Some help would be GREATLY appreciated. Thanks
Last edited by janzdott on Wed Oct 16, 2019 8:57 pm, edited 2 times in total.
Re: First steps in writing an emulator
Take a look at SIDE and PIE.
A function per opcode and per addressing mode will work and could allow to organize things clearly, but don't forget that it could hit the performance a lot as well, with many thousands of funciton calls per frame (so think about inlining, call table, etc).
Read/write handlers is the place to implement mirroring, so yes, Memory class is the place.
When you writing an emulator, one of very first things to do is to take a look at memory map. If you do so, you'll realize NES can't start from address 0, because there is RAM. The answer is in 6502 docs, though, read about so called 'vectors' (namely reset vector), they are the thing that tells CPU where to start and where interrupt handlers are located.
And kind of an offtopic response, I've been a programmer for about 20 years now, wrote few emulators, but still I wouldn't say I'm a pretty good programmer.
A function per opcode and per addressing mode will work and could allow to organize things clearly, but don't forget that it could hit the performance a lot as well, with many thousands of funciton calls per frame (so think about inlining, call table, etc).
Read/write handlers is the place to implement mirroring, so yes, Memory class is the place.
When you writing an emulator, one of very first things to do is to take a look at memory map. If you do so, you'll realize NES can't start from address 0, because there is RAM. The answer is in 6502 docs, though, read about so called 'vectors' (namely reset vector), they are the thing that tells CPU where to start and where interrupt handlers are located.
And kind of an offtopic response, I've been a programmer for about 20 years now, wrote few emulators, but still I wouldn't say I'm a pretty good programmer.
Re: First steps in writing an emulator
Thank you. Yeah, I'm going to inline the functions. I'm not too worried about performance yet. I just want to get it up and running before I worry about optimization. So the reset vector tells the program counter where to start? If I have the program banks loaded into memory, and get memory mirroring set up, and set the program counter to the reset vector, can I start programming opcodes and letting the CPU cycle?
Re: First steps in writing an emulator
Yes, the program counter is loaded with value from the reset vector at reset.
I'd say that writing and debugging a 6502 emulator with a NES emulator at once is way more difficult thing to do than doing these things separately, because you could make mistakes in both counterparts without being sure where it is. A better approach for start would be just writing 6502 emulator, with very simple abstact system, like plain 64K RAM where you will manually put some test code. Just make your 6502 emulation code well separated from everything else, and you'll be able to both debug it in the test enviroment (when you need to catch a bug), and use it in complete NES emulator enviroment.
I'd say that writing and debugging a 6502 emulator with a NES emulator at once is way more difficult thing to do than doing these things separately, because you could make mistakes in both counterparts without being sure where it is. A better approach for start would be just writing 6502 emulator, with very simple abstact system, like plain 64K RAM where you will manually put some test code. Just make your 6502 emulation code well separated from everything else, and you'll be able to both debug it in the test enviroment (when you need to catch a bug), and use it in complete NES emulator enviroment.
Re: First steps in writing an emulator
The best way to handle memory mirroring is doing it like the hardware does: partially decoding addresses. The reason $0000-$07FF is mirrored all the way up to $1FFF is because the NES only has 2KB of memory to fill a space of 8KB. Once the NES detects that $0000-$1FFF is being accessed (detecting $0000-$07FF would need more hardware, because it would have to watch more address lines), it uses the 11 bits it takes to address 2KB and ignores the remaining 2 address line it would take to access 8KB. Mirroring is just a side effect of ignoring some address lines, because it's cheaper to do so. You can do the same thing in software.janzdott wrote:When you write to an address on the CPU memory, it gets mirrored to the corresponding mirror addresses. Can I program this explicitly into the CPU Memory class?
Carts can map anything they want from $4020 to $FFFF. Even though this is not terribly common, it's certainly possible.Can anything change how the CPU memory is mirrored?
Keep in mind that the NES doesn't "load" anything, no data is copied anywhere. It instantaneously sees the ROM when it's powered on. Even when bankswitching is used nothing is ever copied anywhere, what happens is that the address lines are manipulated to make different sections of a larger memory chip visible in the small window that the CPU can see.I also wrote a ROM loader. It reads the header, and loads the 1 or 2 program banks into the CPU PRG memory. I'm only going to worry about ROMs with 1 or 2 program banks, until I get it running.
Nope. It starts at the address pointed by the RESET vector. The 6502 looks for 3 special addresses at the end of the addressing space: $FFFA-$FFFF contain the addresses the CPU is supposed to jump to in case of an NMI, RESET or IRQ. If the RESET vector points to $0000 then the CPU will try to execute code from there, but that wouldn't really work because $0000 is RAM, and right after power on it's contents are undefined (NMI and IRQ can safely point to RAM if the program puts the code to handle these interrupts there though).The program counter starts at 0, right?
Nothing, which means the CPU would try to execute a "random" sequence of undefined bytes as if they were code... that's a certain crash!What gets loaded there when the ROM loads?
The CPU is a good place to start. Keep in mind that you must have the CPU, PPU and APU running in parallel at all times, each one doing there thing and interacting with each other, so you'll have to program these components in a way that they can either run little by little one at a time (often called "cycle-by-cycle", which is somewhat slow depending on the machine that's running the emulator), or predict when the next interaction with other components will be and emulate up until that point (this is significantly harder!).I'd like to have the CPU memory set up first, so I can start programming the opcodes. That should be my next step, right? Or are there other things I have to get set up first? Could someone give me a basic step-by-step order I should program things in?
Nintendulator is one of the most accurate NES emulators out there, so it's no surprise that it's source code is pretty complex. I'm not sure how simple you can keep things if you plan on making accurate emulators, because there's all sorts of little hardware quirks that make it impossible to solve problems with straightforward solutions. If you just want to get games running (as opposed to faithfully emulating all hardware aspects), things get easier and you can make use of game-specific hacks (this makes your emulator suck as a game development tool though, since it's specifically tailored for existing games). I believe most N64 emulators are like that though, since cycle by cycle emulation gets incredibly slower as the complexity of the systems increases.I looked at the Nintendulator source, and it's crazy. I'm a freak about commenting, organizing, and simplifying my code.
Re: First steps in writing an emulator
Thanks guys. That answered all my basic questions. So, for memory mirroring, the NES has to know when memory gets accessed, right? Would it work if I kept an array of function pointers that get called when the memory is read from or written to? And about the whole CPU testing... I found a website that had programs that test the CPU. It might have been this website actually, I don't remember. But that'll definitely be my next step. Thanks guys
Re: First steps in writing an emulator
Also, this really should be moved to the NESemdev section...
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Re: First steps in writing an emulator
Well it says NESemdev now, but someone must've moved it. Sorry! I'll post any further questions I have in this thread when they come up
- cpow
- NESICIDE developer
- Posts: 1097
- Joined: Mon Oct 13, 2008 7:55 pm
- Location: Minneapolis, MN
- Contact:
Re: First steps in writing an emulator
Yep that would work.janzdott wrote:Would it work if I kept an array of function pointers that get called when the memory is read from or written to?
I gathered oodles of them and put them up on GitHub here.janzdott wrote:And about the whole CPU testing... I found a website that had programs that test the CPU. It might have been this website actually, I don't remember. But that'll definitely be my next step. Thanks guys
Re: First steps in writing an emulator
For what it's worth, I do "slow" CPU emulation with interrupt polling in each instruction and no prediction, and CPU emulation accounts for about 4-5% of the runtime in my emulator (out of a total of using about 40% of one core on my two-year-old Core-i7 2600K). Most of that is in the read() and write() routines. My PPU code is very slow due to rendering pixel-for-pixel and doing sprite/bg pixel selection and sprite evaluation like the real PPU.
The point is that you should know your range of target systems before optimizing, and not optimize parts of your program prematurely on a guess that they'll be significant. For modern desktop systems, you can usually get away with doing the straightforward thing in an NES emulator.
The point is that you should know your range of target systems before optimizing, and not optimize parts of your program prematurely on a guess that they'll be significant. For modern desktop systems, you can usually get away with doing the straightforward thing in an NES emulator.
Re: First steps in writing an emulator
When it comes to inlining, one thing you should definitely do is whole-program/link-time optimization. When you're callings lots of function per tick, letting the compiler inline across compilation units can help a lot.
Re: First steps in writing an emulator
Hmm, I was going to write a interrupt function that just sets the program counter whenever it's called. How did you set it up to check for interrupts every cycle? I would imagine that's how the CPU actually handles interrupts, but I'm not very knowledgeable about this stuff... yetulfalizer wrote:For what it's worth, I do "slow" CPU emulation with interrupt polling in each instruction and no prediction
Though I do have about half of my opcodes written now. But I can't test it until they're all done. It's probably gonna be riddled with bugs. I'll stay up late working on it, and I'll find out tonight!
Oh, and one more dumb question to clarify something haha. Each memory read/write uses one cycle, right? I've been looking at a table that shows how many cycles each opcode uses. Do I just waste dummy cycle(s) for the PPU and APU to catch up when the number of cycles in my opcode doesn't match the table? I'm a little confused by this, and obviously keeping the timing correct is very important.
Re: First steps in writing an emulator
The CPU checks for interrupts each instruction. The precise details are at http://wiki.nesdev.com/w/index.php/CPU_interrupts, though it's overkill to get the timing exactly right when starting out (you could just check for pending interrupts between instructions). In addition to setting the program counter, an interrupt also saves the old program counter and the flags on the stack.janzdott wrote:Hmm, I was going to write a interrupt function that just sets the program counter whenever it's called. How did you set it up to check for interrupts every cycle? I would imagine that's how the CPU actually handles interrupts, but I'm not very knowledgeable about this stuff... yetulfalizer wrote:For what it's worth, I do "slow" CPU emulation with interrupt polling in each instruction and no prediction
Yup, each read/write is one cycle. In fact, every cycle executed by the 6502 is either a read or a write cycle, with some being dummy reads/writes that don't do any useful work. Those can still be significant for some games due to reads/writes to certain addresses having side effects though - see Cobra Triangle in http://wiki.nesdev.com/w/index.php/Tric ... late_games.janzdott wrote:Oh, and one more dumb question to clarify something haha. Each memory read/write uses one cycle, right? I've been looking at a table that shows how many cycles each opcode uses. Do I just waste dummy cycle(s) for the PPU and APU to catch up when the number of cycles in my opcode doesn't match the table? I'm a little confused by this, and obviously keeping the timing correct is very important.
You can look in http://nesdev.com/6502_cpu.txt to see what reads/writes are done for different instructions. Implementing the instructions like in that doc is feasible, and makes the timing work out "automagically" without tables. You can also factor out the fetch of the opcode and the byte after that, since all instructions do it.
Re: First steps in writing an emulator
Thanks, that's a pretty helpful page. I hadn't read that one before. But I'm a little confused. It says, "The processors also use a sort of pipelining. If an instruction does not store data in memory on its last cycle, the processor can fetch the opcode of the next instruction while executing the last cycle." So only the opcodes that don't store data in memory on the last cycle do that, or do all of them? Is it necessary to emulate that behavior?ulfalizer wrote:You can look in http://nesdev.com/6502_cpu.txt to see what reads/writes are done for different instructions. Implementing the instructions like in that doc is feasible, and makes the timing work out "automagically" without tables. You can also factor out the fetch of the opcode and the byte after that, since all instructions do it.
And by the way, thanks for helping guys. I wasn't expecting this forum to be as active as it is! Time to get crackin' on the keyboard and load up a test program and get this CPU up and running tonight I'm expecting bugs up the wazzoo though haha
Re: First steps in writing an emulator
Well I tried out a CPU test ROM, and a hard time getting it to work, but there was a problem with my ROM loading. I got it working now, and it executes a lot of code until it hits a loop. I'm not sure if that's intended or not. I do have 4 opcodes that aren't implemented yet. I wrote a function that dumps the memory to a text file. My next step is to write one that dumps the opcode and all the registers for each cycle, so I can compare it to the correct log file and see what problems I have to fix.