package audio.gme; // Nintendo NES 6502 CPU emulator // http://www.slack.net/~ant/ /* Copyright (C) 2007 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ public class NesCpu extends ClassicEmu { // Resets registers and uses supplied physical memory public final void reset( byte [] mem, int unmapped ) { this.mem = mem; a = 0; x = 0; y = 0; s = 0xFF; pc = 0; p = 0x04; c = 0; nz = 1; time = 0; for ( int i = 0; i < pageCount + 1; i++ ) mapPage( i, unmapped ); } static final int pageShift = 11; static final int pageCount = 0x10000 >> pageShift; public static final int pageSize = 1 << pageShift; // Maps address range to offset in physical memory public final void mapMemory( int addr, int size, int offset ) { assert addr % pageSize == 0; assert size % pageSize == 0; int firstPage = addr / pageSize; for ( int i = size / pageSize; i-- > 0; ) mapPage( firstPage + i, offset + i * pageSize ); } // Maps address to memory public final int mapAddr( int addr ) { return pages [addr >> pageShift] + addr; } // Emulation // Registers. NOT kept updated during runCpu() public int a, x, y, p, s, pc; // Current time public int time; // Memory read and write handlers protected int cpuRead ( int addr ) { return 0; } protected void cpuWrite( int addr, int data ) { } final int pages [] = new int [pageCount + 1]; int c, nz; byte [] mem; final void mapPage( int page, int offset ) { if ( debug ) assert 0 <= page && page < pageCount + 1; pages [page] = offset - page * pageSize; } static final int [] instrTimes = {// 0 1 2 3 4 5 6 7 8 9 A B C D E F 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E 2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 // F }; // 0xF2 was 2 static final int [] illop_lens = { 0x95, 0x95, 0x95, 0xD5, 0x95, 0x95, 0xD5, 0xF5 }; static final int N80 = 0x80; static final int V40 = 0x40; static final int R20 = 0x20; static final int B10 = 0x10; static final int D08 = 0x08; static final int I04 = 0x04; static final int Z02 = 0x02; static final int C01 = 0x01; // Runs until time >= 0 public final void runCpu() { // locals are faster, and first three are more efficient to access final byte [] mem = this.mem; int nz = this.nz; int pc = this.pc; int time = this.time; int a = this.a; int x = this.x; int y = this.y; int sp = (this.s + 1) | 0x100; int p = this.p; int c = this.c; final int pages [] = this.pages; final int instrTimes [] = this.instrTimes; int addr = 0; loop: while ( time < 0 ) { if ( debug ) { assert 0 <= a && a < 0x100; assert 0 <= x && x < 0x100; assert 0 <= y && y < 0x100; assert (p & ~(V40 | D08 | I04)) == 0; assert 0 <= pc && pc < 0x10000; assert 0x100 <= sp && sp < 0x200; } int instr; int opcode; this.time = (time += instrTimes [opcode = 0xFF & mem [instr = pages [pc >> pageShift] + pc]]); instr++; // nz is used as variable for incoming data of instructions that // will be modifying nz anyway. // Source switch ( opcode ) { //////// Often used case 0xD0:{// BNE r pc += 2; if ( ((byte) nz) != 0 ) { int old = pc; time += (((pc += mem [instr]) ^ old) >> 8 & 1) + 1; } continue; } case 0xF0:{// BEQ r pc += 2; if ( ((byte) nz) == 0 ) { int old = pc; time += (((pc += mem [instr]) ^ old) >> 8 & 1) + 1; } continue; } case 0xBD:{// LDA a,X pc += 3; int lsb; time += (lsb = (mem [instr] & 0xFF) + x) >> 8; a = nz = cpuRead( ((mem [instr + 1] & 0xFF) << 8) + lsb ); continue; } case 0xC8: // INY pc++; y = (nz = y + 1) & 0xFF; continue; case 0x85: // STA z pc += 2; mem [mem [instr] & 0xFF] = (byte) a; continue; case 0xC9: // CMP #n pc += 2; c = ~(nz = a - (mem [instr] & 0xFF)); nz = (byte) nz; continue; case 0x20:{// JSR a int t = pc + 2; pc = (mem [instr + 1] & 0xFF) << 8 | (mem [instr] & 0xFF); mem [(sp - 1) | 0x100] = (byte) (t >> 8); mem [sp = (sp - 2) | 0x100] = (byte) t; continue; } //////// case 0x10: // BPL r pc += 2; if ( (nz & 0x8080) == 0 ) break; continue; case 0x30: // BMI r pc += 2; if ( (nz & 0x8080) != 0 ) break; continue; case 0x50: // BVC r pc += 2; if ( (p & V40) == 0 ) break; continue; case 0x70: // BVS r pc += 2; if ( (p & V40) != 0 ) break; continue; case 0x90: // BCC r pc += 2; if ( (c & 0x100) == 0 ) break; continue; case 0xB0: // BCS r pc += 2; if ( (c & 0x100) != 0 ) break; continue; case 0xBA: // TSX pc++; x = (nz = sp - 1) & 0xFF; continue; case 0x9A: // TXS pc++; sp = (x + 1) | 0x100; continue; case 0x18: // CLC pc++; c = 0; continue; case 0x38: // SEC pc++; c = ~0; continue; case 0xD8: // CLD pc++; p &= ~D08; continue; case 0xB8: // CLV pc++; p &= ~V40; continue; case 0x58: // CLI pc++; p &= ~I04; continue; case 0x78: // SEI pc++; p |= I04; continue; case 0xF8: // SED pc++; p |= D08; continue; case 0x48: // PHA pc++; mem [sp = (sp - 1) | 0x100] = (byte) a; continue; // SKW - Skip word case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: time += ((mem [instr] & 0xFF) + x) >> 8; case 0x0C: pc += 3; continue; // SKB - Skip byte case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: pc += 2; continue; // NOP case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: pc++; continue; // Illegal case 0xF2: if ( pc > 0xFFFF ) { // handle wrap-around (assumes caller has put 0xF2 at 0x1000-0x10FF) pc &= 0xFFFF; break; } case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: break loop; // Unimplemented default: /* assert false; case 0x03: case 0x07: case 0x0B: case 0x0F: case 0x13: case 0x17: case 0x1B: case 0x1F: case 0x23: case 0x27: case 0x2B: case 0x2F: case 0x33: case 0x37: case 0x3B: case 0x3F: case 0x43: case 0x47: case 0x4B: case 0x4F: case 0x53: case 0x57: case 0x5B: case 0x5F: case 0x63: case 0x67: case 0x6B: case 0x6F: case 0x73: case 0x77: case 0x7B: case 0x7F: case 0x83: case 0x87: case 0x8B: case 0x8F: case 0x93: case 0x97: case 0x9B: case 0x9F: case 0xA3: case 0xA7: case 0xAB: case 0xAF: case 0xB3: case 0xB7: case 0xBB: case 0xBF: case 0xC3: case 0xC7: case 0xCB: case 0xCF: case 0xD3: case 0xD7: case 0xDB: case 0xDF: case 0xE3: case 0xE7: case 0xEF: case 0xF3: case 0xF7: case 0xFB: case 0xFF: case 0x9C: case 0x9E: */ if ( (opcode >> 4) == 0x0B ) { int t = mem [instr] & 0xFF; if ( opcode == 0xB3 ) t = mem [t] & 0xFF; if ( opcode != 0xB7 ) time += (t + y) >> 8; } // skip over proper number of bytes int len = illop_lens [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; if ( opcode == 0x9C ) len = 3; pc += len; continue; case 0x0A: // ASL case 0x2A: // ROL case 0x4A: // LSR case 0x6A: // ROR pc++; nz = a; break; case 0x09: // ORA #n case 0x29: // AND #n case 0x49: // EOR #n case 0x69: // ADC #n case 0xA0: // LDY #n case 0xA2: // LDX #n case 0xA9: // LDA #n case 0xC0: // CPY #n case 0xE0: // CPX #n case 0xE9: // SBC #n case 0xEB: // SBC #n (unofficial) pc += 2; nz = mem [instr] & 0xFF; break; case 0x84: // STY z case 0x86: // STX z pc += 2; addr = mem [instr] & 0xFF; break; case 0x06: // ASL z case 0x26: // ROL z case 0x66: // ROR z case 0xE6: // INC z case 0xC6: // DEC z case 0x46: // LSR z pc += 2; nz = mem [addr = mem [instr] & 0xFF] & 0xFF; break; case 0x05: // ORA z case 0x24: // BIT z case 0x25: // AND z case 0x45: // EOR z case 0x65: // ADC z case 0xA4: // LDY z case 0xA5: // LDA z case 0xA6: // LDX z case 0xC4: // CPY z case 0xC5: // CMP z case 0xE4: // CPX z case 0xE5: // SBC z pc += 2; nz = mem [mem [instr] & 0xFF] & 0xFF; break; case 0x11: // ORA (z),Y case 0x31: // AND (z),Y case 0x51: // EOR (z),Y case 0x71: // ADC (z),Y case 0xB1: // LDA (z),Y case 0xD1: // CMP (z),Y case 0xF1: // SBC (z),Y case 0x91:{// STA (z),Y pc += 2; int z = mem [instr]; int lsb = (mem [z & 0xFF] & 0xFF) + y; addr = ((mem [(z + 1) & 0xFF] & 0xFF) << 8) + lsb; if ( opcode != 0x91 ) { time += lsb >> 8; nz = cpuRead( addr ); } break; } case 0x01: // ORA (z,X) case 0x21: // AND (z,X) case 0x41: // EOR (z,X) case 0x61: // ADC (z,X) case 0xA1: // LDA (z,X) case 0xC1: // CMP (z,X) case 0xE1: // SBC (z,X) case 0x81:{// STA (z,X) pc += 2; int z = mem [instr] + x; addr = (mem [(z + 1) & 0xFF] & 0xFF) << 8 | (mem [z & 0xFF] & 0xFF); if ( opcode != 0x81 ) nz = cpuRead( addr ); break; } case 0x6C: // JMP (a) case 0x8C: // STY a case 0x8D: // STA a case 0x8E: // STX a case 0x4C: // JMP a pc += 3; addr = (mem [instr + 1] & 0xFF) << 8 | (mem [instr] & 0xFF); break; case 0x0D: // ORA a case 0x0E: // ASL a case 0x2C: // BIT a case 0x2D: // AND a case 0x2E: // ROL a case 0x4D: // EOR a case 0x4E: // LSR a case 0x6D: // ADC a case 0x6E: // ROR a case 0xAC: // LDY a case 0xAD: // LDA a case 0xAE: // LDX a case 0xCC: // CPY a case 0xCD: // CMP a case 0xCE: // DEC a case 0xEC: // CPX a case 0xED: // SBC a case 0xEE: // INC a pc += 3; nz = cpuRead( addr = (mem [instr + 1] & 0xFF) << 8 | (mem [instr] & 0xFF) ); break; case 0x1E: // ASL a,X case 0x3E: // ROL a,X case 0x5E: // LSR a,X case 0x7E: // ROR a,X case 0xDE: // DEC a,X case 0xFE: // INC a,X pc += 3; nz = cpuRead( addr = ((mem [instr + 1] & 0xFF) << 8 | (mem [instr] & 0xFF)) + x ); // RMW instructions have no extra clock for page crossing break; case 0x1D: // ORA a,X case 0x3D: // AND a,X case 0x5D: // EOR a,X case 0x7D: // ADC a,X case 0xBC: // LDY a,X case 0xDD: // CMP a,X case 0xFD: // SBC a,X case 0x9D:{// STA a,X pc += 3; int lsb = (mem [instr] & 0xFF) + x; addr = ((mem [instr + 1] & 0xFF) << 8) + lsb; if ( opcode != 0x9D ) { time += lsb >> 8; nz = cpuRead( addr ); } break; } case 0x19: // ORA a,Y case 0x39: // AND a,Y case 0x59: // EOR a,Y case 0x79: // ADC a,Y case 0xB9: // LDA a,Y case 0xBE: // LDX a,Y case 0xD9: // CMP a,Y case 0xF9: // SBC a,Y case 0x99:{// STA a,Y pc += 3; int lsb = (mem [instr] & 0xFF) + y; addr = ((mem [instr + 1] & 0xFF) << 8) + lsb; if ( opcode != 0x99 ) { time += lsb >> 8; nz = cpuRead( addr ); } break; } case 0x15: // ORA z,X case 0x16: // ASL z,X case 0x35: // AND z,X case 0x36: // ROL z,X case 0x55: // EOR z,X case 0x56: // LSR z,X case 0x75: // ADC z,X case 0x76: // ROR z,X case 0xB4: // LDY z,X case 0xB5: // LDA z,X case 0xD5: // CMP z,X case 0xD6: // DEC z,X case 0xF5: // SBC z,X case 0xF6: // INC z,X pc += 2; nz = mem [addr = (mem [instr] + x) & 0xFF] & 0xFF; break; case 0x94: // STY z,X case 0x95: // STA z,X pc += 2; addr = (mem [instr] + x) & 0xFF; break; case 0xB6: // LDX z,Y case 0x96: // STX z,Y pc += 2; addr = (mem [instr] + y) & 0xFF; if ( opcode != 0x96 ) nz = mem [addr] & 0xFF; break; case 0xAA: // TAX case 0xA8: // TAY pc++; nz = a; break; case 0xCA: // DEX case 0xE8: // INX case 0x8A: // TXA pc++; nz = x; break; case 0x88: // DEY case 0x98: // TYA pc++; nz = y; break; case 0x28: // PLP case 0x68: // PLA pc++; nz = mem [sp] & 0xFF; sp = (sp - 0xFF) | 0x100; break; case 0x40: // RTI nz = mem [sp] & 0xFF; sp = (sp - 0xFF) | 0x100; case 0x60: // RTS pc = ((mem [(sp - 0xFF) | 0x100] & 0xFF) << 8 | (mem [sp] & 0xFF)) + 1; sp = (sp - 0xFE) | 0x100; break; case 0x00: // BRK #n case 0x08: // PHP break; } // Operation switch ( opcode ) { case 0x60: // RTS continue; case 0x91: // STA (z),Y case 0x81: // STA (z,X) case 0x8D: // STA a case 0x9D: // STA a,X case 0x99: // STA a,Y if ( addr > 0x7FF ) { cpuWrite( addr, a ); continue; } case 0x95: // STA z,X mem [addr] = (byte) a; continue; case 0x8E: // STX a if ( addr > 0x7FF ) { cpuWrite( addr, x ); continue; } case 0x86: // STX z case 0x96: // STX z,Y mem [addr] = (byte) x; continue; case 0x8C: // STY a if ( addr > 0x7FF ) { cpuWrite( addr, y ); continue; } case 0x84: // STY z case 0x94: // STY z,X mem [addr] = (byte) y; continue; case 0x10: // BPL r case 0x30: // BMI r case 0x50: // BVC r case 0x70: // BVS r case 0x90: // BCC r case 0xB0:{// BCS r int old = pc; time += (((pc += mem [instr]) ^ old) >> 8 & 1) + 1; continue; } case 0xEC: // CPX a case 0xE4: // CPX z case 0xE0: // CPX #n c = ~(nz = x - nz); nz = (byte) nz; continue; case 0xCC: // CPY a case 0xC4: // CPY z case 0xC0: // CPY #n c = ~(nz = y - nz); nz = (byte) nz; continue; case 0xD1: // CMP (z),Y case 0xC1: // CMP (z,X) case 0xCD: // CMP a case 0xDD: // CMP a,X case 0xD9: // CMP a,Y case 0xC5: // CMP z case 0xD5: // CMP z,X c = ~(nz = a - nz); nz = (byte) nz; continue; case 0xF1: // SBC (z),Y case 0xE1: // SBC (z,X) case 0xED: // SBC a case 0xFD: // SBC a,X case 0xF9: // SBC a,Y case 0xE5: // SBC z case 0xF5: // SBC z,X case 0xE9: // SBC #n case 0xEB: // SBC #n (unofficial) nz ^= 0xFF; case 0x71: // ADC (z),Y case 0x61: // ADC (z,X) case 0x6D: // ADC a case 0x7D: // ADC a,X case 0x79: // ADC a,Y case 0x65: // ADC z case 0x75: // ADC z,X case 0x69:{// ADC #n int t = nz ^ a; c = (nz += (c >> 8 & 1) + a); a = nz & 0xFF; p = (p & ~V40) | (((t ^ nz) + 0x80) >> 2 & V40); continue; } case 0x31: // AND (z),Y case 0x21: // AND (z,X) case 0x2D: // AND a case 0x3D: // AND a,X case 0x39: // AND a,Y case 0x25: // AND z case 0x35: // AND z,X case 0x29: // AND #n a = (nz &= a); continue; case 0x11: // ORA (z),Y case 0x01: // ORA (z,X) case 0x0D: // ORA a case 0x1D: // ORA a,X case 0x19: // ORA a,Y case 0x05: // ORA z case 0x15: // ORA z,X case 0x09: // ORA #n a = (nz |= a); continue; case 0x51: // EOR (z),Y case 0x41: // EOR (z,X) case 0x4D: // EOR a case 0x5D: // EOR a,X case 0x59: // EOR a,Y case 0x45: // EOR z case 0x55: // EOR z,X case 0x49: // EOR #n a = (nz ^= a); continue; case 0x2C: // BIT a case 0x24: // BIT z p = (p & ~V40) | (nz & V40); if ( (a & nz) == 0 ) nz <<= 8; // result must be zero, even if N bit is set continue; case 0x6C: // JMP (a) pc = cpuRead( addr + 1 - (((addr & 0xFF) + 1) & 0x100) ) << 8 | cpuRead( addr ); continue; case 0x4C: // JMP a pc = addr; continue; case 0x40: // RTI pc--; case 0x28: // PLP p = nz & (V40 | D08 | I04); nz = (c = nz << 8) | (~nz & Z02); continue; case 0x00:{// BRK #n int t = pc + 2; pc = cpuRead( 0xFFFF ) << 8 | cpuRead( 0xFFFE ); mem [(sp - 1) | 0x100] = (byte) (t >> 8); mem [sp = (sp - 2) | 0x100] = (byte) t; break; } case 0x4E: // LSR a case 0x5E: // LSR a,X case 0x46: // LSR z case 0x56: // LSR z,X case 0x4A: // LSR c = nz << 8; nz >>= 1; break; case 0x0E: // ASL a case 0x1E: // ASL a,X case 0x06: // ASL z case 0x16: // ASL z,X case 0x0A: // ASL nz = (c = nz << 1) & 0xFF; break; case 0x6E: // ROR a case 0x7E: // ROR a,X case 0x66: // ROR z case 0x76: // ROR z,X case 0x6A:{// ROR int t = c & 0x100; c = nz << 8; nz = (nz | t) >> 1; break; } case 0x2E: // ROL a case 0x3E: // ROL a,X case 0x26: // ROL z case 0x36: // ROL z,X case 0x2A:{// ROL int t = c >> 8 & 1; nz = ((c = nz << 1) & 0xFF) | t; break; } case 0xCA: // DEX case 0x88: // DEY case 0xCE: // DEC a case 0xDE: // DEC a,X case 0xC6: // DEC z case 0xD6: // DEC z,X nz = (nz - 1) & 0xFF; break; case 0xE8: // INX case 0xEE: // INC a case 0xFE: // INC a,X case 0xE6: // INC z case 0xF6: // INC z,X nz = (nz + 1) & 0xFF; break; } // Destination switch ( opcode ) { case 0x2A: // ROL case 0x0A: // ASL case 0x6A: // ROR case 0x4A: // LSR case 0x8A: // TXA case 0x98: // TYA case 0xB1: // LDA (z),Y case 0xA1: // LDA (z,X) case 0xAD: // LDA a case 0xB9: // LDA a,Y case 0xA5: // LDA z case 0xB5: // LDA z,X case 0xA9: // LDA #n case 0x68: // PLA a = nz; continue; case 0xCA: // DEX case 0xE8: // INX case 0xAA: // TAX case 0xAE: // LDX a case 0xBE: // LDX a,Y case 0xA6: // LDX z case 0xB6: // LDX z,Y case 0xA2: // LDX #n x = nz; continue; case 0x88: // DEY case 0xA8: // TAY case 0xAC: // LDY a case 0xBC: // LDY a,X case 0xA4: // LDY z case 0xB4: // LDY z,X case 0xA0: // LDY #n y = nz; continue; case 0x08: // PHP pc++; case 0x00:{// BRK #n int t = p | R20 | B10 | (c >> 8 & C01) | (((nz >> 8) | nz) & N80); if ( ((byte) nz) == 0 ) t |= Z02; mem [sp = (sp - 1) | 0x100] = (byte) t; continue; } default: /* assert false; case 0x2E: // ROL a case 0x3E: // ROL a,X case 0x26: // ROL z case 0x36: // ROL z,X case 0x0E: // ASL a case 0x1E: // ASL a,X case 0x06: // ASL z case 0x16: // ASL z,X case 0xEE: // INC a case 0xFE: // INC a,X case 0xE6: // INC z case 0xF6: // INC z,X case 0xCE: // DEC a case 0xDE: // DEC a,X case 0xC6: // DEC z case 0xD6: // DEC z,X case 0x6E: // ROR a case 0x7E: // ROR a,X case 0x66: // ROR z case 0x76: // ROR z,X case 0x4E: // LSR a case 0x5E: // LSR a,X case 0x46: // LSR z case 0x56: // LSR z,X */ if ( addr <= 0x7FF ) { mem [addr] = (byte) nz; continue; } cpuWrite( addr, nz ); continue; } } stop: this.a = a; this.x = x; this.y = y; this.s = (sp - 1) & 0xFF; this.pc = pc; this.p = p; this.c = c; this.nz = nz; this.time = time; } }