#include #include #include #include #include #include #include "cpu.h" #include "opcodes.h" #include "rom.h" #include "ppu.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 rom rom = {0}; uint32_t cycles = 0; bool page_crossed = false; 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 fprintf(stderr, "PRG ROG size is not 0x4000 nor 0x8000\n"), exit(1); } 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 uint16_t opcode_mem(enum addressing_mode mode) { uint16_t arg, val; if (mode == AM_ACC || mode == AM_NONE) { return 0; } else if (mode != AM_ABS && mode != AM_ABS_X && mode != AM_ABS_Y && mode != AM_IND && mode) { arg = peek(regs.pc++); } else { arg = peek16(regs.pc); 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_REL: val = (int8_t)arg + regs.pc; break; case AM_IMM: case AM_ABS: val = arg; break; case AM_ABS_X: val = arg + regs.x; if ((arg & 0xFF00) != (val & 0xFF00)) page_crossed = true; break; case AM_ABS_Y: val = arg + regs.y; if ((arg & 0xFF00) != (val & 0xFF00)) page_crossed = true; 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; if (((uint16_t)(val - regs.y) & 0xFF00) != (val & 0xFF00)) page_crossed = true; break; default: fprintf(stderr, "INVALID ADDRESSING MODE %i\n", mode); abort(); } return val; } static void tick(void) { cycles++; ppu_tick(); ppu_tick(); ppu_tick(); } static void branch(uint16_t addr, bool cond) { if (!cond) return; tick(); if (((regs.pc + 1) & 0xFF00) != (addr & 0xFF00)) tick(); regs.pc = addr; } /* OFFICIAL OPCODES */ void ADC(uint16_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); } void AND(uint16_t arg) { regs.a &= arg; STATUS_UPDATE_NZ(regs.a); } void ASL_acc(uint16_t arg) { uint16_t tmp; tmp = regs.a << 1; regs.a = tmp & 0xFF; regs.status.carry = tmp > 0xFF; STATUS_UPDATE_NZ(regs.a); } void ASL(uint16_t mem) { uint16_t tmp; tmp = peek(mem) << 1; memwrite(mem, tmp & 0xFF); regs.status.carry = tmp > 0xFF; regs.status.negative = (tmp & (1 << 7)) != 0; regs.status.zero = (tmp << 1 & 0xFF) == 0; } void BCC(uint16_t arg) { branch(arg, regs.status.carry == 0); } void BCS(uint16_t arg) { branch(arg, regs.status.carry == 1); } void BEQ(uint16_t arg) { branch(arg, regs.status.zero == 1); } void BIT(uint16_t arg) { uint8_t tmp = arg; regs.status.zero = (regs.a & tmp) == 0; regs.status.overflow = (tmp & (1 << 6)) != 0; STATUS_UPDATE_NEGATIVE(tmp); } void BMI(uint16_t arg) { branch(arg, regs.status.negative == 1); } void BNE(uint16_t arg) { branch(arg, regs.status.zero == 0); } void BPL(uint16_t arg) { branch(arg, regs.status.negative == 0); } void BRK(uint16_t arg) { /* TODO: push regs.pc and regs.status to stack and load IRQ vector */ regs.status.brk = 1; exit(0); } void BVC(uint16_t arg) { //regs.status.overflow = (STATUS_TO_INT() & (1 << 6)) == 0; //if (regs.status.overflow == 0) branch(arg, (STATUS_TO_INT() & (1 << 6)) == 0); } void BVS(uint16_t arg) { regs.status.overflow = (STATUS_TO_INT() & (1 << 6)) != 0; branch(arg, regs.status.overflow == 1); } void CLC(uint16_t arg) { regs.status.carry = 0; } void CLD(uint16_t arg) { regs.status.decimal_mode = 0; } void CLI(uint16_t arg) { regs.status.interrupt_disable = 0; } void CLV(uint16_t arg) { regs.status.overflow = 0; } void CMP(uint16_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); } void CPX(uint16_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); } void CPY(uint16_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); } void DEC(uint16_t mem) { memwrite(mem, peek(mem) - 1); STATUS_UPDATE_NZ(peek(mem)); } void DEX(uint16_t arg) { regs.x--; STATUS_UPDATE_NZ(regs.x); } void DEY(uint16_t arg) { regs.y--; STATUS_UPDATE_NZ(regs.y); } void EOR(uint16_t arg) { regs.a ^= arg; STATUS_UPDATE_NZ(regs.a); } void INC(uint16_t mem) { memwrite(mem, peek(mem) + 1); STATUS_UPDATE_NZ(peek(mem)); } void INX(uint16_t arg) { regs.x++; STATUS_UPDATE_NZ(regs.x); } void INY(uint16_t arg) { regs.y++; STATUS_UPDATE_NZ(regs.y); } void JMP(uint16_t arg) { regs.pc = arg; } 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; } void LDA(uint16_t arg) { regs.a = arg; STATUS_UPDATE_NZ(regs.a); } void LDX(uint16_t arg) { regs.x = arg; STATUS_UPDATE_NZ(regs.x); } void LDY(uint16_t arg) { regs.y = arg; STATUS_UPDATE_NZ(regs.y); } void LSR_acc(uint16_t arg) { 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); } 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); } void NOP(uint16_t arg) { return; } void ORA(uint16_t arg) { regs.a |= arg; STATUS_UPDATE_NZ(regs.a); } void PHA(uint16_t arg) { PUSH(regs.a); } void PHP(uint16_t arg) { PUSH(STATUS_TO_INT() | (1 << 4)); } void PLA(uint16_t arg) { regs.a = PULL(); STATUS_UPDATE_NZ(regs.a); } void PLP(uint16_t arg) { 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; } void ROL_acc(uint16_t arg) { 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); } 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(tmp); } void ROR_acc(uint16_t arg) { 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); } 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); } void RTI(uint16_t arg) { PLP(0); regs.pc = PULL() | (PULL() << 8); } void RTS(uint16_t arg) { regs.pc = (PULL() | (PULL() << 8)) + 1; } void SBC(uint16_t arg) { uint8_t tmp = arg & 0xFF; uint16_t diff; diff = regs.a - tmp - !regs.status.carry; regs.status.carry = diff > 0xFF; regs.status.carry = !regs.status.carry; /* overflow flag formula: https://stackoverflow.com/a/29224684 */ regs.status.overflow = ((regs.a ^ arg) & (regs.a ^ diff) & 0x80) != 0; regs.a = diff & 0xFF; STATUS_UPDATE_NZ(regs.a); } void SEC(uint16_t arg) { regs.status.carry = 1; } void SED(uint16_t arg) { regs.status.decimal_mode = 1; } void SEI(uint16_t arg) { regs.status.interrupt_disable = 1; } void STA(uint16_t mem) { memwrite(mem, regs.a); } void STX(uint16_t mem) { memwrite(mem, regs.x); } void STY(uint16_t mem) { memwrite(mem, regs.y); } void TAX(uint16_t arg) { regs.x = regs.a; STATUS_UPDATE_NZ(regs.x); } void TAY(uint16_t arg) { regs.y = regs.a; STATUS_UPDATE_NZ(regs.y); } void TSX(uint16_t arg) { // regs.x = PULL(); regs.x = regs.sp; STATUS_UPDATE_NZ(regs.x); } void TXA(uint16_t arg) { regs.a = regs.x; STATUS_UPDATE_NZ(regs.a); } void TXS(uint16_t arg) { // PUSH(regs.x); regs.sp = regs.x; } void TYA(uint16_t arg) { regs.a = regs.y; STATUS_UPDATE_NZ(regs.a); } /* UNOFFICIAL OPCODES */ void AAC(uint16_t arg) { AND(arg); regs.status.carry = regs.status.negative; } void SAX(uint16_t arg) { uint8_t tmp = regs.x & regs.a; memwrite(arg, tmp); } void ARR(uint16_t arg) { uint8_t tmp = arg & regs.a; AND(arg); ROR_acc(0); if ((tmp & (1 << 5)) != 0 && (tmp & (1 << 6)) != 0) SEC(0), CLV(0); else if ((tmp & (1 << 5)) == 0 && (tmp & (1 << 6)) == 0) CLC(0), CLV(0); else if ((tmp & (1 << 5)) != 0) CLC(0), regs.status.overflow = 1; else if ((tmp & (1 << 6)) != 0) SEC(0), regs.status.overflow = 1; } void ASR(uint16_t arg) { AND(arg); LSR_acc(0); } void ATX(uint16_t arg) { AND(arg); TAX(0); } void AXA(uint16_t arg) { uint8_t tmp = regs.x & regs.a & 7; memwrite(arg, tmp); } void AXS(uint16_t arg) { regs.x &= regs.a; regs.x -= arg; regs.status.carry = arg <= regs.x; STATUS_UPDATE_NZ(regs.x); } void DCP(uint16_t arg) { uint8_t tmp = peek(arg) - 1; memwrite(arg, tmp); regs.status.carry = tmp <= regs.a; STATUS_UPDATE_NZ(regs.a - tmp); } void ISB(uint16_t arg) { INC(arg); SBC(peek(arg)); } void KIL(uint16_t arg) { /* TODO: figure out how to stop interpret(), I guess with global bool */ NOP(0); } void LAR(uint16_t arg) { regs.a = regs.x = regs.sp = regs.a & regs.sp; STATUS_UPDATE_NZ(regs.a); } void LAX(uint16_t arg) { LDA(arg); LDX(arg); } void RLA(uint16_t arg) { ROL(arg); AND(peek(arg)); } void RRA(uint16_t arg) { ROR(arg); ADC(peek(arg)); } void SLO(uint16_t arg) { ASL(arg); ORA(peek(arg)); } void SRE(uint16_t arg) { LSR(arg); EOR(peek(arg)); } void SXA(uint16_t arg) { memwrite(arg, regs.x & ((arg & 0xFF00) + 1)); } void SYA(uint16_t arg) { memwrite(arg, regs.x & ((arg & 0xFF00) + 1)); } void XAA(uint16_t arg) { /* TODO: apparently the exact operation is unknown */ NOP(0); } void XAS(uint16_t arg) { regs.sp = regs.a & regs.x; memwrite(arg, regs.sp & ((arg & 0xFF00) + 1)); } static void interpret(void) { uint8_t op, spaces; uint16_t arg; enum addressing_mode mode; for (;;) { printf("%04X ", regs.pc); op = peek(regs.pc++); printf("%02X", op); for (uint8_t i = 0; i < opcodes[op].bytes - 1; i++) printf(" %02X", peek(regs.pc + i)); if (opcodes[op].bytes == 1) printf(" "); else if (opcodes[op].bytes == 2) printf(" "); else if (opcodes[op].bytes == 3) putchar(' '); putchar((opcodes[op].unofficial) ? '*' : ' '); printf("%s ", opcodes[op].name); mode = opcodes[op].mode; arg = opcode_mem(mode); switch (mode) { case AM_IMM: printf("#$%02X", arg); break; case AM_ZP: printf("$%02X", arg); break; case AM_ZP_X: printf("$%02X,X @ %02X", peek(regs.pc - 1), arg); break; case AM_ZP_Y: printf("$%02X,Y @ %02X", peek(regs.pc - 1), arg); break; case AM_REL: case AM_ABS: printf("$%04X", arg); break; case AM_ABS_X: printf("$%04X,X @ %04X", (uint16_t)(arg - regs.x), arg); break; case AM_ABS_Y: printf("$%04X,Y @ %04X", (uint16_t)(arg - regs.y), arg); break; case AM_IND: printf("($%04X) = %04X", peek16(regs.pc - 2), arg); break; case AM_IND_X: printf("($%02X,X) @ %02X = %04X", peek(regs.pc - 1), (peek(regs.pc - 1) + regs.x) & 0xFF, arg); break; case AM_IND_Y: printf("($%02X),Y = %04X @ %04X", peek(regs.pc - 1), (uint16_t)(arg - regs.y), arg); break; case AM_ACC: printf("A "); break; case AM_NONE: printf(" "); break; } spaces = 9; if (opcodes[op].memread || opcodes[op].memwrite) printf(" = %02X", peek(arg)); else spaces += 5; if (mode != AM_IND && mode != AM_ABS_X && mode != AM_ABS_Y) spaces += 7; switch (opcodes[op].mode) { case AM_IND_X: spaces -= 12; break; case AM_IND_Y: spaces -= 14; break; case AM_ZP: spaces += 4; break; case AM_ZP_X: case AM_ZP_Y: spaces -= 3; break; case AM_REL: case AM_ABS: spaces += 2; break; case AM_IMM: case AM_ACC: case AM_NONE: spaces += 3; break; default: break; } while (spaces--) putchar(' '); printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X PPU:%3d,%3d CYC:%d\n", regs.a, regs.x, regs.y, STATUS_TO_INT(), regs.sp, ppu.scanlines, ppu.cycles, cycles); if (opcodes[op].memread) opcodes[op].instr(peek(arg)); else opcodes[op].instr(arg); for (uint8_t i = 0; i < opcodes[op].cycles; i++) tick(); if (page_crossed) { if (opcodes[op].page_cross) tick(); page_crossed = false; } } } /* 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; for (uint8_t i = 0; i < 7 * 3; i++) ppu_tick(); } 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); ppu.rom = &rom; 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; }