v6502/vm/computer/cpu6502.js

1733 lines
41 KiB
JavaScript

/*
* 6502 Based Virtual Computer
* Copyright (C) 2011 Scott C. Duensing <scott@jaegertech.com>
*
* 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;
};
}