NES Emulator: Day 1 - 4 (cont.)

Starting with a new blank project, I created a standard SDL app that does nothing but create a window, and run in a message loop -- processing the standard messages as needed. I also decided to keep it a console app as well so that I can easily output debugging information with printfs.

I created a class called CNES2A03 to act as the processor emulator. It didn't take long to come up with a design that I believe will work well for my emulator. First, I created a struct to hold the processor's registers (PC, SP, A, X, Y, and P) -- easy enough.

In order to actually "run" the processor, I created a function called "processOpcode()". This function reads in the opcode pointed to it by the PC (program counter) flag. At this point, I needed some sort of way of parsing this opcode and calling specific logic depending on which OP it is. For this, I decided to create two arrays, both of size 0xFF (0xFF is the max number of opcodes the processor can support). The first array holds function pointers (pointers to specific opcode handlers). The second array holds the operand type of the specific opcode. I could have used one array and a struct, but whatever.

Next, I started stubbing out all of the opcodes in the 6502 doc. But pretty quickly, I saw that if I needed to change something, the code would get REALLY messy, so I took a step back and thought about things.

What I eventually came up with is a combination of 3 preprocessor definitions: OPPROTO(x) for prototyping each Opcode handler in the class header, OPDEF(x) for the actual opcode definition in the class implimentation, and last but certianly not least -- OPH(opcode,opname,opparam), which I use in the class constructor to easily fill out my opcodes, opcode handlers, and param types. The end result is like this (in order of prototype declaration, implimentation, and class constructor code -- 2 in the case of this opcode):
OPPROTO(JMP);
OPDEF(JMP)
OPH(0x4C, JMP, AFP_ABSOLUTE)
OPH(0x6C, JMP, AFP_INDIRECT)

Obviously, this made the code much easier to manage. Here are the #define values:
#define OPDEF(x) bool CNES2A03::op ## x(CNES2A03* parent, OPCODE opcode, VAL8 opParam)
#define OPPROTO(x) static bool op ## x(CNES2A03* parent, OPCODE opcode, VAL8 opParam);
#define OPH(opcode,opname,opparam) opFunc[opcode] = &CNES2A03::op ## opname; opFuncParam[opcode] = opparam;

With this, it was simply a matter of going through the documentation, defining, and implimenting each opcode. I also made a function to easily push/pop the stack (stackPush() and stackPop()) properly in-code.

Lastly, I wrote two methods to read and write memory. I wanted to abstract the memory out of the processor (because the processor itself doesn't contain the system memory!). To do this, I simply added two function pointer variables, and made two helper functions to call the function pointers with the necessary parameters. I will later assign these pointers to my CNESEngine which actually contains the memory.

0 comments:

Post a Comment