Skip to content

Instantly share code, notes, and snippets.

@thoughtpolice
Created October 14, 2015 18:49
Show Gist options
  • Save thoughtpolice/bb23cdea6248d2eab65c to your computer and use it in GitHub Desktop.
Save thoughtpolice/bb23cdea6248d2eab65c to your computer and use it in GitHub Desktop.
#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
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