/* * 6502 Based Virtual Computer * Copyright (C) 2011 Scott C. Duensing * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // 6502 CPU Core - based on http://www.6502asm.com // Lots of good ideas swiped from http://skilldrick.github.io/easy6502/ function cpu6502() { // --- Private variables. var self = this; var TRACING = false; // Memory Pointer. var MEMORY = null; // Registers. var REG_A = 0; var REG_X = 0; var REG_Y = 0; var REG_P = 0; var REG_PC = 0; var REG_SP = 0xff; // Internal CPU Status. var CODE_RUNNING = false; // CPU Event Callback. var CALLBACK = null; // Instruction Function "Pointers". var INSTRUCTION_POINTERS = null; // Opcode names for debugging. (Ugly. Generated programmatically.) var OPCODE_NAMES = ["BRK", "ORA", undefined, undefined, undefined, "ORA", "ASL", undefined, "PHP", "ORA", "ASL", undefined, undefined, "ORA", "ASL", undefined, "BPL", "ORA", undefined, undefined, undefined, "ORA", "ASL", undefined, "CLC", "ORA", undefined, undefined, undefined, "ORA", "ASL", undefined, "JSR", "AND", undefined, undefined, "BIT", "AND", "ROL", undefined, "PLP", "AND", "ROL", undefined, "BIT", "AND", "ROL", undefined, "BMI", "AND", undefined, undefined, undefined, "AND", "ROL", undefined, "SEC", "AND", undefined, undefined, undefined, "AND", "ROL", undefined, "RTI", "EOR", undefined, undefined, undefined, "EOR", "LSR", undefined, "PHA", "EOR", "LSR", undefined, "JMP", "EOR", "LSR", undefined, "BVC", "EOR", undefined, undefined, undefined, "EOR", "LSR", undefined, "CLI", "EOR", undefined, undefined, undefined, "EOR", "LSR", undefined, "RTS", "ADC", undefined, undefined, undefined, "ADC", "ROR", undefined, "PLA", "ADC", "ROR", undefined, "JMP", "ADC", "ROR", undefined, "BVS", "ADC", undefined, undefined, undefined, "ADC", "ROR", undefined, "SEI", "ADC", undefined, undefined, undefined, "ADC", "ROR", undefined, undefined, "STA", undefined, undefined, "STY", "STA", "STX", undefined, "DEY", undefined, "TXA", undefined, "STY", "STA", "STX", undefined, "BCC", "STA", undefined, undefined, "STY", "STA", "STX", undefined, "TYA", "STA", "TXS", undefined, undefined, "STA", undefined, undefined, "LDY", "LDA", "LDX", undefined, "LDY", "LDA", "LDX", undefined, "TAY", "LDA", "TAX", undefined, "LDY", "LDA", "LDX", undefined, "BCS", "LDA", undefined, undefined, "LDY", "LDA", "LDX", undefined, "CLV", "LDA", "TSX", undefined, "LDY", "LDA", "LDX", undefined, "CPY", "CMP", undefined, undefined, "CPY", "CMP", "DEC", undefined, "INY", "CMP", "DEX", undefined, "CPY", "CMP", "DEC", undefined, "BNE", "CMP", undefined, undefined, undefined, "CMP", "DEC", undefined, "CLD", "CMP", undefined, undefined, undefined, "CMP", "DEC", undefined, "CPX", "SBC", undefined, undefined, "CPX", "SBC", "INC", undefined, "INX", "SBC", "NOP", undefined, "CPX", "SBC", "INC", undefined, "BEQ", "SBC", undefined, undefined, undefined, "SBC", "INC", undefined, "SED", "SBC", undefined, undefined, undefined, "SBC", "INC"]; // --- Public variables, enums. this.E_HALT = 0; this.E_UNKNOWN_OPCODE = 1; this.E_STACK_OVERFLOW = 2; this.E_STACK_UNDERFLOW = 3; // --- Private methods, general. var BIT = function(value) { if (value & 0x80) REG_P |= 0x80; else REG_P &= 0x7f; if (value & 0x40) REG_P |= 0x40; else REG_P &= ~0x40; if (REG_A & value) REG_P &= 0xfd; else REG_P |= 0x02; }; var carrySet = function() { return REG_P & 1; }; // Clear Carry var CLC = function() { REG_P &= 0xfe; }; // Clear Overflow var CLV = function() { REG_P &= 0xbf; }; var DEC = function(addr) { var value = MEMORY.readByte(addr); value--; value &= 0xff; MEMORY.writeByte(addr, value); setNVflags(value); }; var decimalMode = function() { return REG_P & 8; }; var doCompare = function(reg, val) { if (reg >= val) SEC(); else CLC(); val = (reg - val); setNVflags(val); }; var INC = function(addr) { var value = MEMORY.readByte(addr); value++; value &= 0xff; MEMORY.writeByte(addr, value); setNVflags(value); }; // Might as well Jump... JUMP! var jumpBranch = function(offset) { if (offset > 0x7f) REG_PC = (REG_PC - (0x100 - offset)); else REG_PC = (REG_PC + offset); }; var negativeSet = function() { return REG_P & 0x80; }; var overflowSet = function() { return REG_P & 0x40; }; // Fetch the next byte pointed to by the program counter. var popByte = function() { return (MEMORY.readByte(REG_PC++) & 0xff); }; // Fetch the next word pointed to by the program counter. var popWord = function() { return popByte() + (popByte() << 8); }; // Run a block of instructions. This is used by 'run' to avoid blocking the browser event queue. var runBlock = function() { if (CODE_RUNNING) { var instructions = 100; while ((instructions-- > 0) && (CODE_RUNNING)) self.execute(); setTimeout(runBlock, 0); } }; // Set Carry var SEC = function() { REG_P |= 1; }; var setCarryFlagFromBit0 = function(value) { REG_P = (REG_P & 0xfe) | (value & 1); }; var setCarryFlagFromBit7 = function(value) { REG_P = (REG_P & 0xfe) | ((value >> 7) & 1); }; // Set Zero and Negative processor flags. var setNVflags = function(value) { if (value) REG_P &= 0xfd; else REG_P |= 0x02; if (value & 0x80) REG_P |= 0x80; else REG_P &= 0x7f; }; var setOverflow = function() { REG_P |= 0x40; }; // Pop a value off the stack. var stackPop = function() { REG_SP++; if (REG_SP > 0xff) { REG_SP &= 0xff; CALLBACK(self.E_STACK_UNDERFLOW); } var value = MEMORY.readByte(REG_SP + 0x100); return value; }; // Push a value onto the stack. var stackPush = function(value) { MEMORY.writeByte(REG_SP + 0x100, value & 0xff); REG_SP--; if (REG_SP < 0) { REG_SP &= 0xff; CALLBACK(self.E_STACK_OVERFLOW); } }; var testADC = function(value) { var tmp; if ((REG_A ^ value) & 0x80) CLV(); else setOverflow(); if (decimalMode()) { tmp = (REG_A & 0xf) + (value & 0xf) + carrySet(); if (tmp >= 10) tmp = 0x10 | ((tmp + 6) & 0xf); tmp += (REG_A & 0xf0) + (value & 0xf0); if (tmp >= 160) { SEC(); if (overflowSet() && tmp >= 0x180) CLV(); tmp += 0x60; } else { CLC(); if (overflowSet() && tmp < 0x80) CLV(); } } else { tmp = REG_A + value + carrySet(); if (tmp >= 0x100) { SEC(); if (overflowSet() && tmp >= 0x180) CLV(); } else { CLC(); if (overflowSet() && tmp < 0x80) CLV(); } } REG_A = tmp & 0xff; setNVflags(REG_A); }; var testSBC = function(value) { var tmp; var w; if ((REG_A ^ value) & 0x80) setOverflow(); else CLV(); if (decimalMode()) { tmp = 0xf + (REG_A & 0xf) - (value & 0xf) + carrySet(); if (tmp < 0x10) { w = 0; tmp -= 6; } else { w = 0x10; tmp -= 0x10; } w += 0xf0 + (REG_A & 0xf0) - (value & 0xf0); if (w < 0x100) { CLC(); if (overflowSet() && w < 0x80) CLV(); w -= 0x60; } else { SEC(); if (overflowSet() && w >= 0x180) CLV(); } w += tmp; } else { w = 0xff + REG_A - value + carrySet(); if (w < 0x100) { CLC(); if (overflowSet() && w < 0x80) CLV(); } else { SEC(); if (overflowSet() && w >= 0x180) CLV(); } } REG_A = w & 0xff; setNVflags(REG_A); }; var zeroSet = function() { return REG_P & 0x02; }; // --- Private methods, CPU core. // BRK (Break) var i00 = function() { CODE_RUNNING = false; }; // ORA (bitwise OR with Accumulator) Indirect,X var i01 = function() { var zp = (popByte() + REG_X) & 0xff; var addr = MEMORY.readWord(addr); var value = MEMORY.readByte(addr); REG_A |= value; setNVflags(REG_A); }; // ORA (bitwise OR with Accumulator) Zero Page var i05 = function() { var zp = popByte(); REG_A |= MEMORY.readByte(zp); setNVflags(REG_A); }; // ASL (Arithmetic Shift Left) Zero Page var i06 = function() { var zp = popByte(); var value = MEMORY.readByte(zp); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; MEMORY.writeByte(zp, value); setNVflags(value); }; // PHP (PusH Processor status) var i08 = function() { stackPush(REG_P | 0x30); }; // ORA (bitwise OR with Accumulator) Immediate var i09 = function() { REG_A |= popByte(); setNVflags(REG_A); }; // ASL (Arithmetic Shift Left) Accumulator var i0a = function() { setCarryFlagFromBit7(REG_A); REG_A = (REG_A << 1) & 0xff; setNVflags(REG_A); }; // ORA (bitwise OR with Accumulator) Absolute var i0d = function() { REG_A |= MEMORY.readByte(popWord()); setNVflags(REG_A); }; // ASL (Arithmetic Shift Left) Absolute var i0e = function() { var addr = popWord(); var value = MEMORY.readByte(addr); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; MEMORY.writeByte(addr, value); setNVflags(value); }; // BPL (Branch on PLus) var i10 = function() { var offset = popByte(); if (!negativeSet()) jumpBranch(offset); }; // ORA (bitwise OR with Accumulator) Indirect,Y var i11 = function() { var zp = popByte(); var value = MEMORY.readWord(zp) + REG_Y; REG_A |= MEMORY.readByte(value); setNVflags(REG_A); }; // ORA (bitwise OR with Accumulator) Zero Page,X var i15 = function() { var addr = (popByte() + REG_X) & 0xff; REG_A |= MEMORY.readByte(addr); setNVflags(REG_A); }; // ASL (Arithmetic Shift Left) Zero Page,X var i16 = function() { var addr = (popByte() + REG_X) & 0xff; var value = MEMORY.readByte(addr); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; MEMORY.writeByte(addr, value); setNVflags(value); }; // CLC (CLear Carry) var i18 = function() { CLC(); }; // ORA (bitwise OR with Accumulator) Absolute,Y var i19 = function() { addr = popWord() + REG_Y; REG_A |= MEMORY.readByte(addr); setNVflags(REG_A); }; // ORA (bitwise OR with Accumulator) Absolute,X var i1d = function() { var addr = popWord() + REG_X; REG_A |= MEMORY.readByte(addr); setNVflags(REG_A); }; // ASL (Arithmetic Shift Left) Absolute,X var i1e = function() { var addr = popWord() + REG_X; var value = MEMORY.readByte(addr); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; MEMORY.writeByte(addr, value); setNVflags(value); }; // JSR (Jump to SubRoutine) Absolute var i20 = function() { var addr = popWord(); var currAddr = REG_PC - 1; stackPush(((currAddr >> 8) & 0xff)); stackPush((currAddr & 0xff)); REG_PC = addr; }; // AND (bitwise AND with accumulator) Indirect,X var i21 = function() { var addr = (popByte() + REG_X) & 0xff; var value = MEMORY.readWord(addr); REG_A &= value; setNVflags(REG_A); }; // BIT (test BITs) Zero Page var i24 = function() { var zp = popByte(); var value = MEMORY.readByte(zp); BIT(value); }; // AND (bitwise AND with accumulator) Zero Page var i25 = function() { var zp = popByte(); REG_A &= MEMORY.readByte(zp); setNVflags(REG_A); }; // ROL (ROtate Left) Zero Page var i26 = function() { var sf = carrySet(); var addr = popByte(); var value = MEMORY.readByte(addr); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; value |= sf; MEMORY.writeByte(addr, value); setNVflags(value); }; // PLP (PuLl Processor status) var i28 = function() { REG_P = stackPop() | 0x30; }; // AND (bitwise AND with accumulator) Immediate var i29 = function() { REG_A &= popByte(); setNVflags(REG_A); }; // ROL (ROtate Left) Accumulator var i2a = function() { var sf = carrySet(); setCarryFlagFromBit7(REG_A); REG_A = (REG_A << 1) & 0xff; REG_A |= sf; setNVflags(REG_A); }; // BIT (test BITs) Absolute var i2c = function() { var value = MEMORY.readByte(popWord()); BIT(value); }; // AND (bitwise AND with accumulator) Absolute var i2d = function() { var value = MEMORY.readByte(popWord()); REG_A &= value; setNVflags(REG_A); }; // ROL (ROtate Left) Absolute var i2e = function() { var sf = carrySet(); var addr = popWord(); var value = MEMORY.readByte(addr); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; value |= sf; MEMORY.writeByte(addr, value); setNVflags(value); }; // BMI (Branch on MInus) var i30 = function() { var offset = popByte(); if (negativeSet()) jumpBranch(offset); }; // AND (bitwise AND with accumulator) Indirect,Y var i31 = function() { var zp = popByte(); var value = MEMORY.readWord(zp) + REG_Y; REG_A &= MEMORY.readByte(value); setNVflags(REG_A); }; // AND (bitwise AND with accumulator) Zero Page,X var i35 = function() { var zp = (popByte() + REG_X) & 0xff; REG_A &= MEMORY.readByte(zp); setNVflags(REG_A); }; // ROL (ROtate Left) Zero Page,X var i36 = function() { var sf = carrySet(); var addr = (popByte() + REG_X) & 0xff; var value = MEMORY.readByte(addr); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; value |= sf; MEMORY.writeByte(addr, value); setNVflags(value); }; // SEC (SEt Carry) var i38 = function() { SEC(); }; // AND (bitwise AND with accumulator) Absolute,Y var i39 = function() { var addr = popWord() + REG_Y; var value = MEMORY.readByte(addr); REG_A &= value; setNVflags(REG_A); }; // AND (bitwise AND with accumulator) Absolute,X var i3d = function() { var addr = popWord() + REG_X; var value = MEMORY.readByte(addr); REG_A &= value; setNVflags(REG_A); }; // ROL (ROtate Left) Absolute,X var i3e = function() { var sf = carrySet(); var addr = popWord() + REG_X; var value = MEMORY.readByte(addr); setCarryFlagFromBit7(value); value = (value << 1) & 0xff; value |= sf; MEMORY.writeByte(addr, value); setNVflags(value); }; // RTI (ReTurn from Interrupt) var i40 = function() { REG_P = stackPop() | 0x30; REG_PC = stackPop() | (stackPop() << 8); }; // EOR (bitwise Exclusive OR) Indirect,X var i41 = function() { var zp = (popByte() + REG_X) & 0xff; var value = MEMORY.readWord(zp); REG_A ^= MEMORY.readByte(value); setNVflags(REG_A); }; // EOR (bitwise Exclusive OR) Zero Page var i45 = function() { var addr = popByte() & 0xff; var value = MEMORY.readByte(addr); REG_A ^= value; setNVflags(REG_A); }; // LSR (Logical Shift Right) Zero Page var i46 = function() { var addr = popByte() & 0xff; var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; MEMORY.writeByte(addr, value); setNVflags(value); }; // PHA (PusH Accumulator) var i48 = function() { stackPush(REG_A); }; // EOR (bitwise Exclusive OR) Immediate var i49 = function() { REG_A ^= popByte(); setNVflags(REG_A); }; // LSR (Logical Shift Right) Accumulator var i4a = function() { setCarryFlagFromBit0(REG_A); REG_A = REG_A >> 1; setNVflags(REG_A); }; // JMP (JuMP) Absolute var i4c = function() { REG_PC = popWord(); }; // EOR (bitwise Exclusive OR) Absolute var i4d = function() { var addr = popWord(); var value = MEMORY.readByte(addr); REG_A ^= value; setNVflags(REG_A); }; // LSR (Logical Shift Right) Absolute var i4e = function() { var addr = popWord(); var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; MEMORY.writeByte(addr, value); setNVflags(value); }; // BVC (Branch on oVerflow Clear) var i50 = function() { var offset = popByte(); if (!overflowSet()) jumpBranch(offset); }; // EOR (bitwise Exclusive OR) Indirect,Y var i51 = function() { var zp = popByte(); var value = MEMORY.readWord(zp) + REG_Y; REG_A ^= MEMORY.readByte(value); setNVflags(REG_A); }; // EOR (bitwise Exclusive OR) Zero Page,X var i55 = function() { var addr = (popByte() + REG_X) & 0xff; REG_A ^= MEMORY.readByte(addr); setNVflags(REG_A); }; // LSR (Logical Shift Right) Zero Page,X var i56 = function() { var addr = (popByte() + REG_X) & 0xff; var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; MEMORY.writeByte(addr, value); setNVflags(value); }; // CLI (CLear Interrupt) var i58 = function() { REG_P &= ~0x04; // Interrupts not supported yet. }; // EOR (bitwise Exclusive OR) Absolute,Y var i59 = function() { var addr = popWord() + REG_Y; var value = MEMORY.readByte(addr); REG_A ^= value; setNVflags(REG_A); }; // EOR (bitwise Exclusive OR) Absolute,X var i5d = function() { var addr = popWord() + REG_X; var value = MEMORY.readByte(addr); REG_A ^= value; setNVflags(REG_A); }; // LSR (Logical Shift Right) Absolute,X var i5e = function() { var addr = popWord() + REG_X; var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; MEMORY.writeByte(addr, value); setNVflags(value); }; // RTS (ReTurn from Subroutine) var i60 = function() { REG_PC = (stackPop() | (stackPop() << 8)) + 1; }; // ADC (ADd with Carry) Indirect,X var i61 = function() { var zp = (popByte() + REG_X) & 0xff; var addr = MEMORY.readWord(zp); value = MEMORY.readByte(addr); testADC(value); }; // ADC (ADd with Carry) Zero Page var i65 = function() { var addr = popByte(); var value = MEMORY.readByte(addr); testADC(value); }; // ROR (ROtate Right) Zero Page var i66 = function() { var sf = carrySet(); var addr = popByte(); var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; if (sf) value |= 0x80; MEMORY.writeByte(addr, value); setNVflags(value); }; // PLA (PuLl Accumulator) var i68 = function() { REG_A = stackPop(); setNVflags(REG_A); }; // ADC (ADd with Carry) Immediate var i69 = function() { var value = popByte(); testADC(value); }; // ROR (ROtate Right) Accumulator var i6a = function() { var sf = carrySet(); setCarryFlagFromBit0(REG_A); REG_A = REG_A >> 1; if (sf) REG_A |= 0x80; setNVflags(REG_A); }; // JMP (JuMP) Indirect var i6c = function() { var addr = popWord(); REG_PC = MEMORY.readWord(addr); }; // ADC (ADd with Carry) Absolute var i6d = function() { var addr = popWord(); var value = MEMORY.readByte(addr); testADC(value); }; // ROR (ROtate Right) Absolute var i6e = function() { var sf = carrySet(); var addr = popWord(); var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; if (sf) value |= 0x80; MEMORY.writeByte(addr, value); setNVflags(value); }; // BVS (Branch on oVerflow Set) var i70 = function() { var offset = popByte(); if (overflowSet()) jumpBranch(offset); }; // ADC (ADd with Carry) Indirect,Y var i71 = function() { var zp = popByte(); var addr = MEMORY.readWord(zp); var value = MEMORY.readByte(addr + REG_Y); testADC(value); }; // ADC (ADd with Carry) Zero Page,X var i75 = function() { var addr = (popByte() + REG_X) & 0xff; var value = MEMORY.readByte(addr); testADC(value); }; // ROR (ROtate Right) Zero Page,X var i76 = function() { var sf = carrySet(); var addr = (popByte() + REG_X) & 0xff; var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; if (sf) value |= 0x80; MEMORY.writeByte(addr, value); setNVflags(value); }; // SEI (SEt Interrupt) var i78 = function() { REG_P |= 0x04; // Interrupts not supported yet. }; // ADC (ADd with Carry) Absolute,Y var i79 = function() { var addr = popWord(); var value = MEMORY.readByte(addr + REG_Y); testADC(value); }; // ADC (ADd with Carry) Absolute,X var i7d = function() { var addr = popWord(); var value = MEMORY.readByte(addr + REG_X); testADC(value); }; // ROR (ROtate Right) Absolute,X var i7e = function() { var sf = carrySet(); var addr = popWord() + REG_X; var value = MEMORY.readByte(addr); setCarryFlagFromBit0(value); value = value >> 1; if (sf) value |= 0x80; MEMORY.writeByte(addr, value); setNVflags(value); }; // STA (STore Accumulator) Indirect,X var i81 = function() { var zp = (popByte() + REG_X) & 0xff; var addr = MEMORY.readWord(zp); MEMORY.writeByte(addr, REG_A); }; // STY (STore Y register) Zero Page var i84 = function() { MEMORY.writeByte(popByte(), REG_Y); }; // STA (STore Accumulator) Zero Page var i85 = function() { MEMORY.writeByte(popByte(), REG_A); }; // STX (STore X register) Zero Page var i86 = function() { MEMORY.writeByte(popByte(), REG_X); }; // DEY (DEcrement Y) var i88 = function() { REG_Y = (REG_Y - 1) & 0xff; setNVflags(REG_Y); }; // TXA (Transfer X to A) var i8a = function() { REG_A = REG_X & 0xff; setNVflags(REG_A); }; // STY (STore Y register) Absolute var i8c = function() { MEMORY.writeByte(popWord(), REG_Y); }; // STA (STore Accumulator) Absolute var i8d = function() { MEMORY.writeByte(popWord(), REG_A); }; // STX (STore X register) Absolute var i8e = function() { MEMORY.writeByte(popWord(), REG_X); }; // BCC (Branch on Carry Clear) var i90 = function() { var offset = popByte(); if (!carrySet()) jumpBranch(offset); }; // STA (STore Accumulator) Indirect,Y var i91 = function() { var zp = popByte(); var addr = MEMORY.readWord(zp) + REG_Y; MEMORY.writeByte(addr, REG_A); }; // STY (STore Y register) Zero Page,X var i94 = function() { MEMORY.writeByte((popByte() + REG_X) & 0xff, REG_Y); }; // STA (STore Accumulator) Zero Page,X var i95 = function() { MEMORY.writeByte((popByte() + REG_X) & 0xff, REG_A); }; // STX (STore X register) Zero Page,Y var i96 = function() { MEMORY.writeByte((popByte() + REG_Y) & 0xff, REG_X); }; // TYA (Transfer Y to A) var i98 = function() { REG_A = REG_Y & 0xff; setNVflags(REG_A); }; // STA (STore Accumulator) Absolute,Y var i99 = function() { MEMORY.writeByte(popWord() + REG_Y, REG_A); }; // TXS (Transfer X to Stack ptr) var i9a = function() { REG_SP = REG_X & 0xff; }; // STA (STore Accumulator) Absolute,X var i9d = function() { var addr = popWord(); MEMORY.writeByte(addr + REG_X, REG_A); }; // LDY (LoaD Y register) Immediate var ia0 = function() { REG_Y = popByte(); setNVflags(REG_Y); }; // LDA (LoaD Accumulator) Indirect,X var ia1 = function() { var zp = (popByte() + REG_X) & 0xff; var addr = MEMORY.readWord(zp); REG_A = MEMORY.readByte(addr); setNVflags(REG_A); }; // LDX (LoaD X register) Immediate var ia2 = function() { REG_X = popByte(); setNVflags(REG_X); }; // LDY (LoaD Y register) Zero Page var ia4 = function() { REG_Y = MEMORY.readByte(popByte()); setNVflags(REG_Y); }; // LDA (LoaD Accumulator) Zero Page var ia5 = function() { REG_A = MEMORY.readByte(popByte()); setNVflags(REG_A); }; // LDX (LoaD X register) Zero Page var ia6 = function() { REG_X = MEMORY.readByte(popByte()); setNVflags(REG_X); }; // TAY (Transfer A to Y) var ia8 = function() { REG_Y = REG_A & 0xff; setNVflags(REG_Y); }; // LDA (LoaD Accumulator) Immediate var ia9 = function() { REG_A = popByte(); setNVflags(REG_A); }; // TAX (Transfer A to X) var iaa = function() { REG_X = REG_A & 0xff; setNVflags(REG_X); }; // LDY (LoaD Y register) Absolute var iac = function() { REG_Y = MEMORY.readByte(popWord()); setNVflags(REG_Y); }; // LDA (LoaD Accumulator) Absolute var iad = function() { REG_A = MEMORY.readByte(popWord()); setNVflags(REG_A); }; // LDX (LoaD X register) Absolute var iae = function() { REG_X = MEMORY.readByte(popWord()); setNVflags(REG_X); }; // BCS (Branch on Carry Set) var ib0 = function() { var offset = popByte(); if (carrySet()) jumpBranch(offset); }; // LDA (LoaD Accumulator) Indirect,Y var ib1 = function() { var zp = popByte(); var addr = MEMORY.readWord(zp) + REG_Y; REG_A = MEMORY.readByte(addr); setNVflags(REG_A); }; // LDY (LoaD Y register) Zero Page,X var ib4 = function() { REG_Y = MEMORY.readByte((popByte() + REG_X) & 0xff); setNVflags(REG_Y); }; // LDA (LoaD Accumulator) Zero Page,X var ib5 = function() { REG_A = MEMORY.readByte((popByte() + REG_X) & 0xff); setNVflags(REG_A); }; // LDX (LoaD X register) Zero Page,Y var ib6 = function() { REG_X = MEMORY.readByte((popByte() + REG_Y) & 0xff); setNVflags(REG_X); }; // CLV (CLear oVerflow) var ib8 = function() { CLV(); }; // LDA (LoaD Accumulator) Absolute,Y var ib9 = function() { var addr = popWord() + REG_Y; REG_A = MEMORY.readByte(addr); setNVflags(REG_A); }; // TSX (Transfer Stack ptr to X) var iba = function() { REG_X = REG_SP & 0xff; setNVflags(REG_X); }; // LDY (LoaD Y register) Absolute,X var ibc = function() { var addr = popWord() + REG_X; REG_Y = MEMORY.readByte(addr); setNVflags(REG_Y); }; // LDA (LoaD Accumulator) Absolute,X var ibd = function() { var addr = popWord() + REG_X; REG_A = MEMORY.readByte(addr); setNVflags(REG_A); }; // LDX (LoaD X register) Absolute,Y var ibe = function() { var addr = popWord() + REG_Y; REG_X = MEMORY.readByte(addr); setNVflags(REG_X); }; // CPY (ComPare Y register) Immediate var ic0 = function() { var value = popByte(); doCompare(REG_Y, value); }; // CMP (CoMPare accumulator) Indirect,X var ic1 = function() { var zp = (popByte() + REG_X) & 0xff; var addr = MEMORY.readWord(zp); var value = MEMORY.readByte(addr); doCompare(REG_A, value); }; // CPY (ComPare Y register) Zero Page var ic4 = function() { var value = MEMORY.readByte(popByte()); doCompare(REG_Y, value); }; // CMP (CoMPare accumulator) Zero Page var ic5 = function() { var value = MEMORY.readByte(popByte()); doCompare(REG_A, value); }; // DEC (DECrement memory) Zero Page var ic6 = function() { var zp = popByte(); DEC(zp); }; // INY (INcrement Y) var ic8 = function() { REG_Y = (REG_Y + 1) & 0xff; setNVflags(REG_Y); }; // CMP (CoMPare accumulator) Immediate var ic9 = function() { var value = popByte(); doCompare(REG_A, value); }; // DEX (DEcrement X) var ica = function() { REG_X = (REG_X - 1) & 0xff; setNVflags(REG_X); }; // CPY (ComPare Y register) Absolute var icc = function() { var value = MEMORY.readByte(popWord()); doCompare(REG_Y, value); }; // CMP (CoMPare accumulator) Absolute var icd = function() { var value = MEMORY.readByte(popWord()); doCompare(REG_A, value); }; // DEC (DECrement memory) Absolute var ice = function() { var addr = popWord(); DEC(addr); }; // BNE (Branch on Not Equal) var id0 = function() { var offset = popByte(); if (!zeroSet()) jumpBranch(offset); }; // CMP (CoMPare accumulator) Indirect,Y var id1 = function() { var zp = popByte(); var addr = MEMORY.readWord(zp) + REG_Y; var value = MEMORY.readByte(addr); doCompare(REG_A, value); }; // CMP (CoMPare accumulator) Zero Page,X var id5 = function() { var value = MEMORY.readByte((popByte() + REG_X) & 0xff); doCompare(REG_A, value); }; // DEC (DECrement memory) Zero Page,X var id6 = function() { var addr = (popByte() + REG_X) & 0xff; DEC(addr); }; // CLD (CLear Decimal) var id8 = function() { REG_P &= 0xf7; }; // CMP (CoMPare accumulator) Absolute,Y var id9 = function() { var addr = popWord() + REG_Y; var value = MEMORY.readByte(addr); doCompare(REG_A, value); }; // CMP (CoMPare accumulator) Absolute,X var idd = function() { var addr = popWord() + REG_X; var value = MEMORY.readByte(addr); doCompare(REG_A, value); }; // DEC (DECrement memory) Absolute,X var ide = function() { var addr = popWord() + REG_X; DEC(addr); }; // CPX (ComPare X register) Immediate var ie0 = function() { var value = popByte(); doCompare(REG_X, value); }; // SBC (SuBtract with Carry) Indirect,X var ie1 = function() { var zp = (popByte() + REG_X) & 0xff; var addr = MEMORY.readWord(zp); var value = MEMORY.readByte(addr); testSBC(value); }; // CPX (ComPare X register) Zero Page var ie4 = function() { var value = MEMORY.readByte(popByte()); doCompare(REG_X, value); }; // SBC (SuBtract with Carry) Zero Page var ie5 = function() { var addr = popByte(); var value = MEMORY.readByte(addr); testSBC(value); }; // INC (INCrement memory) Zero Page var ie6 = function() { var zp = popByte(); INC(zp); }; // INX (INcrement X) var ie8 = function() { REG_X = (REG_X + 1) & 0xff; setNVflags(REG_X); }; // SBC (SuBtract with Carry) Immediate var ie9 = function() { var value = popByte(); testSBC(value); }; // NOP (No OPeration) var iea = function() { // Nothing! }; // CPX (ComPare X register) Absolute var iec = function() { var value = MEMORY.readByte(popWord()); doCompare(REG_X, value); }; // SBC (SuBtract with Carry) Absolute var ied = function() { var addr = popWord(); var value = MEMORY.readByte(addr); testSBC(value); }; // INC (INCrement memory) Absolute var iee = function() { var addr = popWord(); INC(addr); }; // BEQ (Branch on EQual) var if0 = function() { var offset = popByte(); if (zeroSet()) jumpBranch(offset); }; // SBC (SuBtract with Carry) Indirect,Y var if1 = function() { var zp = popByte(); var addr = MEMORY.readWord(zp); var value = MEMORY.readByte(addr + REG_Y); testSBC(value); }; // SBC (SuBtract with Carry) Zero Page,X var if5 = function() { var addr = (popByte() + REG_X) & 0xff; var value = MEMORY.readByte(addr); testSBC(value); }; // INC (INCrement memory) Zero Page,X var if6 = function() { var addr = (popByte() + REG_X) & 0xff; INC(addr); }; // SED (SEt Decimal) var if8 = function() { REG_P |= 8; }; // SBC (SuBtract with Carry) Absolute,Y var if9 = function() { var addr = popWord(); var value = MEMORY.readByte(addr + REG_Y); testSBC(value); }; // SBC (SuBtract with Carry) Absolute,X var ifd = function() { var addr = popWord(); var value = MEMORY.readByte(addr + REG_X); testSBC(value); }; // INC (INCrement memory) Absolute,X var ife = function() { var addr = popWord() + REG_X; INC(addr); }; // Unknown Opcode var ierr = function() { CODE_RUNNING = false; CALLBACK(self.E_UNKNOWN_OPCODE); }; // --- Public methods. // Attach the CPU to the VM. this.attach = function(newMemory, newCallback) { MEMORY = newMemory; CALLBACK = newCallback; INSTRUCTION_POINTERS = [ i00, //00 i01, //01 ierr, //02 ierr, //03 ierr, //04 i05, //05 i06, //06 ierr, //07 i08, //08 i09, //09 i0a, //0a ierr, //0b ierr, //0c i0d, //0d i0e, //0e ierr, //0f i10, //10 i11, //11 ierr, //12 ierr, //13 ierr, //14 i15, //15 i16, //16 ierr, //17 i18, //18 i19, //19 ierr, //1a ierr, //1b ierr, //1c i1d, //1d i1e, //1e ierr, //1f i20, //20 i21, //21 ierr, //22 ierr, //23 i24, //24 i25, //25 i26, //26 ierr, //27 i28, //28 i29, //29 i2a, //2a ierr, //2b i2c, //2c i2d, //2d i2e, //2e ierr, //2f i30, //30 i31, //31 ierr, //32 ierr, //33 ierr, //34 i35, //35 i36, //36 ierr, //37 i38, //38 i39, //39 ierr, //3a ierr, //3b ierr, //3c i3d, //3d i3e, //3e ierr, //3f i40, //40 i41, //41 ierr, //42 ierr, //43 ierr, //44 i45, //45 i46, //46 ierr, //47 i48, //48 i49, //49 i4a, //4a ierr, //4b i4c, //4c i4d, //4d i4e, //4e ierr, //4f i50, //50 i51, //51 ierr, //52 ierr, //53 ierr, //54 i55, //55 i56, //56 ierr, //57 i58, //58 i59, //59 ierr, //5a ierr, //5b ierr, //5c i5d, //5d i5e, //5e ierr, //5f i60, //60 i61, //61 ierr, //62 ierr, //63 ierr, //64 i65, //65 i66, //66 ierr, //67 i68, //68 i69, //69 i6a, //6a ierr, //6b i6c, //6c i6d, //6d i6e, //6e ierr, //6f i70, //70 i71, //71 ierr, //72 ierr, //73 ierr, //74 i75, //75 i76, //76 ierr, //77 i78, //78 i79, //79 ierr, //7a ierr, //7b ierr, //7c i7d, //7d i7e, //7e ierr, //7f ierr, //80 i81, //81 ierr, //82 ierr, //83 i84, //84 i85, //85 i86, //86 ierr, //87 i88, //88 ierr, //89 i8a, //8a ierr, //8b i8c, //8c i8d, //8d i8e, //8e ierr, //8f i90, //90 i91, //91 ierr, //92 ierr, //93 i94, //94 i95, //95 i96, //96 ierr, //97 i98, //98 i99, //99 i9a, //9a ierr, //9b ierr, //9c i9d, //9d ierr, //9e ierr, //9f ia0, //a0 ia1, //a1 ia2, //a2 ierr, //a3 ia4, //a4 ia5, //a5 ia6, //a6 ierr, //a7 ia8, //a8 ia9, //a9 iaa, //aa ierr, //ab iac, //ac iad, //ad iae, //ae ierr, //af ib0, //b0 ib1, //b1 ierr, //b2 ierr, //b3 ib4, //b4 ib5, //b5 ib6, //b6 ierr, //b7 ib8, //b8 ib9, //b9 iba, //ba ierr, //bb ibc, //bc ibd, //bd ibe, //be ierr, //bf ic0, //c0 ic1, //c1 ierr, //c2 ierr, //c3 ic4, //c4 ic5, //c5 ic6, //c6 ierr, //c7 ic8, //c8 ic9, //c9 ica, //ca ierr, //cb icc, //cc icd, //cd ice, //ce ierr, //cf id0, //d0 id1, //d1 ierr, //d2 ierr, //d3 ierr, //d4 id5, //d5 id6, //d6 ierr, //d7 id8, //d8 id9, //d9 ierr, //da ierr, //db ierr, //dc idd, //dd ide, //de ierr, //df ie0, //e0 ie1, //e1 ierr, //e2 ierr, //e3 ie4, //e4 ie5, //e5 ie6, //e6 ierr, //e7 ie8, //e8 ie9, //e9 iea, //ea ierr, //eb iec, //ec ied, //ed iee, //ee ierr, //ef if0, //f0 if1, //f1 ierr, //f2 ierr, //f3 ierr, //f4 if5, //f5 if6, //f6 ierr, //f7 if8, //f8 if9, //f9 ierr, //fa ierr, //fb ierr, //fc ifd, //fd ife, //fe ierr //ff ]; this.reset(); }; // Deserialize CPU state. this.deserialize = function(state) { REG_A = state.A; REG_X = state.X; REG_Y = state.Y; REG_P = state.P; REG_PC = state.PC; REG_SP = state.SP; if (state.R) this.run(); }; // Execute a single instruction. this.execute = function() { if (!CODE_RUNNING) return; var opcode = popByte(); /* if ((REG_PC == 0xb77f + 1) || TRACING) { TRACING = true; console.log("PC = 0x" + (REG_PC - 1).toString(16) + " OP = " + OPCODE_NAMES[opcode] + " (0x" + opcode.toString(16) + ")"); if (OPCODE_NAMES[opcode] == 'JMP') console.log ("0x" + MEMORY.readByte(REG_PC).toString(16) + " 0x" + MEMORY.readByte(REG_PC+1).toString(16)); } var details = " PC = 0x" + (REG_PC - 1).toString(16) + " OP = " + OPCODE_NAMES[opcode] + " (0x" + opcode.toString(16) + ")"; if ((REG_A < 0) || (REG_A > 0xff)) console.log("Error! REG_A = " + REG_A + details); if ((REG_X < 0) || (REG_X > 0xff)) console.log("Error! REG_X = " + REG_X + details); if ((REG_Y < 0) || (REG_Y > 0xff)) console.log("Error! REG_Y = " + REG_Y + details); if ((REG_P < 0) || (REG_P > 0xff)) console.log("Error! REG_P = " + REG_P + details); */ INSTRUCTION_POINTERS[opcode](); if ((REG_PC == 0) || (!CODE_RUNNING)) { CODE_RUNNING = false; CALLBACK(self.E_HALT); } }; // Get PC. this.getPC = function() { return REG_PC; }; // Pause CPU. this.pause = function() { if (CODE_RUNNING) { console.log("Pausing CPU."); CODE_RUNNING = false; } }; // Reset CPU. this.reset = function() { REG_A = 0; REG_X = 0; REG_Y = 0; REG_P = 0; REG_PC = MEMORY.readWord(0xfffc); REG_SP = 0x100; }; // Run until we die. this.run = function() { if (!CODE_RUNNING) { console.log("Starting CPU."); CODE_RUNNING = true; setTimeout(runBlock, 0); } }; // Are we running? this.running = function() { return CODE_RUNNING; }; // Serialize CPU state. this.serialize = function() { var state = { A: REG_A, X: REG_X, Y: REG_Y, P: REG_P, PC: REG_PC, SP: REG_SP, R: CODE_RUNNING }; return state; }; // Set PC. this.setPC = function(newPC) { REG_PC = newPC; }; }