Created
October 14, 2015 18:49
-
-
Save thoughtpolice/bb23cdea6248d2eab65c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "nes/cpu.h" | |
/* ------------------------------------------------------------------------- */ | |
/* -- MOS-6502 emulation and specifics ------------------------------------- */ | |
namespace mos6502 | |
{ | |
// The MOS-6502 instruction set, as an X-Macro. This encodes the | |
// opcode, instruction name, address mode, cycle count, length, and | |
// page-crossing penalties of every valid 6502 instruction. | |
// | |
// Fields: | |
// - Opcode | |
// - Mnemonic | |
// - Addressing mode | |
// - Modified flags (using mos6502::instrmodflag) | |
// - Length of instruction | |
// - Cycle count | |
// - Page crossing penalty (if applicable), OR for branch | |
// instructions, cost of jumping to a new page. | |
#define MOS6502INSTR(_) \ | |
/* ADC -- Add with Carry */ \ | |
_(0x69, ADC, immediate, CZVN, 2, 2, 0) \ | |
_(0x65, ADC, zeropage, CZVN, 2, 3, 0) \ | |
_(0x75, ADC, zeropageX, CZVN, 2, 4, 0) \ | |
_(0x6d, ADC, absolute, CZVN, 3, 4, 0) \ | |
_(0x7d, ADC, absoluteX, CZVN, 3, 4, 1) \ | |
_(0x79, ADC, absoluteY, CZVN, 3, 4, 1) \ | |
_(0x61, ADC, indirectX, CZVN, 2, 6, 0) \ | |
_(0x71, ADC, indirectY, CZVN, 2, 5, 1) \ | |
/* AND -- Logical AND */ \ | |
_(0x29, AND, immediate, ZN, 2, 2, 0) \ | |
_(0x25, AND, zeropage, ZN, 2, 3, 0) \ | |
_(0x35, AND, zeropageX, ZN, 2, 4, 0) \ | |
_(0x2d, AND, absolute, ZN, 3, 4, 0) \ | |
_(0x3d, AND, absoluteX, ZN, 3, 4, 1) \ | |
_(0x39, AND, absoluteY, ZN, 3, 4, 1) \ | |
_(0x21, AND, indirectX, ZN, 2, 6, 0) \ | |
_(0x31, AND, indirectY, ZN, 2, 5, 1) \ | |
/* ASL -- Arithmetic Shift Left */ \ | |
_(0x0a, ASL, accumulator, CZN, 1, 2, 0) \ | |
_(0x06, ASL, zeropage, CZN, 2, 5, 0) \ | |
_(0x16, ASL, zeropageX, CZN, 2, 6, 0) \ | |
_(0x0e, ASL, absolute, CZN, 3, 6, 0) \ | |
_(0x1e, ASL, absoluteX, CZN, 3, 7, 0) \ | |
/* BCC -- Branch if Carry Clear */ \ | |
_(0x90, BCC, relative, none, 2, 2, 2) \ | |
/* BCS -- Branch if Carry Set */ \ | |
_(0xb0, BCS, relative, none, 2, 2, 2) \ | |
/* BEQ -- Branch if Equal */ \ | |
_(0xf0, BEQ, relative, none, 2, 2, 2) \ | |
/* BIT -- Bit Test */ \ | |
_(0x24, BIT, zeropage, ZVN, 2, 3, 0) \ | |
_(0x2c, BIT, absolute, ZVN, 3, 4, 0) \ | |
/* BMI -- Branch if Minus */ \ | |
_(0x30, BMI, relative, none, 2, 2, 2) \ | |
/* BNE -- Branch if Not Equal */ \ | |
_(0xd0, BNE, relative, none, 2, 2, 2) \ | |
/* BPL -- Branch if Positive */ \ | |
_(0x10, BPL, relative, none, 2, 2, 2) \ | |
/* BRK -- Force Interrupt */ \ | |
_(0x00, BRK, implicit, B, 1, 7, 0) \ | |
/* BVC -- Branch if Overflow Clear */ \ | |
_(0x50, BVC, relative, none, 2, 2, 2) \ | |
/* BVS -- Branch if Overflow Set */ \ | |
_(0x70, BVS, relative, none, 2, 2, 2) \ | |
/* CLC -- Clear Carry Flag */ \ | |
_(0x18, CLC, implicit, C, 1, 2, 0) \ | |
/* CLD -- Clear Decimal Mode */ \ | |
_(0xd8, CLD, implicit, D, 1, 2, 0) \ | |
/* CLI -- Clear Interrupt Disable */ \ | |
_(0x58, CLI, implicit, I, 1, 2, 0) \ | |
/* CLV -- Clear Overflow Flag */ \ | |
_(0xb8, CLV, implicit, V, 1, 2, 0) \ | |
/* CMP -- Compare */ \ | |
_(0xc9, CMP, immediate, CZN, 2, 2, 0) \ | |
_(0xc5, CMP, zeropage, CZN, 2, 3, 0) \ | |
_(0xd5, CMP, zeropageX, CZN, 2, 4, 0) \ | |
_(0xcd, CMP, absolute, CZN, 3, 4, 0) \ | |
_(0xdd, CMP, absoluteX, CZN, 3, 4, 1) \ | |
_(0xd9, CMP, absoluteY, CZN, 3, 4, 1) \ | |
_(0xc1, CMP, indirectX, CZN, 2, 6, 0) \ | |
_(0xd1, CMP, indirectY, CZN, 2, 5, 1) \ | |
/* CPX -- Compare X Register */ \ | |
_(0xe0, CPX, immediate, CZN, 2, 2, 0) \ | |
_(0xe4, CPX, zeropage, CZN, 2, 3, 0) \ | |
_(0xec, CPX, absolute, CZN, 3, 4, 0) \ | |
/* CPY -- Compare Y Register */ \ | |
_(0xc0, CPY, immediate, CZN, 2, 2, 0) \ | |
_(0xc4, CPY, zeropage, CZN, 2, 3, 0) \ | |
_(0xcc, CPY, absolute, CZN, 3, 4, 0) \ | |
/* DEC -- Decrement Memory */ \ | |
_(0xc6, DEC, zeropage, ZN, 2, 5, 0) \ | |
_(0xd6, DEC, zeropageX, ZN, 2, 6, 0) \ | |
_(0xce, DEC, absolute, ZN, 3, 6, 0) \ | |
_(0xde, DEC, absoluteX, ZN, 3, 7, 0) \ | |
/* DEX -- Decrement X Register */ \ | |
_(0xca, DEX, implicit, ZN, 1, 2, 0) \ | |
/* DEY -- Decrement Y Register */ \ | |
_(0x88, DEY, implicit, ZN, 1, 2, 0) \ | |
/* EOR -- Exclusive OR */ \ | |
_(0x49, EOR, immediate, ZN, 2, 2, 0) \ | |
_(0x45, EOR, zeropage, ZN, 2, 3, 0) \ | |
_(0x55, EOR, zeropageX, ZN, 2, 4, 0) \ | |
_(0x4d, EOR, absolute, ZN, 3, 4, 0) \ | |
_(0x5d, EOR, absoluteX, ZN, 3, 4, 1) \ | |
_(0x59, EOR, absoluteY, ZN, 3, 4, 1) \ | |
_(0x41, EOR, indirectX, ZN, 2, 6, 0) \ | |
_(0x51, EOR, indirectY, ZN, 2, 5, 1) \ | |
/* INC -- Increment Memory */ \ | |
_(0xe6, INC, zeropage, ZN, 2, 5, 0) \ | |
_(0xf6, INC, zeropageX, ZN, 2, 6, 0) \ | |
_(0xee, INC, absolute, ZN, 3, 6, 0) \ | |
_(0xfe, INC, absoluteX, ZN, 3, 7, 0) \ | |
/* INX -- Increment X Register */ \ | |
_(0xe8, INX, implicit, ZN, 1, 2, 0) \ | |
/* INY -- Increment Y Register */ \ | |
_(0xc8, INY, implicit, ZN, 1, 2, 0) \ | |
/* JMP -- Jump */ \ | |
_(0x4c, JMP, absolute, none, 3, 3, 0) \ | |
_(0x6c, JMP, indirect, none, 3, 5, 0) \ | |
/* JSR -- Jump to Subroutine */ \ | |
_(0x20, JSR, absolute, none, 3, 6, 0) \ | |
/* LDA -- Load Accumulator */ \ | |
_(0xa9, LDA, immediate, ZN, 2, 2, 0) \ | |
_(0xa5, LDA, zeropage, ZN, 2, 3, 0) \ | |
_(0xb5, LDA, zeropageX, ZN, 2, 4, 0) \ | |
_(0xad, LDA, absolute, ZN, 3, 4, 0) \ | |
_(0xbd, LDA, absoluteX, ZN, 3, 4, 1) \ | |
_(0xb9, LDA, absoluteY, ZN, 3, 4, 1) \ | |
_(0xa1, LDA, indirectX, ZN, 2, 5, 0) \ | |
_(0xb1, LDA, indirectY, ZN, 2, 6, 1) \ | |
/* LDX -- Load X Register */ \ | |
_(0xa2, LDX, immediate, ZN, 2, 2, 0) \ | |
_(0xa6, LDX, zeropage, ZN, 2, 3, 0) \ | |
_(0xb6, LDX, zeropageY, ZN, 2, 4, 0) \ | |
_(0xae, LDX, absolute, ZN, 3, 4, 0) \ | |
_(0xbe, LDX, absoluteY, ZN, 3, 4, 1) \ | |
/* LDY -- Load Y Register */ \ | |
_(0xa0, LDY, immediate, ZN, 2, 2, 0) \ | |
_(0xa4, LDY, zeropage, ZN, 2, 3, 0) \ | |
_(0xb4, LDY, zeropageX, ZN, 2, 4, 0) \ | |
_(0xac, LDY, absolute, ZN, 3, 4, 0) \ | |
_(0xbc, LDY, absoluteX, ZN, 3, 4, 1) \ | |
/* LSR -- Logical Shift Right */ \ | |
_(0x4a, LSR, accumulator, CZN, 1, 2, 0) \ | |
_(0x46, LSR, zeropage, CZN, 2, 5, 0) \ | |
_(0x56, LSR, zeropageX, CZN, 2, 6, 0) \ | |
_(0x4e, LSR, absolute, CZN, 3, 6, 0) \ | |
_(0x5e, LSR, absoluteX, CZN, 3, 7, 0) \ | |
/* NOP -- No Operation */ \ | |
_(0xea, NOP, implicit, none, 1, 2, 0) \ | |
/* ORA -- Inclusive OR */ \ | |
_(0x09, ORA, immediate, ZN, 2, 2, 0) \ | |
_(0x05, ORA, zeropage, ZN, 2, 3, 0) \ | |
_(0x15, ORA, zeropageX, ZN, 2, 4, 0) \ | |
_(0x0d, ORA, absolute, ZN, 3, 4, 0) \ | |
_(0x1d, ORA, absoluteX, ZN, 3, 4, 1) \ | |
_(0x19, ORA, absoluteY, ZN, 3, 4, 1) \ | |
_(0x01, ORA, indirectX, ZN, 2, 6, 0) \ | |
_(0x11, ORA, indirectY, ZN, 2, 5, 1) \ | |
/* PHA -- Push Accumulator */ \ | |
_(0x48, PHA, implicit, none, 1, 3, 0) \ | |
/* PHP -- Push Processor Status */ \ | |
_(0x08, PHP, implicit, none, 1, 3, 0) \ | |
/* PLA -- Pull Accumulator */ \ | |
_(0x68, PLA, implicit, ZN, 1, 4, 0) \ | |
/* PLP -- Pull Processor Status */ \ | |
_(0x28, PLP, implicit, none, 1, 4, 0) \ | |
/* ROL -- Rotate Left */ \ | |
_(0x2a, ROL, accumulator, CZN, 1, 2, 0) \ | |
_(0x26, ROL, zeropage, CZN, 2, 5, 0) \ | |
_(0x36, ROL, zeropageX, CZN, 2, 6, 0) \ | |
_(0x2e, ROL, absolute, CZN, 3, 6, 0) \ | |
_(0x3e, ROL, absoluteX, CZN, 3, 7, 0) \ | |
/* ROR -- Rotate Right */ \ | |
_(0x6a, ROR, accumulator, CZN, 1, 2, 0) \ | |
_(0x66, ROR, zeropage, CZN, 2, 5, 0) \ | |
_(0x76, ROR, zeropageX, CZN, 2, 6, 0) \ | |
_(0x6e, ROR, absolute, CZN, 3, 6, 0) \ | |
_(0x7e, ROR, absoluteX, CZN, 3, 7, 0) \ | |
/* RTI -- Return from Interrupt */ \ | |
_(0x40, RTI, implicit, none, 1, 6, 0) \ | |
/* RTS -- Return from Subroutine */ \ | |
_(0x60, RTS, implicit, none, 1, 6, 0) \ | |
/* SBC -- Subtract with Carry */ \ | |
_(0xe9, SBC, immediate, CZN, 2, 2, 0) \ | |
_(0xe5, SBC, zeropage, CZN, 2, 3, 0) \ | |
_(0xf5, SBC, zeropageX, CZN, 2, 4, 0) \ | |
_(0xed, SBC, absolute, CZN, 3, 4, 0) \ | |
_(0xfd, SBC, absoluteX, CZN, 3, 4, 1) \ | |
_(0xf9, SBC, absoluteY, CZN, 3, 4, 1) \ | |
_(0xe1, SBC, indirectX, CZN, 2, 6, 0) \ | |
_(0xf1, SBC, indirectY, CZN, 2, 5, 1) \ | |
/* SEC -- Set Carry Flag */ \ | |
_(0x38, SEC, implicit, C, 1, 2, 0) \ | |
/* SED -- Set Decimal Flag */ \ | |
_(0xf8, SED, implicit, D, 1, 2, 0) \ | |
/* SEI -- Set Interrupt Disable */ \ | |
_(0x78, SEI, implicit, I, 1, 2, 0) \ | |
/* STA -- Store Accumulator */ \ | |
_(0x85, STA, zeropage, none, 2, 3, 0) \ | |
_(0x95, STA, zeropageX, none, 2, 4, 0) \ | |
_(0x8d, STA, absolute, none, 3, 4, 0) \ | |
_(0x9d, STA, absoluteX, none, 3, 5, 0) \ | |
_(0x99, STA, absoluteY, none, 3, 5, 0) \ | |
_(0x81, STA, indirectX, none, 2, 6, 0) \ | |
_(0x91, STA, indirectY, none, 2, 6, 0) \ | |
/* STX -- Store X Register */ \ | |
_(0x86, STX, zeropage, none, 2, 3, 0) \ | |
_(0x96, STX, zeropageY, none, 2, 4, 0) \ | |
_(0x8e, STX, absolute, none, 3, 4, 0) \ | |
/* STY -- Store Y Register */ \ | |
_(0x84, STY, zeropage, none, 2, 3, 0) \ | |
_(0x94, STY, zeropageX, none, 2, 4, 0) \ | |
_(0x8c, STY, absolute, none, 3, 4, 0) \ | |
/* TAX -- Transfer Accumulator to X */ \ | |
_(0xaa, TAX, implicit, ZN, 1, 2, 0) \ | |
/* TAY -- Transfer Accumulator to Y */ \ | |
_(0xa8, TAY, implicit, ZN, 1, 2, 0) \ | |
/* TSX -- Transfer SP to X */ \ | |
_(0xba, TSX, implicit, ZN, 1, 2, 0) \ | |
/* TXA -- Transfer X to Accumulator */ \ | |
_(0x8a, TXA, implicit, ZN, 1, 2, 0) \ | |
/* TXS -- Transfer X to SP */ \ | |
_(0x9a, TXS, implicit, none, 1, 2, 0) \ | |
/* TYA -- Transfer Y to Accumulator */ \ | |
_(0x98, TYA, implicit, ZN, 1, 2, 0) | |
/* Extra metadata */ | |
// A list of opcodes that are branches, so we can additionally | |
// add an extra cycle when the branch succeeds. Also used | |
// for extra disassembler metadata. | |
const uint8_t branchops[] = { | |
0x90, /* BCS */ | |
0xb0, /* BCC */ | |
0xf0, /* BEQ */ | |
0x30, /* BMI */ | |
0xd0, /* BNE */ | |
0x10, /* BPL */ | |
0x50, /* BVC */ | |
0x70 /* BVS */ | |
}; | |
static bool | |
op_is_branch(uint8_t op) | |
{ | |
for (int i = 0; i < sizeof(branchops); i++) | |
if (op == branchops[i]) | |
return true; | |
return false; | |
} | |
// Disassembler: Disassemble the MOS-6502 instruction sequence, | |
// specified by code, of length len. | |
void | |
disasm_init(disasmctx_t* ctx, uint8_t* code, uint32_t len) | |
{ | |
ctx->code = code; | |
ctx->len = len; | |
ctx->off = 0; | |
} | |
void | |
disasm_one(disasmctx_t* ctx, cpuinsn_t* insn) | |
{ | |
uint8_t* ptr; | |
if (ctx->off > ctx->len) { /* Err: bad offset */ | |
insn->opname = NULL; | |
return; | |
} | |
ptr = ctx->code + ctx->off; | |
switch (*ptr) { | |
#define DISASMBYTE(opc, ops, amode, mflag, bytes, cyc, ecyc) \ | |
case opc: { \ | |
insn->opcode = opc; \ | |
insn->opname = #ops; \ | |
insn->addrmode = addrmodety::amode; \ | |
insn->modflag = instrmodflag::mod##mflag; \ | |
insn->len = bytes; \ | |
insn->cycles = cyc; \ | |
insn->ecycles = ecyc; \ | |
insn->bcycles = 0; \ | |
if (op_is_branch(opc)) \ | |
insn->bcycles = 1; \ | |
ctx->off += bytes; \ | |
} | |
MOS6502INSTR(DISASMBYTE) /* Generate code */ | |
#undef DISASMBYTE | |
default: | |
insn->opname = NULL; | |
} | |
return; | |
} | |
}; // namespace mos6502 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace mos6502 | |
{ | |
// The register file for a 6502, with three special purpose registers | |
// and three general purpose registers. | |
typedef struct { | |
uint8_t pc; // SPECIAL: Program Counter | |
uint8_t sp; // SPECIAL: Stack Pointer | |
uint8_t p; // SPECIAL: Processor Status | |
uint8_t a; // GENERAL: Accumulator | |
uint8_t x; // GENERAL: Counter, and get stack pointer | |
uint8_t y; // GENERAL: Counter only | |
} regs_t; | |
// The type of interrupts the CPU can receive. Upon every tick of the | |
// CPU, we check the interrupt flag and see if one of these events has | |
// occurred. | |
enum class irqty { | |
none, // None, move on. | |
reset, // Reset interrupt, caused by hitting the 'Reset' button | |
irq, // Maskable interrupt; ignored if PSTATUS_I is set | |
nmi // Non-maskable interrupt | |
}; | |
// The type of address modes that any particular instruction may | |
// support. | |
enum class addrmodety { | |
implicit, | |
accumulator, | |
immediate, | |
zeropage, | |
zeropageX, | |
zeropageY, | |
relative, | |
absolute, | |
absoluteX, | |
absoluteY, | |
indirect, | |
indirectX, | |
indirectY | |
}; | |
// The set of flags in the processor status register (P) that a | |
// particular instruction may modify. | |
enum class instrmodflag { | |
modnone, | |
modZN, | |
modCZN, | |
modC, | |
modZVN, | |
modCZIDBVN, | |
modI, | |
modCZVN, | |
modV, | |
modD, | |
modB | |
}; | |
// A MOS-6502 CPU. | |
typedef struct { | |
uint64_t ncycles; // Total number of cycles | |
regs_t regs; // Register file | |
irqty interrupt; // Current interrupt setting per-tick | |
} cpu_t; | |
typedef struct { | |
uint8_t* code; | |
uint32_t len; | |
uint32_t off; | |
} disasmctx_t; | |
typedef struct { | |
uint8_t opcode; | |
const char* opname; | |
addrmodety addrmode; | |
instrmodflag modflag; | |
uint8_t len; | |
uint8_t cycles; | |
uint8_t ecycles; | |
uint8_t bcycles; | |
} cpuinsn_t; | |
// Disassembler API. | |
void disasm_init(disasmctx_t* ctx, uint8_t* code, uint32_t len); | |
void disasm_one(disasmctx_t* ctx, cpuinsn_t* insn); | |
}; /* namespace mos6502 */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment