#include #include #include #include #include #include "rom.h" #define MAX(a, b) ((a > b) ? a : b) #define STATUS_UPDATE_ZERO(r) \ (regs.status.zero = r == 0) #define STATUS_UPDATE_NEGATIVE(r) \ (regs.status.negative = ((r & (1 << 7)) != 0)) #define STATUS_UPDATE_NZ(r) \ {STATUS_UPDATE_ZERO(r); \ STATUS_UPDATE_NEGATIVE(r);} #define STATUS_TO_INT() \ (regs.status.carry | (regs.status.zero << 1) \ | (regs.status.interrupt_disable << 2) | (regs.status.decimal_mode << 3) \ | (regs.status.brk << 4) | (regs.status.unused << 5) \ | (regs.status.overflow << 6) | (regs.status.negative << 7)) #define MEMORY_MIRROR(addr) \ if (addr < 0x2000) \ addr &= 0x07FF; \ else if (addr < 0x4000) \ addr &= 0x2007; #define PUSH(b) \ (memwrite(0x0100 + regs.sp--, b)) #define PULL() \ (peek(0x0100 + ++regs.sp)) struct cpu_flags { uint8_t carry : 1; uint8_t zero : 1; uint8_t interrupt_disable : 1; uint8_t decimal_mode : 1; uint8_t brk : 1; uint8_t unused : 1; uint8_t overflow : 1; uint8_t negative : 1; }; struct registers { uint8_t a, x, y, sp; struct cpu_flags status; uint16_t pc; }; struct registers regs = {0}; struct Rom rom = {0}; enum addressing_mode { AM_ACC, AM_IMPLICIT, AM_IMM, AM_ZP, AM_ZP_X, AM_ZP_Y, AM_REL, AM_ABS, AM_ABS_X, AM_ABS_Y, AM_IND, AM_IND_X, AM_IND_Y, }; /* 64K address space, 16bit words */ uint8_t memory[0x16000]; uint32_t cycles = 0; static uint8_t peek(uint16_t addr) { MEMORY_MIRROR(addr); if (addr > 0x7FFF) { if (rom.prg_rom_size == 0x4000) return rom.prg_rom[(addr - 0x8000) % 0x4000]; else if (rom.prg_rom_size == 0x8000) return rom.prg_rom[addr - 0x8000]; } else { return memory[addr]; } } static uint16_t peek16(uint16_t addr) { /* bytes are stored in little-endian (low then high) */ return peek(addr) | (peek(addr + 1) << 8); } static void memwrite(uint16_t addr, uint8_t byte) { MEMORY_MIRROR(addr); memory[addr] = byte; } static void memwrite16(uint16_t addr, uint16_t word) { MEMORY_MIRROR(addr); /* bytes are stored in little-endian (low then high) */ memory[addr] = word & 0xFF; memory[addr + 1] = (word & 0xFF00) >> 8; } static uint8_t opcode_arg(enum addressing_mode mode) { uint16_t arg, val; if (mode != AM_ABS && mode != AM_ABS_X && mode != AM_ABS_Y) { arg = peek(regs.pc++); printf(" %02X\t", arg); } else { arg = peek16(regs.pc); printf(" %02X %02X\t", peek(regs.pc), peek(regs.pc + 1)); regs.pc += 2; } switch (mode) { case AM_IMM: case AM_REL: val = arg; break; case AM_ZP: val = peek(arg % 256); break; case AM_ZP_X: val = peek((arg + regs.x) % 256); break; case AM_ZP_Y: val = peek((arg + regs.y) % 256); break; case AM_ABS: val = peek16(arg); break; case AM_ABS_X: val = peek16(arg + regs.x); break; case AM_ABS_Y: val = peek16(arg + regs.y); break; case AM_IND_X: val = peek(peek((arg + regs.x) % 256) + peek((arg + regs.x + 1) % 256) * 256); break; case AM_IND_Y: val = peek(peek(arg) + peek((arg + 1) % 256) * 256 + regs.y); break; default: fprintf(stderr, "INVALID ADDRESSING MODE %i\n", mode); abort(); } return val; } static uint16_t opcode_mem(enum addressing_mode mode) { uint16_t arg, val; if (mode != AM_ABS && mode != AM_ABS_X && mode != AM_ABS_Y && mode != AM_IND && mode) { arg = peek(regs.pc++); printf(" %02X\t", arg); } else { arg = peek16(regs.pc); printf(" %02X %02X\t", peek(regs.pc), peek(regs.pc + 1)); regs.pc += 2; } switch (mode) { case AM_ZP: val = arg % 256; break; case AM_ZP_X: val = (arg + regs.x) % 256; break; case AM_ZP_Y: val = (arg + regs.y) % 256; break; case AM_IMM: case AM_ABS: val = arg; break; case AM_ABS_X: val = arg + regs.x; break; case AM_ABS_Y: val = arg + regs.y; break; case AM_IND: val = peek(arg) | (peek(((arg + 1) & 0xFF) | (arg & 0xFF00)) << 8); break; case AM_IND_X: val = peek((arg + regs.x) % 256) + peek((arg + regs.x + 1) % 256) * 256; break; case AM_IND_Y: val = peek(arg) + peek((arg + 1) % 256) * 256 + regs.y; break; default: fprintf(stderr, "INVALID ADDRESSING MODE\n"); abort(); } return val; } static void adc(uint8_t arg) { uint16_t sum; // 16-bit sum makes it easier to determine carry flag sum = regs.a + arg + regs.status.carry; regs.status.carry = sum > 0xFF; /* overflow flag formula: https://stackoverflow.com/a/29224684 */ regs.status.overflow = (~(regs.a ^ arg) & (regs.a ^ sum) & 0x80) != 0; regs.a = sum & 0xFF; STATUS_UPDATE_NZ(regs.a); } static void and(uint8_t arg) { regs.a &= arg; STATUS_UPDATE_NZ(regs.a); } static void asl_acc(void) { uint16_t tmp; tmp = regs.a << 1; regs.a = tmp & 0xFF; regs.status.carry = tmp > 0xFF; STATUS_UPDATE_NZ(regs.a); } static void asl(uint16_t mem) { uint16_t tmp; tmp = peek(mem) << 1; memwrite(mem, tmp & 0xFF); regs.status.carry = tmp > 0xFF; STATUS_UPDATE_NZ(tmp); } static void bcc(uint8_t arg) { if (regs.status.carry == 0) regs.pc += arg; } static void bcs(uint8_t arg) { if (regs.status.carry == 1) regs.pc += arg; } static void beq(uint8_t arg) { if (regs.status.zero == 1) regs.pc += arg; } static uint8_t bit(uint8_t arg) { regs.status.zero = (regs.a & arg) == 0; regs.status.overflow = (arg & (1 << 6)) != 0; STATUS_UPDATE_NEGATIVE(arg); return regs.a & arg; } static void bmi(uint8_t arg) { if (regs.status.negative == 1) regs.pc += arg; } static void bne(uint8_t arg) { if (regs.status.zero == 0) regs.pc += arg; } static void bpl(uint8_t arg) { if (regs.status.negative == 0) regs.pc += arg; } static void brk(void) { /* TODO: push regs.pc and regs.status to stack and load IRQ vector */ regs.status.brk = 1; // exit(0); } static void bvc(uint8_t arg) { //regs.status.overflow = (STATUS_TO_INT() & (1 << 6)) == 0; //if (regs.status.overflow == 0) if ((STATUS_TO_INT() & (1 << 6)) == 0) regs.pc += arg; } static void bvs(uint8_t arg) { regs.status.overflow = (STATUS_TO_INT() & (1 << 6)) != 0; if (regs.status.overflow == 1) regs.pc += arg; } static void clc(void) { regs.status.carry = 0; } static void cld(void) { regs.status.decimal_mode = 0; } static void cli(void) { regs.status.interrupt_disable = 0; } static void clv(void) { regs.status.overflow = 0; } static void cmp(uint8_t arg) { uint8_t tmp; tmp = regs.a - arg; regs.status.carry = regs.a >= arg; regs.status.zero = regs.a == arg; STATUS_UPDATE_NEGATIVE(tmp); } static void cpx(uint8_t arg) { uint8_t tmp; tmp = regs.x - arg; regs.status.carry = regs.x >= arg; regs.status.zero = regs.x == arg; STATUS_UPDATE_NEGATIVE(tmp); } static void cpy(uint8_t arg) { uint8_t tmp; tmp = regs.y - arg; regs.status.carry = regs.y >= arg; regs.status.zero = regs.y == arg; STATUS_UPDATE_NEGATIVE(tmp); } static void dec(uint16_t mem) { memwrite(mem, peek(mem) - 1); STATUS_UPDATE_NZ(peek(mem)); } static void dex(void) { regs.x--; STATUS_UPDATE_NZ(regs.x); } static void dey(void) { regs.y--; STATUS_UPDATE_NZ(regs.y); } static void eor(uint8_t arg) { regs.a ^= arg; STATUS_UPDATE_NZ(regs.a); } static void inc(uint16_t mem) { memwrite(mem, peek(mem) + 1); STATUS_UPDATE_NZ(peek(mem)); } static void inx(void) { regs.x++; STATUS_UPDATE_NZ(regs.x); } static void iny(void) { regs.y++; STATUS_UPDATE_NZ(regs.y); } static void jmp(uint16_t arg) { regs.pc = arg; } static void jsr(uint16_t arg) { uint16_t tmp = regs.pc - 1; /* * first push high-byte of return address then low-byte * https://www.masswerk.at/6502/6502_instruction_set.html */ PUSH((tmp & 0xFF00) >> 8); PUSH(tmp & 0xFF); regs.pc = arg; } static void lda(uint8_t arg) { regs.a = arg; STATUS_UPDATE_NZ(regs.a); } static void ldx(uint8_t arg) { regs.x = arg; STATUS_UPDATE_NZ(regs.x); } static void ldy(uint8_t arg) { regs.y = arg; STATUS_UPDATE_NZ(regs.y); } static void lsr_acc(void) { regs.status.carry = regs.a & 1; // bit 0 in carry regs.a >>= 1; regs.a &= ~(1 << 7); // bit 7 cleared STATUS_UPDATE_NZ(regs.a); } static void lsr(uint16_t mem) { uint8_t tmp; tmp = peek(mem); regs.status.carry = tmp & 1; // bit 0 in carry tmp >>= 1; tmp &= ~(1 << 7); // bit 7 cleared memwrite(mem, tmp); STATUS_UPDATE_NZ(tmp); } static void nop(void) { return; } static void ora(uint8_t arg) { regs.a |= arg; STATUS_UPDATE_NZ(regs.a); } static void pha(void) { PUSH(regs.a); } static void php(void) { PUSH(STATUS_TO_INT() | (1 << 4)); } static void pla(void) { regs.a = PULL(); STATUS_UPDATE_NZ(regs.a); } static void plp(void) { uint8_t status; status = PULL(); regs.status.carry = (status & 1) != 0; regs.status.zero = (status & (1 << 1)) != 0; regs.status.interrupt_disable = (status & (1 << 2)) != 0; regs.status.decimal_mode = (status & (1 << 3)) != 0; regs.status.brk = 0; regs.status.unused = 1; regs.status.overflow = (status & (1 << 6)) != 0; regs.status.negative = (status & (1 << 7)) != 0; } static void rol_acc(void) { uint8_t carry; carry = (regs.a & (1 << 7)) != 0; regs.a <<= 1; regs.a |= regs.status.carry; regs.status.carry = carry; STATUS_UPDATE_NZ(regs.a); } static void rol(uint16_t mem) { uint8_t carry, tmp; carry = (peek(mem) & (1 << 7)) != 0; tmp = (peek(mem) << 1) | regs.status.carry; memwrite(mem, tmp); regs.status.carry = carry; STATUS_UPDATE_NZ(regs.a); } static void ror_acc(void) { uint8_t carry; carry = regs.a & 1; regs.a >>= 1; regs.a |= regs.status.carry << 7; regs.status.carry = carry; STATUS_UPDATE_NZ(regs.a); } static void ror(uint16_t mem) { uint8_t carry, tmp; carry = peek(mem) & 1; tmp = (peek(mem) >> 1) | (regs.status.carry << 7); memwrite(mem, tmp); regs.status.carry = carry; STATUS_UPDATE_NZ(tmp); } static void rti(void) { plp(); regs.pc = PULL() | (PULL() << 8); } static void rts(void) { regs.pc = (PULL() | (PULL() << 8)) + 1; } static void sbc(uint8_t arg) { /* SBC is described online as ADC with argument as two's complement */ adc(~arg); } static void sec(void) { regs.status.carry = 1; } static void sed(void) { regs.status.decimal_mode = 1; } static void sei(void) { regs.status.interrupt_disable = 1; } static uint8_t sta(uint16_t mem) { uint8_t tmp = peek(mem); memwrite(mem, regs.a); return tmp; } static uint8_t stx(uint16_t mem) { uint8_t tmp = peek(mem); memwrite(mem, regs.x); return tmp; } static uint8_t sty(uint16_t mem) { uint8_t tmp = peek(mem); memwrite(mem, regs.y); return tmp; } static void tax(void) { regs.x = regs.a; STATUS_UPDATE_NZ(regs.x); } static void tay(void) { regs.y = regs.a; STATUS_UPDATE_NZ(regs.y); } static void tsx(void) { // regs.x = PULL(); regs.x = regs.sp; STATUS_UPDATE_NZ(regs.x); } static void txa(void) { regs.a = regs.x; STATUS_UPDATE_NZ(regs.a); } static void txs(void) { // PUSH(regs.x); regs.sp = regs.x; } static void tya(void) { regs.a = regs.y; STATUS_UPDATE_NZ(regs.a); } static void interpret(void) { uint8_t opcode, did_memwrite, ret, status; uint16_t arg, mem, deref; uint32_t cycles_; enum addressing_mode mode; struct registers regs_; for (;;) { opcode = peek(regs.pc++); cycles_ = cycles; did_memwrite = 0; status = STATUS_TO_INT(); regs_ = regs; printf("%04X %02X", regs.pc - 1, opcode); switch (opcode) { case 0x69: mode = AM_IMM; arg = opcode_arg(mode); adc(arg); cycles += 2; printf("ADC"); break; case 0x65: mode = AM_ZP; arg = opcode_arg(mode); adc(arg); cycles += 3; printf("ADC"); break; case 0x75: mode = AM_ZP_X; arg = opcode_arg(mode); adc(arg); cycles += 4; printf("ADC"); break; case 0x6d: mode = AM_ABS; arg = opcode_arg(mode); adc(arg); cycles += 4; printf("ADC"); break; case 0x7d: mode = AM_ABS_X; arg = opcode_arg(mode); adc(arg); cycles += 4; printf("ADC"); break; case 0x79: mode = AM_ABS_Y; arg = opcode_arg(mode); adc(arg); cycles += 4; printf("ADC"); break; case 0x61: mode = AM_IND_X; arg = opcode_arg(mode); adc(arg); cycles += 6; printf("ADC"); break; case 0x71: mode = AM_IND_Y; arg = opcode_arg(mode); adc(arg); cycles += 5; printf("ADC"); break; case 0x29: mode = AM_IMM; arg = opcode_arg(mode); and(arg); cycles += 2; printf("AND"); break; case 0x25: mode = AM_ZP; arg = opcode_arg(mode); and(arg); cycles += 3; printf("AND"); break; case 0x35: mode = AM_ZP_X; arg = opcode_arg(mode); and(arg); cycles += 4; printf("AND"); break; case 0x2d: mode = AM_ABS; arg = opcode_arg(mode); and(arg); cycles += 4; printf("AND"); break; case 0x3d: mode = AM_ABS_X; arg = opcode_arg(mode); and(arg); cycles += 4; printf("AND"); break; case 0x39: mode = AM_ABS_Y; arg = opcode_arg(mode); and(arg); cycles += 4; printf("AND"); break; case 0x21: mode = AM_IND_X; arg = opcode_arg(mode); and(arg); cycles += 6; printf("AND"); break; case 0x31: mode = AM_IND_Y; arg = opcode_arg(mode); and(arg); cycles += 5; printf("AND"); break; case 0x0a: mode = AM_ACC; asl_acc(); cycles += 2; printf("\tASL"); break; case 0x06: mode = AM_ZP; arg = opcode_mem(mode); asl(arg); cycles += 5; printf("ASL"); break; case 0x16: mode = AM_ZP_X; arg = opcode_mem(mode); asl(arg); cycles += 6; printf("ASL"); break; case 0x0e: mode = AM_ABS; arg = opcode_mem(mode); asl(arg); cycles += 6; printf("ASL"); break; case 0x1e: mode = AM_ABS_X; arg = opcode_mem(mode); asl(arg); cycles += 6; printf("ASL"); break; case 0x90: mode = AM_REL; arg = opcode_arg(mode); bcc(arg); arg += regs_.pc + 1; cycles += 2; printf("BCC"); break; case 0xb0: mode = AM_REL; arg = opcode_arg(mode); bcs(arg); arg += regs_.pc + 1; cycles += 2; printf("BCS"); break; case 0xf0: mode = AM_REL; arg = opcode_arg(mode); beq(arg); arg += regs_.pc + 1; cycles += 2; printf("BEQ"); break; case 0x89: mode = AM_IMM; arg = opcode_arg(mode); did_memwrite = 1; ret = bit(arg); cycles += 2; printf("BIT"); break; case 0x24: mode = AM_ZP; arg = opcode_arg(mode); did_memwrite = 1; ret = bit(arg); cycles += 3; printf("BIT"); break; case 0x34: mode = AM_ZP_X; arg = opcode_arg(mode); did_memwrite = 1; ret = bit(arg); cycles += 4; printf("BIT"); break; case 0x2c: mode = AM_ABS; arg = opcode_arg(mode); did_memwrite = 1; ret = bit(arg); cycles += 4; printf("BIT"); break; case 0x3c: mode = AM_ABS_X; arg = opcode_arg(mode); did_memwrite = 1; ret = bit(arg); cycles += 4; printf("BIT"); break; case 0x30: mode = AM_REL; arg = opcode_arg(mode); bmi(arg); arg += regs_.pc + 1; cycles += 2; printf("BMI"); break; case 0xd0: mode = AM_REL; arg = opcode_arg(mode); bne(arg); arg += regs_.pc + 1; cycles += 2; printf("BNE"); break; case 0x10: mode = AM_REL; arg = opcode_arg(mode); bpl(arg); arg += regs_.pc + 1; cycles += 2; printf("BPL"); break; case 0x00: mode = AM_IMPLICIT; brk(); cycles += 7; printf("\tBRK"); goto loop_exit; case 0x50: mode = AM_REL; arg = opcode_arg(mode); bvc(arg); arg += regs_.pc + 1; cycles += 2; printf("BVC"); break; case 0x70: mode = AM_REL; arg = opcode_arg(mode); bvs(arg); arg += regs_.pc + 1; cycles += 2; printf("BVS"); break; case 0x18: mode = AM_IMPLICIT; clc(); cycles += 2; printf("\tCLC"); break; case 0xd8: mode = AM_IMPLICIT; cld(); cycles += 2; printf("\tCLD"); break; case 0x58: mode = AM_IMPLICIT; cli(); cycles += 2; printf("\tCLI"); break; case 0xb8: mode = AM_IMPLICIT; clv(); cycles += 2; printf("\tCLV"); break; case 0xc9: mode = AM_IMM; arg = opcode_arg(mode); cmp(arg); cycles += 2; printf("CMP"); break; case 0xc5: mode = AM_ZP; arg = opcode_arg(mode); cmp(arg); cycles += 3; printf("CMP"); break; case 0xd5: mode = AM_ZP_X; arg = opcode_arg(mode); cmp(arg); cycles += 4; printf("CMP"); break; case 0xcd: mode = AM_ABS; arg = opcode_arg(mode); cmp(arg); cycles += 4; printf("CMP"); break; case 0xdd: mode = AM_ABS_X; arg = opcode_arg(mode); cmp(arg); cycles += 4; printf("CMP"); break; case 0xd9: mode = AM_ABS_Y; arg = opcode_arg(mode); cmp(arg); cycles += 4; printf("CMP"); break; case 0xc1: mode = AM_IND_X; arg = opcode_arg(mode); cmp(arg); cycles += 6; printf("CMP"); break; case 0xd1: mode = AM_IND_Y; arg = opcode_arg(mode); cmp(arg); cycles += 5; printf("CMP"); break; case 0xe0: mode = AM_IMM; arg = opcode_arg(mode); cpx(arg); cycles += 2; printf("CPX"); break; case 0xe4: mode = AM_ZP; arg = opcode_arg(mode); cpx(arg); cycles += 3; printf("CPX"); break; case 0xec: mode = AM_ABS; arg = opcode_arg(mode); cpx(arg); cycles += 4; printf("CPX"); break; case 0xc0: mode = AM_IMM; arg = opcode_arg(mode); cpy(arg); cycles += 2; printf("CPY"); break; case 0xc4: mode = AM_ZP; arg = opcode_arg(mode); cpy(arg); cycles += 3; printf("CPY"); break; case 0xcc: mode = AM_ABS; arg = opcode_arg(mode); cpy(arg); cycles += 4; printf("CPY"); break; case 0xc6: mode = AM_ZP; arg = opcode_mem(mode); dec(arg); cycles += 5; printf("DEC"); break; case 0xd6: mode = AM_ZP_X; arg = opcode_mem(mode); dec(arg); cycles += 6; printf("DEC"); break; case 0xce: mode = AM_ABS; arg = opcode_mem(mode); dec(arg); cycles += 6; printf("DEC"); break; case 0xde: mode = AM_ABS_X; arg = opcode_mem(mode); dec(arg); cycles += 7; printf("DEC"); break; case 0xca: mode = AM_IMPLICIT; dex(); cycles += 2; printf("\tDEX"); break; case 0x88: mode = AM_IMPLICIT; dey(); cycles += 2; printf("\tDEY"); break; case 0x49: mode = AM_IMM; arg = opcode_arg(mode); eor(arg); cycles += 2; printf("EOR"); break; case 0x45: mode = AM_ZP; arg = opcode_arg(mode); eor(arg); cycles += 3; printf("EOR"); break; case 0x55: mode = AM_ZP_X; arg = opcode_arg(mode); eor(arg); cycles += 4; printf("EOR"); break; case 0x4d: mode = AM_ABS; arg = opcode_arg(mode); eor(arg); cycles += 4; printf("EOR"); break; case 0x5d: mode = AM_ABS_X; arg = opcode_arg(mode); eor(arg); cycles += 4; printf("EOR"); break; case 0x59: mode = AM_ABS_Y; arg = opcode_arg(mode); eor(arg); cycles += 4; printf("EOR"); break; case 0x41: mode = AM_IND_X; arg = opcode_arg(mode); eor(arg); cycles += 6; printf("EOR"); break; case 0x51: mode = AM_IND_Y; arg = opcode_arg(mode); eor(arg); cycles += 5; printf("EOR"); break; case 0xe6: mode = AM_ZP; arg = opcode_arg(mode); inc(arg); cycles += 5; printf("INC"); break; case 0xf6: mode = AM_ZP_X; arg = opcode_arg(mode); inc(arg); cycles += 6; printf("INC"); break; case 0xee: mode = AM_ABS; arg = opcode_mem(mode); inc(arg); cycles += 6; printf("INC"); break; case 0xfe: mode = AM_ABS_X; arg = opcode_arg(mode); inc(arg); cycles += 7; printf("INC"); break; case 0xe8: mode = AM_IMPLICIT; inx(); cycles += 2; printf("\tINX"); break; case 0xc8: mode = AM_IMPLICIT; iny(); cycles += 2; printf("\tINY"); break; case 0x4c: mode = AM_ABS; arg = opcode_mem(mode); jmp(arg); cycles += 3; printf("JMP"); break; case 0x6c: mode = AM_IND; mem = peek16(regs.pc); deref = peek16(mem); arg = opcode_mem(mode); deref = arg; jmp(arg); cycles += 6; printf("JMP"); break; case 0x7c: mode = AM_ABS_X; arg = opcode_arg(mode); jmp(arg); cycles += 6; printf("JMP"); break; case 0x20: mode = AM_ABS; arg = opcode_mem(mode); jsr(arg); cycles += 6; printf("JSR"); break; case 0xa9: mode = AM_IMM; arg = opcode_arg(mode); lda(arg); cycles += 2; printf("LDA"); break; case 0xa5: mode = AM_ZP; arg = opcode_arg(mode); lda(arg); cycles += 3; printf("LDA"); break; case 0xb5: mode = AM_ZP_X; arg = opcode_arg(mode); lda(arg); cycles += 4; printf("LDA"); break; case 0xad: mode = AM_ABS; arg = opcode_arg(mode); lda(arg); cycles += 4; printf("LDA"); break; case 0xbd: mode = AM_ABS_X; arg = opcode_arg(mode); lda(arg); cycles += 4; printf("LDA"); break; case 0xb9: mode = AM_ABS_Y; arg = opcode_arg(mode); lda(arg); cycles += 4; printf("LDA"); break; case 0xa1: mode = AM_IND_X; arg = opcode_arg(mode); lda(arg); cycles += 6; printf("LDA"); break; case 0xb1: mode = AM_IND_Y; arg = opcode_arg(mode); lda(arg); cycles += 5; printf("LDA"); break; case 0xa2: mode = AM_IMM; arg = opcode_mem(mode); ldx(arg); cycles += 2; printf("LDX"); break; case 0xa6: mode = AM_ZP; arg = opcode_arg(mode); ldx(arg); cycles += 3; printf("LDX"); break; case 0xb6: mode = AM_ZP_Y; arg = opcode_arg(mode); ldx(arg); cycles += 4; printf("LDX"); break; case 0xae: mode = AM_ABS; arg = opcode_arg(mode); ldx(arg); cycles += 4; printf("LDX"); break; case 0xbe: mode = AM_ABS_Y; arg = opcode_arg(mode); ldx(arg); cycles += 4; printf("LDX"); break; case 0xa0: mode = AM_IMM; arg = opcode_arg(mode); ldy(arg); cycles += 2; printf("LDY"); break; case 0xa4: mode = AM_ZP; arg = opcode_arg(mode); ldy(arg); cycles += 3; printf("LDY"); break; case 0xb4: mode = AM_ZP_X; arg = opcode_arg(mode); ldy(arg); cycles += 4; printf("LDY"); break; case 0xac: mode = AM_ABS; arg = opcode_arg(mode); ldy(arg); cycles += 4; printf("LDY"); break; case 0xbc: mode = AM_ABS_X; arg = opcode_arg(mode); ldy(arg); cycles += 4; printf("LDY"); break; case 0x4a: mode = AM_ACC; lsr_acc(); cycles += 2; printf("\tLSR"); break; case 0x46: mode = AM_ZP; arg = opcode_arg(mode); lsr(arg); cycles += 5; printf("LSR"); break; case 0x56: mode = AM_ZP_X; arg = opcode_arg(mode); lsr(arg); cycles += 6; printf("LSR"); break; case 0x4e: mode = AM_ABS; arg = opcode_mem(mode); lsr(arg); cycles += 6; printf("LSR"); break; case 0x5e: mode = AM_ABS_X; arg = opcode_mem(mode); lsr(arg); cycles += 6; printf("LSR"); break; case 0xea: mode = AM_IMPLICIT; nop(); cycles += 2; printf("\tNOP"); break; case 0x09: mode = AM_IMM; arg = opcode_arg(mode); ora(arg); cycles += 2; printf("ORA"); break; case 0x05: mode = AM_ZP; arg = opcode_arg(mode); ora(arg); cycles += 3; printf("ORA"); break; case 0x15: mode = AM_ZP_X; arg = opcode_arg(mode); ora(arg); cycles += 4; printf("ORA"); break; case 0x0d: mode = AM_ABS; arg = opcode_arg(mode); ora(arg); cycles += 4; printf("ORA"); break; case 0x1d: mode = AM_ABS_X; arg = opcode_arg(mode); ora(arg); cycles += 4; printf("ORA"); break; case 0x19: mode = AM_ABS_Y; arg = opcode_arg(mode); ora(arg); cycles += 4; printf("ORA"); break; case 0x01: mode = AM_IND_X; arg = opcode_arg(mode); ora(arg); cycles += 6; printf("ORA"); break; case 0x11: mode = AM_IND_Y; arg = opcode_arg(mode); ora(arg); cycles += 5; printf("ORA"); break; case 0x48: mode = AM_IMPLICIT; pha(); cycles += 3; printf("\tPHA"); break; case 0x08: mode = AM_IMPLICIT; php(); cycles += 3; printf("\tPHP"); break; case 0x68: mode = AM_IMPLICIT; pla(); cycles += 4; printf("\tPLA"); break; case 0x28: mode = AM_IMPLICIT; plp(); cycles += 4; printf("\tPLP"); break; case 0x2a: mode = AM_ACC; rol_acc(); cycles += 2; printf("\tROL"); break; case 0x26: mode = AM_ZP; arg = opcode_arg(mode); rol(arg); cycles += 5; printf("ROL"); break; case 0x36: mode = AM_ZP_X; arg = opcode_arg(mode); rol(arg); cycles += 6; printf("ROL"); break; case 0x2e: mode = AM_ABS; arg = opcode_arg(mode); rol(arg); cycles += 6; printf("ROL"); break; case 0x3e: mode = AM_ABS_X; arg = opcode_arg(mode); rol(arg); cycles += 6; printf("ROL"); break; case 0x6a: mode = AM_ACC; ror_acc(); cycles += 2; printf("\tROR"); break; case 0x66: mode = AM_ZP; arg = opcode_arg(mode); ror(arg); cycles += 5; printf("ROR"); break; case 0x76: mode = AM_ZP_X; arg = opcode_arg(mode); ror(arg); cycles += 6; printf("ROR"); break; case 0x6e: mode = AM_ABS; arg = opcode_arg(mode); ror(arg); cycles += 6; printf("ROR"); break; case 0x7e: mode = AM_ABS_X; arg = opcode_arg(mode); ror(arg); cycles += 6; printf("ROR"); break; case 0x40: mode = AM_IMPLICIT; rti(); cycles += 6; printf("\tRTI"); break; case 0x60: mode = AM_IMPLICIT; rts(); cycles += 6; printf("\tRTS"); break; case 0xe9: mode = AM_IMM; arg = opcode_arg(mode); sbc(arg); cycles += 2; printf("SBC"); break; case 0xe5: mode = AM_ZP; arg = opcode_arg(mode); sbc(arg); cycles += 3; printf("SBC"); break; case 0xf5: mode = AM_ZP_X; arg = opcode_arg(mode); sbc(arg); cycles += 4; printf("SBC"); break; case 0xed: mode = AM_ABS; arg = opcode_arg(mode); sbc(arg); cycles += 4; printf("SBC"); break; case 0xfd: mode = AM_ABS_X; arg = opcode_arg(mode); sbc(arg); cycles += 4; printf("SBC"); break; case 0xf9: mode = AM_ABS_Y; arg = opcode_arg(mode); sbc(arg); cycles += 4; printf("SBC"); break; case 0xe1: mode = AM_IND_X; arg = opcode_arg(mode); sbc(arg); cycles += 6; printf("SBC"); break; case 0xf1: mode = AM_IND_Y; arg = opcode_arg(mode); sbc(arg); cycles += 5; printf("SBC"); break; case 0x38: mode = AM_IMPLICIT; sec(); cycles += 2; printf("\tSEC"); break; case 0xf8: mode = AM_IMPLICIT; sed(); cycles += 2; printf("\tSED"); break; case 0x78: mode = AM_IMPLICIT; sei(); cycles += 2; printf("\tSEI"); break; case 0x85: mode = AM_ZP; arg = opcode_mem(mode); did_memwrite = 1; ret = sta(arg); cycles += 4; printf("STA"); break; case 0x95: mode = AM_ZP_X; arg = opcode_mem(mode); did_memwrite = 1; ret = sta(arg); cycles += 5; printf("STA"); break; case 0x8d: mode = AM_ABS; arg = opcode_mem(mode); did_memwrite = 1; ret = sta(arg); cycles += 5; printf("STA"); break; case 0x9d: mode = AM_ABS_X; arg = opcode_mem(mode); did_memwrite = 1; ret = sta(arg); cycles += 6; printf("STA"); break; case 0x99: mode = AM_ABS_Y; arg = opcode_mem(mode); did_memwrite = 1; ret = sta(arg); cycles += 6; printf("STA"); break; case 0x81: mode = AM_IND_X; arg = opcode_mem(mode); did_memwrite = 1; ret = sta(arg); cycles += 7; printf("STA"); break; case 0x91: mode = AM_IND_Y; arg = opcode_mem(mode); did_memwrite = 1; ret = sta(arg); cycles += 7; printf("STA"); break; case 0x86: mode = AM_ZP; arg = opcode_mem(mode); did_memwrite = 1; ret = stx(arg); cycles += 4; printf("STX"); break; case 0x96: mode = AM_ZP_Y; arg = opcode_mem(mode); did_memwrite = 1; ret = stx(arg); cycles += 5; printf("STX"); break; case 0x8e: mode = AM_ABS; arg = opcode_mem(mode); did_memwrite = 1; ret = stx(arg); cycles += 5; printf("STX"); break; case 0x84: mode = AM_ZP; arg = opcode_mem(mode); did_memwrite = 1; ret = sty(arg); cycles += 4; printf("STY"); break; case 0x94: mode = AM_ZP_X; arg = opcode_mem(mode); did_memwrite = 1; ret = sty(arg); cycles += 5; printf("STY"); break; case 0x8c: mode = AM_ABS; arg = opcode_mem(mode); did_memwrite = 1; ret = sty(arg); cycles += 5; printf("STY"); break; case 0xaa: mode = AM_IMPLICIT; tax(); cycles += 2; printf("\tTAX"); break; case 0xa8: mode = AM_IMPLICIT; tay(); cycles += 2; printf("\tTAY"); break; case 0xba: mode = AM_IMPLICIT; tsx(); cycles += 2; printf("\tTSX"); break; case 0x8a: mode = AM_IMPLICIT; txa(); cycles += 2; printf("\tTXA"); break; case 0x9a: mode = AM_IMPLICIT; txs(); cycles += 2; printf("\tTXS"); break; case 0x98: mode = AM_IMPLICIT; tya(); cycles += 2; printf("\tTYA"); break; default: printf("opcode $%02X not implemented\n", opcode); break; } switch (mode) { case AM_IMM: printf(" #$%02X", arg); if (did_memwrite) printf(" = %02X\t\t\t", ret); else printf("\t\t\t"); break; case AM_ZP: printf(" $%02X", arg); if (did_memwrite) printf(" = %02X\t\t\t", ret); else printf("\t\t\t\t"); break; case AM_ZP_X: printf(" $%02X,X", arg); if (did_memwrite) printf(" = %02X\t\t\t", ret); else printf("\t\t\t"); break; case AM_ZP_Y: printf(" $%02X,Y", arg); if (did_memwrite) printf(" = %02X\t\t\t", ret); else printf("\t\t\t\t"); break; case AM_REL: case AM_ABS: printf(" $%04X", arg); if (did_memwrite) printf(" = %02X\t\t\t", ret); else printf("\t\t\t"); break; case AM_ABS_X: printf(" $%04X,X", arg); if (did_memwrite) printf(" = %02X\t\t", ret); else printf("\t\t\t"); break; case AM_ABS_Y: printf(" $%04X,Y", arg); if (did_memwrite) printf(" = %02X\t\t\t", ret); else printf("\t\t\t"); break; case AM_IND: printf(" ($%04X) = %04X\t\t", mem, deref); break; case AM_ACC: case AM_IMPLICIT: default: printf("\t\t\t\t"); break; } printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%d\n", regs_.a, regs_.x, regs_.y, status, regs_.sp, cycles_); } loop_exit: } /* https://www.nesdev.org/wiki/CPU_power_up_state */ void cpu_init(void) { regs.a = regs.x = regs.y = 0; regs.pc = 0xFFFC; regs.sp = 0xFD; //memset(®s.status, 0, sizeof(regs.status)); regs.status.interrupt_disable = 1; regs.status.unused = 1; cycles += 7; } int main(int argc, char *argv[]) { FILE *fp; uint8_t *buf; size_t buflen; if (argc != 2) { fprintf(stderr, "Usage: %s rom.nes\n", basename(argv[0])); return 1; } fp = fopen(argv[1], "r"); fseek(fp, 0, SEEK_END); buflen = ftell(fp); buf = calloc(1, buflen); fseek(fp, 0, SEEK_SET); if (fread(buf, 1, buflen, fp) != buflen && ferror(fp)) { fprintf(stderr, "file %s was not read properly\n", argv[1]); clearerr(fp); fclose(fp); return 1; } fclose(fp); parse_rom(buf, buflen, &rom); free(buf); cpu_init(); /* TODO: move to separate file? */ if (rom.mapper != 0) { fprintf(stderr, "Only iNES ROMs using Mapper 0 are supported for now.\n"); return 1; } memwrite16(0xFFFC, 0xC000); regs.pc = 0xC000; interpret(); printf("\n$02 = %02X\n", memory[0x02]); printf("$03 = %02X\n", memory[0x03]); free_rom(&rom); return 0; }