I don't have a switch in my CPU emulator (and it is 99% finished, and passes all CPU test roms).
Here' is the code:
Code: Select all
bool CNES2A03::processOpcode(void)
{
if (clockDuty > 0) {
clockDuty--;
if (clockDuty == 0) {
switch (dutyIntType) {
case NES_INTTYPE_NMI:
{
NES_FLAGCLEAR(regs.P, NES_PROCESSOR_STATUS_BREAK_COMMAND)
SETFLAG(regs.P, NES_PROCESSOR_STATUS_UNUSED, false)
stackPush(regs.PC >> 8);
stackPush(regs.PC & 0xFF);
stackPush(regs.P);
regs.PC = readMem(0xFFFA) + (readMem(0xFFFB) << 8);
} break;
case NES_INTTYPE_BRK:
{
NES_FLAGSET(regs.P, NES_PROCESSOR_STATUS_BREAK_COMMAND)
NESADDRESS16 addr = regs.PC + 1;
SETFLAG(regs.P, NES_PROCESSOR_STATUS_UNUSED, true)
stackPush(addr >> 8);
stackPush(addr & 0xFF);
stackPush(regs.P);
regs.PC = readMem(0xFFFE) + (readMem(0xFFFF) << 8);
} break;
}
}
}
bool result = false;
OPCODE opcode = readMem(regs.PC++);
if (opFunc[opcode] == NULL) {
return false; // TODO: handle invalid opcode
} else {
if (!opFunc[opcode](this, opcode, opFuncParam[opcode]))
return false;
}
return true;
}
So instead of a huge switch my code simply says:
Code: Select all
opFunc[opcode](this, opcode, opFuncParam[opcode])
Here's an example of an opcode implimentation:
Code: Select all
OPDEF(JMP)
{
/* JMP Jump. The program jumps to a label and continues from there.
An original 6502 has does not correctly fetch the target address if the indirect vector falls
on a page boundary (e.g. $xxFF where xx is and value from $00 to $FF). In this case fetches
the LSB from $xxFF as expected but takes the MSB from $xx00. This is fixed in some later chips
like the 65SC02. */
if (opParam == AFP_ABSOLUTE) {
NESADDRESS16 addr = NEXTOP;
addr += (NEXTOP << 8);
parent->regs.PC = addr;
} else {// Indirect
NESADDRESS16 srcAddr = NEXTOP;
srcAddr += (NEXTOP << 8);
NESADDRESS16 finalAddr;
#ifdef ENABLE_6502_BUGS
if ((srcAddr & 0xFF) == 0xFF)
{
// Emulate the original 6502 bug where fetching an indirect address on a page boundery
// for JMP opcodes causes the address to be loaded improperly.
finalAddr = parent->readMem(srcAddr);
finalAddr += (parent->readMem((srcAddr & 0xFF00)) << 8); // Emulate the page wrap bug
} else {
// Not on a page boundery -- do it the normal way.
finalAddr = parent->readMem(srcAddr) + (parent->readMem(srcAddr+1) << 8);
}
#else
finalAddr = parent->readMem(srcAddr);
finalAddr += (parent->readMem(srcAddr+1) << 8);
#endif // ENABLE_6502_BUGS
parent->regs.PC = finalAddr;
}
return true;
}
And the OPDEF macro...
Code: Select all
#define OPDEF(x) __inline bool CNES2A03::op ## x(CNES2A03* parent, OPCODE opcode, VAL8 opParam)
Runs super fast. Switch statements require a LOT of branching (one per case) that kills the (real) CPU's prefetch cache. Having the higher used opcodes at the top of the switch will help, but the speed results will be different for different opcodes.
I decided to go with a global function pointer array. There's only one call, and no branching (in regards to finding the proper opcode functionality). There may be some duplicated code in certain situations, but this method works for me and keeps things nice and clean. Besides the CPU isn't going to change once it's done, so a few extra lines is better than dotting your code with gotos.