UNPKG

nes-js

Version:
1,280 lines (1,092 loc) 60.7 kB
import {Register8bit, Register16bit} from './Register.js'; import {Memory} from './Memory.js'; import {Utility} from './Utility.js'; /** * Ricoh 6502 * Refer to https://wiki.nesdev.com/w/index.php/CPU */ function Cpu() { // registers this.pc = new Register16bit(); this.sp = new Register8bit(); this.a = new Register8bit(); this.x = new Register8bit(); this.y = new Register8bit(); this.p = new CpuStatusRegister(); // CPU inside RAM this.ram = new Memory(64 * 1024); // 64KB // other devices this.ppu = null; // set by setPpu() this.apu = null; // set by setApu() this.pad1 = null; // set by setJoypad1() this.pad2 = null; // set by setJoypad2() // cartridge ROM this.rom = null; // set by setRom() // Executing an instruction takes 1, 2, or more cycles. // .stallCycle represents the number of cycles left to // complete the currently executed instruction. this.stallCycle = 0; } // Interrups Cpu.INTERRUPTS = { NMI: 0, RESET: 1, IRQ: 2, BRK: 3 // not interrupt but instruction }; Cpu.INTERRUPT_HANDLER_ADDRESSES = []; Cpu.INTERRUPT_HANDLER_ADDRESSES[Cpu.INTERRUPTS.NMI] = 0xFFFA; Cpu.INTERRUPT_HANDLER_ADDRESSES[Cpu.INTERRUPTS.RESET] = 0xFFFC; Cpu.INTERRUPT_HANDLER_ADDRESSES[Cpu.INTERRUPTS.IRQ] = 0xFFFE; Cpu.INTERRUPT_HANDLER_ADDRESSES[Cpu.INTERRUPTS.BRK] = 0xFFFE; // Instructions Cpu.INSTRUCTIONS = { INV: {'id': 0, 'name': 'inv'}, // Invalid ADC: {'id': 1, 'name': 'adc'}, AND: {'id': 2, 'name': 'and'}, ASL: {'id': 3, 'name': 'asl'}, BCC: {'id': 4, 'name': 'bcc'}, BCS: {'id': 5, 'name': 'bcs'}, BEQ: {'id': 6, 'name': 'beq'}, BIT: {'id': 7, 'name': 'bit'}, BMI: {'id': 8, 'name': 'bmi'}, BNE: {'id': 9, 'name': 'bne'}, BPL: {'id': 10, 'name': 'bpl'}, BRK: {'id': 11, 'name': 'brk'}, BVC: {'id': 12, 'name': 'bvc'}, BVS: {'id': 13, 'name': 'bvs'}, CLC: {'id': 14, 'name': 'clc'}, CLD: {'id': 15, 'name': 'cld'}, CLI: {'id': 16, 'name': 'cli'}, CLV: {'id': 17, 'name': 'clv'}, CMP: {'id': 18, 'name': 'cmp'}, CPX: {'id': 19, 'name': 'cpx'}, CPY: {'id': 20, 'name': 'cpy'}, DEC: {'id': 21, 'name': 'dec'}, DEX: {'id': 22, 'name': 'dex'}, DEY: {'id': 23, 'name': 'dey'}, EOR: {'id': 24, 'name': 'eor'}, INC: {'id': 25, 'name': 'inc'}, INX: {'id': 26, 'name': 'inx'}, INY: {'id': 27, 'name': 'iny'}, JMP: {'id': 28, 'name': 'jmp'}, JSR: {'id': 29, 'name': 'jsr'}, LDA: {'id': 30, 'name': 'lda'}, LDX: {'id': 31, 'name': 'ldx'}, LDY: {'id': 32, 'name': 'ldy'}, LSR: {'id': 33, 'name': 'lsr'}, NOP: {'id': 34, 'name': 'nop'}, ORA: {'id': 35, 'name': 'ora'}, PHA: {'id': 36, 'name': 'pha'}, PHP: {'id': 37, 'name': 'php'}, PLA: {'id': 38, 'name': 'pla'}, PLP: {'id': 39, 'name': 'plp'}, ROL: {'id': 40, 'name': 'rol'}, ROR: {'id': 41, 'name': 'ror'}, RTI: {'id': 42, 'name': 'rti'}, RTS: {'id': 43, 'name': 'rts'}, SBC: {'id': 44, 'name': 'sbc'}, SEC: {'id': 45, 'name': 'sec'}, SED: {'id': 46, 'name': 'sed'}, SEI: {'id': 47, 'name': 'sei'}, STA: {'id': 48, 'name': 'sta'}, STX: {'id': 49, 'name': 'stx'}, STY: {'id': 50, 'name': 'sty'}, TAX: {'id': 51, 'name': 'tax'}, TAY: {'id': 52, 'name': 'tay'}, TSX: {'id': 53, 'name': 'tsx'}, TXA: {'id': 54, 'name': 'txa'}, TXS: {'id': 55, 'name': 'txs'}, TYA: {'id': 56, 'name': 'tya'} }; // Addressing modes Cpu.ADDRESSINGS = { IMMEDIATE: {'id': 0, 'pc': 2, 'name': 'immediate'}, ABSOLUTE: {'id': 1, 'pc': 3, 'name': 'absolute'}, INDEXED_ABSOLUTE_X: {'id': 2, 'pc': 3, 'name': 'indexed_absolute_x'}, INDEXED_ABSOLUTE_Y: {'id': 3, 'pc': 3, 'name': 'indexed_absolute_y'}, ZERO_PAGE: {'id': 4, 'pc': 2, 'name': 'zero_page'}, INDEXED_ZERO_PAGE_X: {'id': 5, 'pc': 2, 'name': 'indexed_zero_page_x'}, INDEXED_ZERO_PAGE_Y: {'id': 6, 'pc': 2, 'name': 'indexed_zero_page_y'}, IMPLIED: {'id': 7, 'pc': 1, 'name': 'implied'}, ACCUMULATOR: {'id': 8, 'pc': 1, 'name': 'accumulator'}, INDIRECT: {'id': 9, 'pc': 3, 'name': 'indirect'}, INDEXED_INDIRECT_X: {'id': 10, 'pc': 2, 'name': 'indexed_indirect_x'}, INDEXED_INDIRECT_Y: {'id': 11, 'pc': 2, 'name': 'indexed_indirect_y'}, RELATIVE: {'id': 12, 'pc': 2, 'name': 'relative'} }; // Operations (the combinations of interuction and addressing mode) // Decoding in advance because it's much easier than implementing decoder. Cpu.OPS = [ /* 0x00 */ {'instruction': Cpu.INSTRUCTIONS.BRK, 'cycle': 7, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x01 */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0x02 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x03 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x04 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x05 */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x06 */ {'instruction': Cpu.INSTRUCTIONS.ASL, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x07 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x08 */ {'instruction': Cpu.INSTRUCTIONS.PHP, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x09 */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0x0A */ {'instruction': Cpu.INSTRUCTIONS.ASL, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.ACCUMULATOR}, /* 0x0B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x0C */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x0D */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x0E */ {'instruction': Cpu.INSTRUCTIONS.ASL, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x0F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x10 */ {'instruction': Cpu.INSTRUCTIONS.BPL, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0x11 */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0x12 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x13 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x14 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x15 */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x16 */ {'instruction': Cpu.INSTRUCTIONS.ASL, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x17 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x18 */ {'instruction': Cpu.INSTRUCTIONS.CLC, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x19 */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0x1A */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x1B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x1C */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x1D */ {'instruction': Cpu.INSTRUCTIONS.ORA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x1E */ {'instruction': Cpu.INSTRUCTIONS.ASL, 'cycle': 7, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x1F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x20 */ {'instruction': Cpu.INSTRUCTIONS.JSR, 'cycle': 0, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x21 */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0x22 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x23 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x24 */ {'instruction': Cpu.INSTRUCTIONS.BIT, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x25 */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x26 */ {'instruction': Cpu.INSTRUCTIONS.ROL, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x27 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x28 */ {'instruction': Cpu.INSTRUCTIONS.PLP, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x29 */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0x2A */ {'instruction': Cpu.INSTRUCTIONS.ROL, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.ACCUMULATOR}, /* 0x2B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x2C */ {'instruction': Cpu.INSTRUCTIONS.BIT, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x2D */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x2E */ {'instruction': Cpu.INSTRUCTIONS.ROL, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x2F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x30 */ {'instruction': Cpu.INSTRUCTIONS.BMI, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0x31 */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0x32 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x33 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x34 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x35 */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x36 */ {'instruction': Cpu.INSTRUCTIONS.ROL, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x37 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x38 */ {'instruction': Cpu.INSTRUCTIONS.SEC, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x39 */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0x3A */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x3B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x3C */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x3D */ {'instruction': Cpu.INSTRUCTIONS.AND, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x3E */ {'instruction': Cpu.INSTRUCTIONS.ROL, 'cycle': 7, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x3F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x40 */ {'instruction': Cpu.INSTRUCTIONS.RTI, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x41 */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0x42 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x43 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x44 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x45 */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x46 */ {'instruction': Cpu.INSTRUCTIONS.LSR, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x47 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x48 */ {'instruction': Cpu.INSTRUCTIONS.PHA, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x49 */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0x4A */ {'instruction': Cpu.INSTRUCTIONS.LSR, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.ACCUMULATOR}, /* 0x4B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x4C */ {'instruction': Cpu.INSTRUCTIONS.JMP, 'cycle': 0, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x4D */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x4E */ {'instruction': Cpu.INSTRUCTIONS.LSR, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x4F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x50 */ {'instruction': Cpu.INSTRUCTIONS.BVC, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0x51 */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0x52 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x53 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x54 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x55 */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x56 */ {'instruction': Cpu.INSTRUCTIONS.LSR, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x57 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x58 */ {'instruction': Cpu.INSTRUCTIONS.CLI, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x59 */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0x5A */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x5B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x5C */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x5D */ {'instruction': Cpu.INSTRUCTIONS.EOR, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x5E */ {'instruction': Cpu.INSTRUCTIONS.LSR, 'cycle': 7, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x5F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x60 */ {'instruction': Cpu.INSTRUCTIONS.RTS, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x61 */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0x62 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x63 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x64 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x65 */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x66 */ {'instruction': Cpu.INSTRUCTIONS.ROR, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x67 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x68 */ {'instruction': Cpu.INSTRUCTIONS.PLA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x69 */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0x6A */ {'instruction': Cpu.INSTRUCTIONS.ROR, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.ACCUMULATOR}, /* 0x6B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x6C */ {'instruction': Cpu.INSTRUCTIONS.JMP, 'cycle': 0, 'mode': Cpu.ADDRESSINGS.INDIRECT}, /* 0x6D */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x6E */ {'instruction': Cpu.INSTRUCTIONS.ROR, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x6F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x70 */ {'instruction': Cpu.INSTRUCTIONS.BVS, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0x71 */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0x72 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x73 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x74 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x75 */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x76 */ {'instruction': Cpu.INSTRUCTIONS.ROR, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x77 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x78 */ {'instruction': Cpu.INSTRUCTIONS.SEI, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x79 */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0x7A */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x7B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x7C */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x7D */ {'instruction': Cpu.INSTRUCTIONS.ADC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x7E */ {'instruction': Cpu.INSTRUCTIONS.ROR, 'cycle': 7, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x7F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x80 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x81 */ {'instruction': Cpu.INSTRUCTIONS.STA, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0x82 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x83 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x84 */ {'instruction': Cpu.INSTRUCTIONS.STY, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x85 */ {'instruction': Cpu.INSTRUCTIONS.STA, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x86 */ {'instruction': Cpu.INSTRUCTIONS.STX, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0x87 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x88 */ {'instruction': Cpu.INSTRUCTIONS.DEY, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x89 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x8A */ {'instruction': Cpu.INSTRUCTIONS.TXA, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x8B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x8C */ {'instruction': Cpu.INSTRUCTIONS.STY, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x8D */ {'instruction': Cpu.INSTRUCTIONS.STA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x8E */ {'instruction': Cpu.INSTRUCTIONS.STX, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0x8F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x90 */ {'instruction': Cpu.INSTRUCTIONS.BCC, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0x91 */ {'instruction': Cpu.INSTRUCTIONS.STA, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0x92 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x93 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x94 */ {'instruction': Cpu.INSTRUCTIONS.STY, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x95 */ {'instruction': Cpu.INSTRUCTIONS.STA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0x96 */ {'instruction': Cpu.INSTRUCTIONS.STX, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_Y}, /* 0x97 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x98 */ {'instruction': Cpu.INSTRUCTIONS.TYA, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x99 */ {'instruction': Cpu.INSTRUCTIONS.STA, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0x9A */ {'instruction': Cpu.INSTRUCTIONS.TXS, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0x9B */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x9C */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x9D */ {'instruction': Cpu.INSTRUCTIONS.STA, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0x9E */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0x9F */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xA0 */ {'instruction': Cpu.INSTRUCTIONS.LDY, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0xA1 */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0xA2 */ {'instruction': Cpu.INSTRUCTIONS.LDX, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0xA3 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xA4 */ {'instruction': Cpu.INSTRUCTIONS.LDY, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xA5 */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xA6 */ {'instruction': Cpu.INSTRUCTIONS.LDX, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xA7 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xA8 */ {'instruction': Cpu.INSTRUCTIONS.TAY, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xA9 */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0xAA */ {'instruction': Cpu.INSTRUCTIONS.TAX, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xAB */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xAC */ {'instruction': Cpu.INSTRUCTIONS.LDY, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xAD */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xAE */ {'instruction': Cpu.INSTRUCTIONS.LDX, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xAF */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xB0 */ {'instruction': Cpu.INSTRUCTIONS.BCS, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0xB1 */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0xB2 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xB3 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xB4 */ {'instruction': Cpu.INSTRUCTIONS.LDY, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0xB5 */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0xB6 */ {'instruction': Cpu.INSTRUCTIONS.LDX, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_Y}, /* 0xB7 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xB8 */ {'instruction': Cpu.INSTRUCTIONS.CLV, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xB9 */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0xBA */ {'instruction': Cpu.INSTRUCTIONS.TSX, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xBB */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xBC */ {'instruction': Cpu.INSTRUCTIONS.LDY, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0xBD */ {'instruction': Cpu.INSTRUCTIONS.LDA, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0xBE */ {'instruction': Cpu.INSTRUCTIONS.LDX, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0xBF */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xC0 */ {'instruction': Cpu.INSTRUCTIONS.CPY, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0xC1 */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0xC2 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xC3 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xC4 */ {'instruction': Cpu.INSTRUCTIONS.CPY, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xC5 */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xC6 */ {'instruction': Cpu.INSTRUCTIONS.DEC, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xC7 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xC8 */ {'instruction': Cpu.INSTRUCTIONS.INY, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xC9 */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0xCA */ {'instruction': Cpu.INSTRUCTIONS.DEX, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xCB */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xCC */ {'instruction': Cpu.INSTRUCTIONS.CPY, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xCD */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xCE */ {'instruction': Cpu.INSTRUCTIONS.DEC, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xCF */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xD0 */ {'instruction': Cpu.INSTRUCTIONS.BNE, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0xD1 */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0xD2 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xD3 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xD4 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xD5 */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0xD6 */ {'instruction': Cpu.INSTRUCTIONS.DEC, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0xD7 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xD8 */ {'instruction': Cpu.INSTRUCTIONS.CLD, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xD9 */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0xDA */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xDB */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xDC */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xDD */ {'instruction': Cpu.INSTRUCTIONS.CMP, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0xDE */ {'instruction': Cpu.INSTRUCTIONS.DEC, 'cycle': 7, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0xDF */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xE0 */ {'instruction': Cpu.INSTRUCTIONS.CPX, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0xE1 */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_X}, /* 0xE2 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xE3 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xE4 */ {'instruction': Cpu.INSTRUCTIONS.CPX, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xE5 */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 3, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xE6 */ {'instruction': Cpu.INSTRUCTIONS.INC, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.ZERO_PAGE}, /* 0xE7 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xE8 */ {'instruction': Cpu.INSTRUCTIONS.INX, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xE9 */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMMEDIATE}, /* 0xEA */ {'instruction': Cpu.INSTRUCTIONS.NOP, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xEB */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xEC */ {'instruction': Cpu.INSTRUCTIONS.CPX, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xED */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xEE */ {'instruction': Cpu.INSTRUCTIONS.INC, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.ABSOLUTE}, /* 0xEF */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xF0 */ {'instruction': Cpu.INSTRUCTIONS.BEQ, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.RELATIVE}, /* 0xF1 */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 5, 'mode': Cpu.ADDRESSINGS.INDEXED_INDIRECT_Y}, /* 0xF2 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xF3 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xF4 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xF5 */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0xF6 */ {'instruction': Cpu.INSTRUCTIONS.INC, 'cycle': 6, 'mode': Cpu.ADDRESSINGS.INDEXED_ZERO_PAGE_X}, /* 0xF7 */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xF8 */ {'instruction': Cpu.INSTRUCTIONS.SED, 'cycle': 2, 'mode': Cpu.ADDRESSINGS.IMPLIED}, /* 0xF9 */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_Y}, /* 0xFA */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xFB */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xFC */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null}, /* 0xFD */ {'instruction': Cpu.INSTRUCTIONS.SBC, 'cycle': 4, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0xFE */ {'instruction': Cpu.INSTRUCTIONS.INC, 'cycle': 7, 'mode': Cpu.ADDRESSINGS.INDEXED_ABSOLUTE_X}, /* 0xFF */ {'instruction': Cpu.INSTRUCTIONS.INV, 'cycle': 0, 'mode': null} ]; Object.assign(Cpu.prototype, { isCpu: true, // INTERRUPTS: Cpu.INTERRUPTS, INTERRUPT_HANDLER_ADDRESSES: Cpu.INTERRUPT_HANDLER_ADDRESSES, ADDRESSINGS: Cpu.ADDRESSINGS, INSTRUCTIONS: Cpu.INSTRUCTIONS, OPS: Cpu.OPS, // public methods // set methods /** * */ setPpu: function(ppu) { this.ppu = ppu; }, /** * */ setApu: function(apu) { this.apu = apu; }, /** * */ setJoypad1: function(pad1) { this.pad1 = pad1; }, /** * */ setJoypad2: function(pad2) { this.pad2 = pad2; }, /** * */ setRom: function(rom) { this.rom = rom; }, // /** * Refer to https://wiki.nesdev.com/w/index.php/CPU_power_up_state */ bootup: function() { this.p.store(0x34); this.a.clear(); this.x.clear(); this.y.clear(); this.sp.store(0xFD); for(var i = 0; i < 0xF; i++) this.store(0x4000 + i, 0); this.store(0x4015, 0); this.store(0x4017, 0); this.interrupt(this.INTERRUPTS.RESET); }, /** * Refer to https://wiki.nesdev.com/w/index.php/CPU_power_up_state */ reset: function() { this.sp.sub(3); this.p.setI(); this.interrupt(this.INTERRUPTS.RESET); }, /** * */ runCycle: function() { if(this.isStall() !== true) { var opc = this.fetch(); var op = this.decode(opc); this.operate(op, opc); this.stallCycle = op.cycle; } this.stallCycle--; }, /** * */ isStall: function() { return this.stallCycle > 0; }, /** * */ interrupt: function(type) { if(type === this.INTERRUPTS.IRQ && this.p.isI() === true) return; if(type !== this.INTERRUPTS.RESET) { if(type !== this.INTERRUPTS.BRK) this.p.clearB(); this.p.setA(); this.pushStack2Bytes(this.pc.load()); this.pushStack(this.p.load()); this.p.setI(); } this.jumpToInterruptHandler(type); }, // load/store methods /** * */ load: function(address) { address = address & 0xFFFF; // just in case // 0x0000 - 0x07FF: 2KB internal RAM // 0x0800 - 0x1FFF: Mirrors of 0x0000 - 0x07FF (repeats every 0x800 bytes) if(address >= 0 && address < 0x2000) return this.ram.load(address & 0x07FF); // 0x2000 - 0x2007: PPU registers // 0x2008 - 0x3FFF: Mirrors of 0x2000 - 0x2007 (repeats every 8 bytes) if(address >= 0x2000 && address < 0x4000) return this.ppu.loadRegister(address & 0x2007); // 0x4000 - 0x4017: APU, PPU and I/O registers // 0x4018 - 0x401F: APU and I/O functionality that is normally disabled if(address >= 0x4000 && address < 0x4014) return this.apu.loadRegister(address); if(address === 0x4014) return this.ppu.loadRegister(address); if(address === 0x4015) return this.apu.loadRegister(address); if(address === 0x4016) return this.pad1.loadRegister(); if(address >= 0x4017 && address < 0x4020) return this.apu.loadRegister(address); // cartridge space if(address >= 0x4020 && address < 0x6000) return this.ram.load(address); // 0x6000 - 0x7FFF: Battery Backed Save or Work RAM if(address >= 0x6000 && address < 0x8000) return this.ram.load(address); // 0x8000 - 0xFFFF: ROM if(address >= 0x8000 && address < 0x10000) return this.rom.load(address); }, /** * */ store: function(address, value) { address = address & 0xFFFF; // just in case // 0x0000 - 0x07FF: 2KB internal RAM // 0x0800 - 0x1FFF: Mirrors of 0x0000 - 0x07FF (repeats every 0x800 bytes) if(address >= 0 && address < 0x2000) return this.ram.store(address & 0x07FF, value); // 0x2000 - 0x2007: PPU registers // 0x2008 - 0x3FFF: Mirrors of 0x2000 - 0x2007 (repeats every 8 bytes) if(address >= 0x2000 && address < 0x4000) return this.ppu.storeRegister(address & 0x2007, value); // 0x4000 - 0x4017: APU, PPU and I/O registers // 0x4018 - 0x401F: APU and I/O functionality that is normally disabled if(address >= 0x4000 && address < 0x4014) return this.apu.storeRegister(address, value); if(address === 0x4014) return this.ppu.storeRegister(address, value); if(address === 0x4015) return this.apu.storeRegister(address, value); if(address === 0x4016) return this.pad1.storeRegister(value); if(address >= 0x4017 && address < 0x4020) return this.apu.storeRegister(address, value); // cartridge space if(address >= 0x4020 && address < 0x6000) return this.ram.store(address, value); // 0x6000 - 0x7FFF: Battery Backed Save or Work RAM if(address >= 0x6000 && address < 0x8000) return this.ram.store(address, value); // 0x8000 - 0xFFFF: ROM if(address >= 0x8000 && address < 0x10000) return this.rom.store(address, value); }, // private methods // load/store methods /** * */ load2Bytes: function(address) { return this.load(address) | (this.load(address + 1) << 8); }, /** * */ load2BytesFromZeropage: function(address) { return this.ram.load(address & 0xff) | (this.ram.load((address + 1) & 0xff) << 8); }, /** * */ load2BytesInPage: function(address) { var addr1 = address; var addr2 = (address & 0xff00) | ((address + 1) & 0xff); return this.load(addr1) | (this.load(addr2) << 8); }, /** * */ store2Bytes: function(address, value) { this.store(address, value); this.store(address + 1, value >> 8); }, // processing methods /** * */ fetch: function() { var opc = this.load(this.pc.load()); this.pc.increment(); return opc; }, /** * */ decode: function(opc) { return this.OPS[opc]; }, /** * */ jumpToInterruptHandler: function(type) { this.pc.store(this.load2Bytes(this.INTERRUPT_HANDLER_ADDRESSES[type])); }, // /** * */ loadWithAddressingMode: function(op) { if(op.mode.id === this.ADDRESSINGS.ACCUMULATOR.id) return this.a.load(); var address = this.getAddressWithAddressingMode(op); var value = this.load(address); // expects that relative addressing mode is used only for load. if(op.mode.id === this.ADDRESSINGS.RELATIVE.id) { // TODO: confirm if this logic is right. if(value & 0x80) value = value | 0xff00; } return value; }, storeWithAddressingMode: function(op, value) { if(op.mode.id === this.ADDRESSINGS.ACCUMULATOR.id) { this.a.store(value); return; } var address = this.getAddressWithAddressingMode(op); this.store(address, value); }, updateMemoryWithAddressingMode: function(op, func) { var address; var src; if(op.mode.id == this.ADDRESSINGS.ACCUMULATOR.id) { src = this.a.load(); } else { address = this.getAddressWithAddressingMode(op); src = this.load(address); } var result = func(src); if(op.mode.id == this.ADDRESSINGS.ACCUMULATOR.id) { this.a.store(result); } else { this.store(address, result); } }, getAddressWithAddressingMode: function(op) { var address = null; switch(op.mode.id) { case this.ADDRESSINGS.IMMEDIATE.id: case this.ADDRESSINGS.RELATIVE.id: address = this.pc.load(); this.pc.increment(); break; case this.ADDRESSINGS.ABSOLUTE.id: case this.ADDRESSINGS.INDEXED_ABSOLUTE_X.id: case this.ADDRESSINGS.INDEXED_ABSOLUTE_Y.id: address = this.load2Bytes(this.pc.load()); this.pc.incrementBy2(); switch(op.mode.id) { case this.ADDRESSINGS.INDEXED_ABSOLUTE_X.id: address += this.x.load(); break; case this.ADDRESSINGS.INDEXED_ABSOLUTE_Y.id: address += this.y.load(); break; } address = address & 0xffff; break; case this.ADDRESSINGS.ZERO_PAGE.id: case this.ADDRESSINGS.INDEXED_ZERO_PAGE_X.id: case this.ADDRESSINGS.INDEXED_ZERO_PAGE_Y.id: address = this.load(this.pc.load()); this.pc.increment(); switch(op.mode.id) { case this.ADDRESSINGS.INDEXED_ZERO_PAGE_X.id: address += this.x.load(); break; case this.ADDRESSINGS.INDEXED_ZERO_PAGE_Y.id: address += this.y.load(); break; } address = address & 0xff; break; case this.ADDRESSINGS.INDIRECT.id: var tmp = this.load2Bytes(this.pc.load()); this.pc.incrementBy2(); address = this.load2BytesInPage(tmp); break; case this.ADDRESSINGS.INDEXED_INDIRECT_X.id: var tmp = this.load(this.pc.load()); this.pc.increment(); tmp += this.x.load(); tmp = tmp & 0xff; address = this.load2BytesFromZeropage(tmp); break; case this.ADDRESSINGS.INDEXED_INDIRECT_Y.id: var tmp = this.load(this.pc.load()); this.pc.increment(); address = this.load2BytesFromZeropage(tmp); address += this.y.load(); address = address & 0xffff; break; default: throw new Error('Cpu: Unkown addressing mode.'); break; } return address; }, /** * */ updateN: function(value) { if((value & 0x80) === 0) this.p.clearN(); else this.p.setN(); }, /** * */ updateZ: function(value) { if((value & 0xff) === 0) this.p.setZ(); else this.p.clearZ(); }, /** * */ updateC: function(value) { if((value & 0x100) === 0) this.p.clearC(); else this.p.setC(); }, getStackAddress: function() { return this.sp.load() + 0x100; }, pushStack: function(value) { this.store(this.getStackAddress(), value); this.sp.decrement(); }, pushStack2Bytes: function(value) { this.store(this.getStackAddress(), (value >> 8) & 0xff); this.sp.decrement(); this.store(this.getStackAddress(), value & 0xff); this.sp.decrement(); }, popStack: function() { this.sp.increment(); return this.load(this.getStackAddress()); }, popStack2Bytes: function() { this.sp.increment(); var value = this.load(this.getStackAddress()); this.sp.increment(); return (this.load(this.getStackAddress()) << 8) | value; }, doBranch: function(op, flag) { var result = this.loadWithAddressingMode(op); if(flag) this.pc.add(result); }, operate: function(op, opc) { switch(op.instruction.id) { case this.INSTRUCTIONS.ADC.id: var src1 = this.a.load(); var src2 = this.loadWithAddressingMode(op); var c = this.p.isC() ? 1 : 0; var result = src1 + src2 + c; this.a.store(result); this.updateN(result) this.updateZ(result) this.updateC(result) if(!((src1 ^ src2) & 0x80) && ((src2 ^ result) & 0x80)) this.p.setV(); else this.p.clearV(); break; case this.INSTRUCTIONS.AND.id: var src1 = this.a.load(); var src2 = this.loadWithAddressingMode(op); var result = src1 & src2; this.a.store(result); this.updateN(result); this.updateZ(result); break; case this.INSTRUCTIONS.ASL.id: var self = this; var func = function(src) { var result = src << 1; self.updateN(result) self.updateZ(result); self.updateC(result); return result; }; this.updateMemoryWithAddressingMode(op, func); break; case this.INSTRUCTIONS.BCC.id: this.doBranch(op, !this.p.isC()); break; case this.INSTRUCTIONS.BCS.id: this.doBranch(op, this.p.isC()); break; case this.INSTRUCTIONS.BEQ.id: this.doBranch(op, this.p.isZ()); break; // TODO: check logic. case this.INSTRUCTIONS.BIT.id: var src1 = this.a.load(); var src2 = this.loadWithAddressingMode(op); var result = src1 & src2; this.updateN(src2); this.updateZ(result); if((src2 & 0x40) == 0) this.p.clearV(); else this.p.setV(); break; case this.INSTRUCTIONS.BMI.id: this.doBranch(op, this.p.isN()); break; case this.INSTRUCTIONS.BNE.id: this.doBranch(op, !this.p.isZ()); break; case this.INSTRUCTIONS.BPL.id: this.doBranch(op, !this.p.isN()); break; case this.INSTRUCTIONS.BRK.id: this.pc.increment(); // seems like necessary this.p.setA(); this.p.setB(); this.interrupt(this.INTERRUPTS.BRK); break; case this.INSTRUCTIONS.BVC.id: this.doBranch(op, !this.p.isV()); break; case this.INSTRUCTIONS.BVS.id: this.doBranch(op, this.p.isV()); break; case this.INSTRUCTIONS.CLC.id: this.p.clearC(); break; case this.INSTRUCTIONS.CLD.id: this.p.clearD(); break; case this.INSTRUCTIONS.CLI.id: this.p.clearI(); break; case this.INSTRUCTIONS.CLV.id: this.p.clearV(); break; // TODO: separate? case this.INSTRUCTIONS.CMP.id: case this.INSTRUCTIONS.CPX.id: case this.INSTRUCTIONS.CPY.id: var src1; switch(op.instruction.id) { case this.INSTRUCTIONS.CMP.id: src1 = this.a.load(); break; case this.INSTRUCTIONS.CPX.id: src1 = this.x.load(); break; case this.INSTRUCTIONS.CPY.id: src1 = this.y.load(); break; } var src2 = this.loadWithAddressingMode(op); var result = src1 - src2; this.updateN(result); this.updateZ(result); if(src1 >= src2) this.p.setC(); else this.p.clearC(); break; case this.INSTRUCTIONS.DEC.id: var self = this; var func = function(src) { var result = src - 1; self.updateN(result); self.updateZ(result); return result; }; this.updateMemoryWithAddressingMode(op, func); break; case this.INSTRUCTIONS.DEX.id: case this.INSTRUCTIONS.DEY.id: var reg; switch(op.instruction.id) { case this.INSTRUCTIONS.DEX.id: reg = this.x; break; case this.INSTRUCTIONS.DEY.id: reg = this.y; break; } var src1 = reg.load(); var result = src1 - 1; reg.store(result); this.updateN(result); this.updateZ(result); break; case this.INSTRUCTIONS.EOR.id: var src1 = this.a.load(); var src2 = this.loadWithAddressingMode(op); var result = src1 ^ src2; this.a.store(result); this.updateN(result); this.updateZ(result); break; case this.INSTRUCTIONS.INC.id: var self = this; var func = function(src) { var result = src + 1; self.updateN(result); self.updateZ(result); return result; }; this.updateMemoryWithAddressingMode(op, func); break; case this.INSTRUCTIONS.INX.id: case this.INSTRUCTIONS.INY.id: var reg; switch(op.instruction.id) { case this.INSTRUCTIONS.INX.id: reg = this.x; break; case this.INSTRUCTIONS.INY.id: reg = this.y; break; } var src1 = reg.load(); var result = src1 + 1; reg.store(result); this.updateN(result); this.updateZ(result); break; // TODO: check the logic. case this.INSTRUCTIONS.JMP.id: var address = this.getAddressWithAddressingMode(op); this.pc.store(address); break; // TODO: check the logic. case this.INSTRUCTIONS.JSR.id: var address = this.getAddressWithAddressingMode(op); this.pc.decrement(); this.pushStack2Bytes(this.pc.load()); this.pc.store(address); break; case this.INSTRUCTIONS.LDA.id: case this.INSTRUCTIONS.LDX.id: case this.INSTRUCTIONS.LDY.id: var result = this.loadWithAddressingMode(op); var reg; switch(op.instruction.id) { case this.INSTRUCTIONS.LDA.id: reg = this.a; break; case this.INSTRUCTIONS.LDX.id: reg = this.x; break; case this.INSTRUCTIONS.LDY.id: reg = this.y; break; } reg.store(result); this.updateN(result); this.updateZ(result); break; case this.INSTRUCTIONS.LSR.id: var self = this; var func = function(src) { var result = src >> 1; self.p.clearN(); self.updateZ(result); if((src & 1) == 0) self.p.clearC(); else self.p.setC(); return result; }; this.updateMemoryWithAddressingMode(op, func); break; case this.INSTRUCTIONS.NOP.id: break; case this.INSTRUCTIONS.ORA.id: var src1 = this.a.load(); var src2 = this.loadWithAddressingMode(op); var result = src1 | src2; this.a.store(result); this.updateN(result); this.updateZ(result); break; case this.INSTRUCTIONS.PHA.id: case this.INSTRUCTIONS.PHP.id: var reg; switch(op.instruction.id) { case this.INSTRUCTIONS.PHA.id: reg = this.a; break; case this.INSTRUCTIONS.PHP.id: this.p.setA(); this.p.setB(); reg = this.p; break; } this.pushStack(reg.load()); break; case this.INSTRUCTIONS.PLA.id: var result = this.popStack(); this.a.store(result); this.updateN(result); this.updateZ(result); break; case this.INSTRUCTIONS.PLP.id: this.p.store(this.popStack()); break; case this.INSTRUCTIONS.ROL.id: var self = this; var func = function(src) { var c = self.p.isC() ? 1 : 0; var result = (src << 1) | c; self.updateN(result); self.updateZ(result); self.updateC(result); return result; }; this.updateMemoryWithAddressingMode(op, func); break; case this.INSTRUCTIONS.ROR.id: var self = this; var func = function(src) { var c = self.p.isC() ? 0x80 : 0x00; var result = (src >> 1) | c; self.updateN(result); self.updateZ(result); if((src & 1) == 0) self.p.clearC(); else self.p.setC(); return result; }; this.updateMemoryWithAddressingMode(op, func); break; // TODO: check logic. case this.INSTRUCTIONS.RTI.id: this.p.store(this.popStack()); this.pc.store(this.popStack2Bytes()); break; // TODO: check logic. case this.INSTRUCTIONS.RTS.id: this.pc.store(this.popStack2Bytes() + 1); break; case this.INSTRUCTIONS.SBC.id: var src1 = this.a.load(); var src2 = this.loadWithAddressingMode(op); var c = this.p.isC() ? 0 : 1; var result = src1 - src2 - c; this.a.store(result); this.updateN(result) this.updateZ(result) // TODO: check if this logic is right. if(src1 >= src2 + c) this.p.setC(); else this.p.clearC(); // TODO: implement right overflow logic. // this is just a temporal logic. if(((src1 ^ result) & 0x80) && ((src1 ^ src2) & 0x80)) this.p.setV(); else this.p.clearV(); break; case this.INSTRUCTIONS.SEC.id: this.p.setC(); break; case this.INSTRUCTIONS.SED.id: this.p.setD(); break; case this.INSTRUCTIONS.SEI.id: this.p.setI(); break; case this.INSTRUCTIONS.STA.id: case this.INSTRUCTIONS.STX.id: case this.INSTRUCTIONS.STY.id: var reg; switch(op.instruction.id) { case this.INSTRUCTIONS.STA.id: reg = this.a; break; case this.INSTRUCTIONS.STX.id: reg = this.x; break; case this.INSTRUCTIONS.STY.id: reg = this.y; break; } this.s