From fc536fdb2098197b44bc330a7367c33363ea52b9 Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Sat, 5 Aug 2023 19:43:03 -0700 Subject: [PATCH] Convert cpu6502 to Typescript --- js/cpu6502.js | 1626 ----------------------- js/cpu6502.ts | 3417 +++++++++++++++++++++++++++++++++++++++++++++++++ js/types.ts | 25 + 3 files changed, 3442 insertions(+), 1626 deletions(-) delete mode 100644 js/cpu6502.js create mode 100644 js/cpu6502.ts diff --git a/js/cpu6502.js b/js/cpu6502.js deleted file mode 100644 index a2dc513..0000000 --- a/js/cpu6502.js +++ /dev/null @@ -1,1626 +0,0 @@ -/* - * Copyright 2010-2019 Will Scullin - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that - * copyright notice and this permission notice appear in supporting - * documentation. No representations are made about the suitability of this - * software for any purpose. It is provided "as is" without express or - * implied warranty. - */ - -import { debug, toHex } from './util'; - -export default function CPU6502(options) -{ - options = options || {}; - - var is65C02 = options['65C02'] ? true : false; - - /* Registers */ - var pc = 0, // Program Counter - sr = 0x20, // Process Status Register - ar = 0, // Accumulator - xr = 0, // X Register - yr = 0, // Y Register - sp = 0xff; // Stack Pointer - - /* Addressing Mode */ - var modes = { - accumulator: 0, // A (Accumulator) - implied: 1, // Implied - immediate: 2, // # Immediate - absolute: 3, // a Absolute - zeroPage: 4, // zp Zero Page - relative: 5, // r Relative - - absoluteX: 6, // a,X Absolute, X - absoluteY: 7, // a,Y Absolute, Y - zeroPageX: 8, // zp,X Zero Page, X - zeroPageY: 9, // zp,Y Zero Page, Y - - absoluteIndirect: 10, // (a) Indirect - zeroPageXIndirect: 11, // (zp,X) Zero Page Indexed Indirect - zeroPageIndirectY: 12, // (zp),Y Zero Page Indexed with Y - - /* 65c02 */ - zeroPageIndirect: 13, // (zp), - absoluteXIndirect: 14, // (a, X), - zeroPage_relative: 15 // zp, Relative - }; - - var sizes = { - 0 /* modes.accumulator */: 1, - 1 /* modes.implied */: 1, - 2 /* modes.immediate */: 2, - 3 /* modes.absolute */: 3, - 4 /* modes.zeroPage */: 2, - 5 /* modes.relative */: 2, - 6 /* modes.absoluteX */: 3, - 7 /* modes.absoluteY */: 3, - 8 /* modes.zeroPageX */: 2, - 9 /* modes.zeroPageY */: 2, - 10 /* modes.indirect */: 3, - 11 /* modes.zeroPageXIndirect */: 2, - 12 /* modes.zeroPageYIndirect */: 2, - - 13 /* mode.zeroPageIndirect */: 2, - 14 /* mode.absoluteXIndirect */: 3, - 15 /* mode.zeroPage_relative */: 3 - }; - - /* Flags */ - var flags = { - N: 0x80, // Negative - V: 0x40, // oVerflow - B: 0x10, // Break - D: 0x08, // Decimal - I: 0x04, // Interrupt - Z: 0x02, // Zero - C: 0x01 // Carry - }; - - /* Memory Locations */ - var loc = { - STACK: 0x100, - NMI: 0xFFFA, - RESET: 0xFFFC, - BRK: 0xFFFE - }; - - var idx; - - var readPages = []; - var writePages = []; - var resetHandlers = []; - var cycles = 0; - var sync = false; - - var blankPage = { - read: function() { return 0; }, - write: function() {} - }; - - for (idx = 0; idx < 0x100; idx++) { - readPages[idx] = blankPage; - writePages[idx] = blankPage; - } - - function setFlag(f, on) { - sr = on ? (sr | f) : (sr & ~f); - } - - function testNZ(val) { - sr = val === 0 ? (sr | flags.Z) : (sr & ~flags.Z); - sr = (val & 0x80) ? (sr | flags.N) : (sr & ~flags.N); - - return val; - } - - function testZ(val) { - sr = val === 0 ? (sr | flags.Z) : (sr & ~flags.Z); - - return val; - } - - function add(a, b, sub) { - if (sub) - b ^= 0xff; - - // KEGS - var c, v; - if ((sr & flags.D) !== 0) { - c = (a & 0x0f) + (b & 0x0f) + (sr & flags.C); - if (sub) { - if (c < 0x10) - c = (c - 0x06) & 0x0f; - c += (a & 0xf0) + (b & 0xf0); - v = (c >> 1) ^ c; - if (c < 0x100) - c = (c + 0xa0) & 0xff; - } else { - if (c > 0x09) - c = (c - 0x0a) | 0x10; // carry to MSN - c += (a & 0xf0) + (b & 0xf0); - v = (c >> 1) ^ c; - if (c > 0x99) - c += 0x60; - } - } else { - c = a + b + (sr & flags.C); - v = (c ^ a) & 0x80; - } - - if (((a ^ b) & 0x80) !== 0) { - v = 0; - } - - setFlag(flags.C, c > 0xff); - setFlag(flags.V, v); - - return testNZ(c & 0xff); - } - - function increment(a) { - return testNZ((a + 0x01) & 0xff); - } - - function decrement(a) { - return testNZ((a + 0xff) & 0xff); - } - - function readBytePC(dbg) { - var addr = (pc++) & 0xffff, - page = addr >> 8, - off = addr & 0xff; - - var result = readPages[page].read(page, off, dbg); - - if (!dbg) { - cycles++; - } - - return result; - } - - function readByte(addr, dbg) { - var page = addr >> 8, - off = addr & 0xff; - - var result = readPages[page].read(page, off, dbg); - - if (!dbg) { - cycles++; - } - - return result; - } - - function writeByte(addr, val) { - var page = addr >> 8, - off = addr & 0xff; - - writePages[page].write(page, off, val); - - cycles++; - } - - function readWord(addr, dbg) { - return readByte(addr, dbg) | (readByte(addr + 1, dbg) << 8); - } - - function readWordPC(dbg) { - return readBytePC(dbg) | (readBytePC(dbg) << 8); - } - - function readZPWord(addr, dbg) { - var lsb, msb; - - lsb = readByte(addr & 0xff, dbg); - msb = readByte((addr + 1) & 0xff, dbg); - - return (msb << 8) | lsb; - } - - function pushByte(val) { - writeByte(loc.STACK | sp, val); - sp = (sp + 0xff) & 0xff; - } - - function pushWord(val) { - pushByte(val >> 8); - pushByte(val & 0xff); - } - - function pullByte() { - sp = (sp + 0x01) & 0xff; - return readByte(loc.STACK | sp); - } - - function pullWordRaw() { - var lsb = pullByte(loc.STACK | sp); - var msb = pullByte(loc.STACK | sp); - - return (msb << 8) | lsb; - } - - /* - * Read functions - */ - - function readImplied() { - } - - // #$00 - function readImmediate() { - return readBytePC(); - } - - // $0000 - function readAbsolute() { - return readByte(readWordPC()); - } - - // $00 - function readZeroPage() { - return readByte(readBytePC()); - } - - // $0000,X - function readAbsoluteX() { - var addr = readWordPC(); - var oldPage = addr >> 8; - addr = (addr + xr) & 0xffff; - var newPage = addr >> 8; - if (newPage != oldPage) { - var off = addr & 0xff; - readByte(oldPage << 8 | off); - } - return readByte(addr); - } - - // $0000,Y - function readAbsoluteY() { - var addr = readWordPC(); - var oldPage = addr >> 8; - addr = (addr + yr) & 0xffff; - var newPage = addr >> 8; - if (newPage != oldPage) { - var off = addr & 0xff; - readByte(oldPage << 8 | off); - } - return readByte(addr); - } - - // $00,X - function readZeroPageX() { - var zpAddr = readBytePC(); - readByte(zpAddr); - return readByte((zpAddr + xr) & 0xff); - } - - // $00,Y - function readZeroPageY() { - var zpAddr = readBytePC(); - readByte(zpAddr); - return readByte((zpAddr + yr) & 0xff); - } - - // ($00,X) - function readZeroPageXIndirect() { - var zpAddr = readBytePC(); - readByte(zpAddr); - var addr = readZPWord((zpAddr + xr) & 0xff); - return readByte(addr); - } - - // ($00),Y - function readZeroPageIndirectY() { - var addr = readZPWord(readBytePC()); - var oldPage = addr >> 8; - addr = (addr + yr) & 0xffff; - var newPage = addr >> 8; - if (newPage != oldPage) { - var off = addr & 0xff; - readByte(oldPage << 8 | off); - } - return readByte(addr); - } - - // ($00) (65C02) - function readZeroPageIndirect() { - return readByte(readZPWord(readBytePC())); - } - - /* - * Write Functions - */ - - // $0000 - function writeAbsolute(val) { - writeByte(readWordPC(), val); - } - - // $00 - function writeZeroPage(val) { - writeByte(readBytePC(), val); - } - - // $0000,X - function writeAbsoluteX(val) { - var addr = readWordPC(), oldPage = addr >> 8; - addr = (addr + xr) & 0xffff; - var off = addr & 0xff; - readByte(oldPage << 8 | off); - writeByte(addr, val); - } - - // $0000,Y - function writeAbsoluteY(val) { - var addr = readWordPC(), oldPage = addr >> 8; - addr = (addr + yr) & 0xffff; - var off = addr & 0xff; - readByte(oldPage << 8 | off); - writeByte(addr, val); - } - - // $00,X - function writeZeroPageX(val) { - var zpAddr = readBytePC(); - readByte(zpAddr); - writeByte((zpAddr + xr) & 0xff, val); - } - - // $00,Y - function writeZeroPageY(val) { - var zpAddr = readBytePC(); - readByte(zpAddr); - writeByte((zpAddr + yr) & 0xff, val); - } - - // ($00,X) - function writeZeroPageXIndirect(val) { - var zpAddr = readBytePC(); - readByte(zpAddr); - var addr = readZPWord((zpAddr + xr) & 0xff); - writeByte(addr, val); - } - - // ($00),Y - function writeZeroPageIndirectY(val) { - var addr = readZPWord(readBytePC()), oldPage = addr >> 8; - addr = (addr + yr) & 0xffff; - var off = addr & 0xff; - readByte(oldPage << 8 | off); - writeByte(addr, val); - } - - // ($00) (65C02) - function writeZeroPageIndirect(val) { - writeByte(readZPWord(readBytePC()), val); - } - - // $00 - function readAddrZeroPage() { - return readBytePC(); - } - - // $00,X - function readAddrZeroPageX() { - var zpAddr = readBytePC(); - readByte(zpAddr); - return (zpAddr + xr) & 0xff; - } - - // $0000 (65C02) - function readAddrAbsolute() { - return readWordPC(); - } - - // ($0000) (6502) - function readAddrAbsoluteIndirectBug() { - var addr = readWordPC(); - var page = addr & 0xff00; - var off = addr & 0x00ff; - var lsb = readByte(addr); - var msb = readByte(page | ((off + 0x01) & 0xff)); - return msb << 8 | lsb; - } - - // ($0000) (65C02) - function readAddrAbsoluteIndirect() { - var lsb = readBytePC(); - var msb = readBytePC(); - readByte(pc); - return readWord(msb << 8 | lsb); - } - - // $0000,X - function readAddrAbsoluteX(opts) { - var addr = readWordPC(); - if (!is65C02 || (opts && opts.rwm)) { - readByte(addr); - } else { - readByte(pc); - } - return (addr + xr) & 0xffff; - } - - // $(0000,X) (65C02) - function readAddrAbsoluteXIndirect() { - var address = readWordPC(); - readByte(pc); - return readWord((address + xr) & 0xffff); - } - - /* Break */ - function brk(readFn) { - readFn(); - pushWord(pc); - pushByte(sr | flags.B); - if (is65C02) { - setFlag(flags.D, false); - } - setFlag(flags.I, true); - pc = readWord(loc.BRK); - } - - /* Load Accumulator */ - function lda(readFn) { - ar = testNZ(readFn()); - } - - /* Load X Register */ - function ldx(readFn) { - xr = testNZ(readFn()); - } - - /* Load Y Register */ - function ldy(readFn) { - yr = testNZ(readFn()); - } - - /* Store Accumulator */ - function sta(writeFn) { - writeFn(ar); - } - - /* Store X Register */ - function stx(writeFn) { - writeFn(xr); - } - - /* Store Y Register */ - function sty(writeFn) { - writeFn(yr); - } - - /* Store Zero */ - function stz(writeFn) { - writeFn(0); - } - - /* Add with Carry */ - function adc(readFn) { - ar = add(ar, readFn(), false); - } - - /* Subtract with Carry */ - function sbc(readFn) { - ar = add(ar, readFn(), true); - } - - /* Increment Memory */ - function incA() { - readByte(pc); - ar = increment(ar); - } - - function inc(readAddrFn) { - var addr = readAddrFn({rwm: true}); - var oldVal = readByte(addr); - writeByte(addr, oldVal); - var val = increment(oldVal); - writeByte(addr, val); - } - - /* Increment X */ - function inx() { - readByte(pc); - xr = increment(xr); - } - - /* Increment Y */ - function iny() { - readByte(pc); - yr = increment(yr); - } - - /* Decrement Memory */ - function decA() { - readByte(pc); - ar = decrement(ar); - } - - function dec(readAddrFn) { - var addr = readAddrFn({rwm: true}); - var oldVal = readByte(addr); - writeByte(addr, oldVal); - var val = decrement(oldVal); - writeByte(addr, val); - } - - /* Decrement X */ - function dex() { - readByte(pc); - xr = decrement(xr); - } - - /* Decrement Y */ - function dey() { - readByte(pc); - yr = decrement(yr); - } - - function shiftLeft(val) { - setFlag(flags.C, val & 0x80); - return testNZ((val << 1) & 0xff); - } - - /* Arithmetic Shift Left */ - function aslA() { - readByte(pc); - ar = shiftLeft(ar); - } - - function asl(readAddrFn) { - var addr = readAddrFn({rwm: true}); - var oldVal = readByte(addr); - writeByte(addr, oldVal); - var val = shiftLeft(oldVal); - writeByte(addr, val); - } - - function shiftRight(val) { - setFlag(flags.C, val & 0x01); - return testNZ(val >> 1); - } - - /* Logical Shift Right */ - function lsrA() { - readByte(pc); - ar = shiftRight(ar); - } - - function lsr(readAddrFn) { - var addr = readAddrFn({rwm: true}); - var oldVal = readByte(addr); - writeByte(addr, oldVal); - var val = shiftRight(oldVal); - writeByte(addr, val); - } - - function rotateLeft(val) { - var c = (sr & flags.C); - setFlag(flags.C, val & 0x80); - return testNZ(((val << 1) | (c ? 0x01 : 0x00)) & 0xff); - } - - /* Rotate Left */ - function rolA() { - readByte(pc); - ar = rotateLeft(ar); - } - - function rol(readAddrFn) { - var addr = readAddrFn({rwm: true}); - var oldVal = readByte(addr); - writeByte(addr, oldVal); - var val = rotateLeft(oldVal); - writeByte(addr, val); - } - - function rotateRight(a) { - var c = (sr & flags.C); - setFlag(flags.C, a & 0x01); - return testNZ((a >> 1) | (c ? 0x80 : 0x00)); - } - - /* Rotate Right */ - function rorA() { - readByte(pc); - ar = rotateRight(ar); - } - - function ror(readAddrFn) { - var addr = readAddrFn({rwm: true}); - var oldVal = readByte(addr); - writeByte(addr, oldVal); - var val = rotateRight(oldVal); - writeByte(addr, val); - } - - /* Logical And Accumulator */ - function and(readFn) { - ar = testNZ(ar & readFn()); - } - - /* Logical Or Accumulator */ - function ora(readFn) { - ar = testNZ(ar | readFn()); - } - - /* Logical Exclusive Or Accumulator */ - function eor(readFn) { - ar = testNZ(ar ^ readFn()); - } - - /* Reset Bit */ - - function rmb(b) { - var bit = (0x1 << b) ^ 0xFF; - var addr = readBytePC(); - var val = readByte(addr); - readByte(addr); - val &= bit; - writeByte(addr, val); - } - - /* Set Bit */ - - function smb(b) { - var bit = 0x1 << b; - var addr = readBytePC(); - var val = readByte(addr); - readByte(addr); - val |= bit; - writeByte(addr, val); - } - - /* Test and Reset Bits */ - function trb(readAddrFn) { - var addr = readAddrFn(); - var val = readByte(addr); - testZ(val & ar); - readByte(addr); - writeByte(addr, val & ~ar); - } - - /* Test and Set Bits */ - function tsb(readAddrFn) { - var addr = readAddrFn(); - var val = readByte(addr); - testZ(val & ar); - readByte(addr); - writeByte(addr, val | ar); - } - - /* Bit */ - function bit(readFn) { - var val = readFn(); - setFlag(flags.Z, (val & ar) === 0); - setFlag(flags.N, val & 0x80); - setFlag(flags.V, val & 0x40); - } - - /* Bit Immediate*/ - function bitI(readFn) { - var val = readFn(); - setFlag(flags.Z, (val & ar) === 0); - } - - function compare(a, b) - { - b = (b ^ 0xff); - var c = a + b + 1; - setFlag(flags.C, c > 0xff); - testNZ(c & 0xff); - } - - function cmp(readFn) { - compare(ar, readFn()); - } - - function cpx(readFn) { - compare(xr, readFn()); - } - - function cpy(readFn) { - compare(yr, readFn()); - } - - /* Branches */ - function brs(f) { - var off = readBytePC(); // changes pc - if ((f & sr) !== 0) { - readByte(pc); - var oldPage = pc >> 8; - pc += off > 127 ? off - 256 : off; - var newPage = pc >> 8; - var newOff = pc & 0xff; - if (newPage != oldPage) readByte(oldPage << 8 | newOff); - } - } - - function brc(f) { - var off = readBytePC(); // changes pc - if ((f & sr) === 0) { - readByte(pc); - var oldPage = pc >> 8; - pc += off > 127 ? off - 256 : off; - var newPage = pc >> 8; - var newOff = pc & 0xff; - if (newPage != oldPage) readByte(oldPage << 8 | newOff); - } - } - - /* WDC 65C02 branches */ - - function bbr(b) { - var zpAddr = readBytePC(); - var val = readByte(zpAddr); - readByte(zpAddr); - var off = readBytePC(); // changes pc - - if (((1 << b) & val) === 0) { - var oldPc = pc; - var oldPage = oldPc >> 8; - readByte(oldPc); - pc += off > 127 ? off - 256 : off; - var newPage = pc >> 8; - if (oldPage != newPage) { - readByte(oldPc); - } - } - } - - function bbs(b) { - var zpAddr = readBytePC(); - var val = readByte(zpAddr); - readByte(zpAddr); - var off = readBytePC(); // changes pc - - if (((1 << b) & val) !== 0) { - var oldPc = pc; - var oldPage = oldPc >> 8; - readByte(oldPc); - pc += off > 127 ? off - 256 : off; - var newPage = pc >> 8; - if (oldPage != newPage) { - readByte(oldPc); - } - } - } - - /* Transfers and stack */ - function tax() { readByte(pc); testNZ(xr = ar); } - - function txa() { readByte(pc); testNZ(ar = xr); } - - function tay() { readByte(pc); testNZ(yr = ar); } - - function tya() { readByte(pc); testNZ(ar = yr); } - - function tsx() { readByte(pc); testNZ(xr = sp); } - - function txs() { readByte(pc); sp = xr; } - - function pha() { readByte(pc); pushByte(ar); } - - function pla() { readByte(pc); readByte(0x0100 | sp); testNZ(ar = pullByte()); } - - function phx() { readByte(pc); pushByte(xr); } - - function plx() { readByte(pc); readByte(0x0100 | sp);testNZ(xr = pullByte()); } - - function phy() { readByte(pc); pushByte(yr); } - - function ply() { readByte(pc); readByte(0x0100 | sp); testNZ(yr = pullByte()); } - - function php() { readByte(pc); pushByte(sr | flags.B); } - - function plp() { readByte(pc); readByte(0x0100 | sp); sr = (pullByte() & ~flags.B) | 0x20; } - - /* Jump */ - function jmp(readAddrFn) { - pc = readAddrFn(); - } - - /* Jump Subroutine */ - function jsr() { - var lsb = readBytePC(); - readByte(0x0100 | sp); - pushWord(pc); - var msb = readBytePC(); - pc = (msb << 8 | lsb) & 0xffff; - } - - /* Return from Subroutine */ - function rts() { - readByte(pc); - readByte(0x0100 | sp); - var addr = pullWordRaw(); - readByte(addr); - pc = (addr + 1) & 0xffff; - } - - /* Return from Subroutine */ - function rti() { - readByte(pc); - readByte(0x0100 | sp); - sr = pullByte() & ~flags.B; - pc = pullWordRaw(); - } - - /* Set and Clear */ - function set(flag) { - readByte(pc); - sr |= flag; - } - - function clr(flag) { - readByte(pc); - sr &= ~flag; - } - - /* No-Op */ - function nop(readAddrFn) { - readByte(pc); - readAddrFn(); - } - - var ops = { - // LDA - 0xa9: ['LDA', lda, readImmediate, modes.immediate, 2], - 0xa5: ['LDA', lda, readZeroPage, modes.zeroPage, 3], - 0xb5: ['LDA', lda, readZeroPageX, modes.zeroPageX, 4], - 0xad: ['LDA', lda, readAbsolute, modes.absolute, 4], - 0xbd: ['LDA', lda, readAbsoluteX, modes.absoluteX, 4], - 0xb9: ['LDA', lda, readAbsoluteY, modes.absoluteY, 4], - 0xa1: ['LDA', lda, readZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0xb1: ['LDA', lda, readZeroPageIndirectY, modes.zeroPageIndirectY, 5], - - // LDX - 0xa2: ['LDX', ldx, readImmediate, modes.immediate, 2], - 0xa6: ['LDX', ldx, readZeroPage, modes.zeroPage, 3], - 0xb6: ['LDX', ldx, readZeroPageY, modes.zeroPageY, 4], - 0xae: ['LDX', ldx, readAbsolute, modes.absolute, 4], - 0xbe: ['LDX', ldx, readAbsoluteY, modes.absoluteY, 4], - - // LDY - 0xa0: ['LDY', ldy, readImmediate, modes.immediate, 2], - 0xa4: ['LDY', ldy, readZeroPage, modes.zeroPage, 3], - 0xb4: ['LDY', ldy, readZeroPageX, modes.zeroPageX, 4], - 0xac: ['LDY', ldy, readAbsolute, modes.absolute, 4], - 0xbc: ['LDY', ldy, readAbsoluteX, modes.absoluteX, 4], - - // STA - 0x85: ['STA', sta, writeZeroPage, modes.zeroPage, 3], - 0x95: ['STA', sta, writeZeroPageX, modes.zeroPageX, 4], - 0x8d: ['STA', sta, writeAbsolute, modes.absolute, 4], - 0x9d: ['STA', sta, writeAbsoluteX, modes.absoluteX, 5], - 0x99: ['STA', sta, writeAbsoluteY, modes.absoluteY, 5], - 0x81: ['STA', sta, writeZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0x91: ['STA', sta, writeZeroPageIndirectY, modes.zeroPageIndirectY, 6], - - // STX - 0x86: ['STX', stx, writeZeroPage, modes.zeroPage, 3], - 0x96: ['STX', stx, writeZeroPageY, modes.zeroPageY, 4], - 0x8e: ['STX', stx, writeAbsolute, modes.absolute, 4], - - // STY - 0x84: ['STY', sty, writeZeroPage, modes.zeroPage, 3], - 0x94: ['STY', sty, writeZeroPageX, modes.zeroPageX, 4], - 0x8c: ['STY', sty, writeAbsolute, modes.absolute, 4], - - // ADC - 0x69: ['ADC', adc, readImmediate, modes.immediate, 2], - 0x65: ['ADC', adc, readZeroPage, modes.zeroPage, 3], - 0x75: ['ADC', adc, readZeroPageX, modes.zeroPageX, 4], - 0x6D: ['ADC', adc, readAbsolute, modes.absolute, 4], - 0x7D: ['ADC', adc, readAbsoluteX, modes.absoluteX, 4], - 0x79: ['ADC', adc, readAbsoluteY, modes.absoluteY, 4], - 0x61: ['ADC', adc, readZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0x71: ['ADC', adc, readZeroPageIndirectY, modes.zeroPageIndirectY, 5], - - // SBC - 0xe9: ['SBC', sbc, readImmediate, modes.immediate, 2], - 0xe5: ['SBC', sbc, readZeroPage, modes.zeroPage, 3], - 0xf5: ['SBC', sbc, readZeroPageX, modes.zeroPageX, 4], - 0xeD: ['SBC', sbc, readAbsolute, modes.absolute, 4], - 0xfD: ['SBC', sbc, readAbsoluteX, modes.absoluteX, 4], - 0xf9: ['SBC', sbc, readAbsoluteY, modes.absoluteY, 4], - 0xe1: ['SBC', sbc, readZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0xf1: ['SBC', sbc, readZeroPageIndirectY, modes.zeroPageIndirectY, 5], - - // INC - 0xe6: ['INC', inc, readAddrZeroPage, modes.zeroPage, 5], - 0xf6: ['INC', inc, readAddrZeroPageX, modes.zeroPageX, 6], - 0xee: ['INC', inc, readAddrAbsolute, modes.absolute, 6], - 0xfe: ['INC', inc, readAddrAbsoluteX, modes.absoluteX, 7], - - // INX - 0xe8: ['INX', inx, null, modes.implied, 2], - - // INY - 0xc8: ['INY', iny, null, modes.implied, 2], - - // DEC - 0xc6: ['DEC', dec, readAddrZeroPage, modes.zeroPage, 5], - 0xd6: ['DEC', dec, readAddrZeroPageX, modes.zeroPageX, 6], - 0xce: ['DEC', dec, readAddrAbsolute, modes.absolute, 6], - 0xde: ['DEC', dec, readAddrAbsoluteX, modes.absoluteX, 7], - - // DEX - 0xca: ['DEX', dex, null, modes.implied, 2], - - // DEY - 0x88: ['DEY', dey, null, modes.implied, 2], - - // ASL - 0x0A: ['ASL', aslA, null, modes.accumulator, 2], - 0x06: ['ASL', asl, readAddrZeroPage, modes.zeroPage, 5], - 0x16: ['ASL', asl, readAddrZeroPageX, modes.zeroPageX, 6], - 0x0E: ['ASL', asl, readAddrAbsolute, modes.absolute, 6], - 0x1E: ['ASL', asl, readAddrAbsoluteX, modes.absoluteX, 7], - - // LSR - 0x4A: ['LSR', lsrA, null, modes.accumulator, 2], - 0x46: ['LSR', lsr, readAddrZeroPage, modes.zeroPage, 5], - 0x56: ['LSR', lsr, readAddrZeroPageX, modes.zeroPageX, 6], - 0x4E: ['LSR', lsr, readAddrAbsolute, modes.absolute, 6], - 0x5E: ['LSR', lsr, readAddrAbsoluteX, modes.absoluteX, 7], - - // ROL - 0x2A: ['ROL', rolA, null, modes.accumulator, 2], - 0x26: ['ROL', rol, readAddrZeroPage, modes.zeroPage, 5], - 0x36: ['ROL', rol, readAddrZeroPageX, modes.zeroPageX, 6], - 0x2E: ['ROL', rol, readAddrAbsolute, modes.absolute, 6], - 0x3E: ['ROL', rol, readAddrAbsoluteX, modes.absoluteX, 7], - - // ROR - 0x6A: ['ROR', rorA, null, modes.accumulator, 2], - 0x66: ['ROR', ror, readAddrZeroPage, modes.zeroPage, 5], - 0x76: ['ROR', ror, readAddrZeroPageX, modes.zeroPageX, 6], - 0x6E: ['ROR', ror, readAddrAbsolute, modes.absolute, 6], - 0x7E: ['ROR', ror, readAddrAbsoluteX, modes.absoluteX, 7], - - // AND - 0x29: ['AND', and, readImmediate, modes.immediate, 2], - 0x25: ['AND', and, readZeroPage, modes.zeroPage, 3], - 0x35: ['AND', and, readZeroPageX, modes.zeroPageX, 4], - 0x2D: ['AND', and, readAbsolute, modes.absolute, 4], - 0x3D: ['AND', and, readAbsoluteX, modes.absoluteX, 4], - 0x39: ['AND', and, readAbsoluteY, modes.absoluteY, 4], - 0x21: ['AND', and, readZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0x31: ['AND', and, readZeroPageIndirectY, modes.zeroPageIndirectY, 5], - - // ORA - 0x09: ['ORA', ora, readImmediate, modes.immediate, 2], - 0x05: ['ORA', ora, readZeroPage, modes.zeroPage, 3], - 0x15: ['ORA', ora, readZeroPageX, modes.zeroPageX, 4], - 0x0D: ['ORA', ora, readAbsolute, modes.absolute, 4], - 0x1D: ['ORA', ora, readAbsoluteX, modes.absoluteX, 4], - 0x19: ['ORA', ora, readAbsoluteY, modes.absoluteY, 4], - 0x01: ['ORA', ora, readZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0x11: ['ORA', ora, readZeroPageIndirectY, modes.zeroPageIndirectY, 5], - - // EOR - 0x49: ['EOR', eor, readImmediate, modes.immediate, 2], - 0x45: ['EOR', eor, readZeroPage, modes.zeroPage, 3], - 0x55: ['EOR', eor, readZeroPageX, modes.zeroPageX, 4], - 0x4D: ['EOR', eor, readAbsolute, modes.absolute, 4], - 0x5D: ['EOR', eor, readAbsoluteX, modes.absoluteX, 4], - 0x59: ['EOR', eor, readAbsoluteY, modes.absoluteY, 4], - 0x41: ['EOR', eor, readZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0x51: ['EOR', eor, readZeroPageIndirectY, modes.zeroPageIndirectY, 5], - - // CMP - 0xc9: ['CMP', cmp, readImmediate, modes.immediate, 2], - 0xc5: ['CMP', cmp, readZeroPage, modes.zeroPage, 3], - 0xd5: ['CMP', cmp, readZeroPageX, modes.zeroPageX, 4], - 0xcD: ['CMP', cmp, readAbsolute, modes.absolute, 4], - 0xdD: ['CMP', cmp, readAbsoluteX, modes.absoluteX, 4], - 0xd9: ['CMP', cmp, readAbsoluteY, modes.absoluteY, 4], - 0xc1: ['CMP', cmp, readZeroPageXIndirect, modes.zeroPageXIndirect, 6], - 0xd1: ['CMP', cmp, readZeroPageIndirectY, modes.zeroPageIndirectY, 5], - - // CPX - 0xE0: ['CPX', cpx, readImmediate, modes.immediate, 2], - 0xE4: ['CPX', cpx, readZeroPage, modes.zeroPage, 3], - 0xEC: ['CPX', cpx, readAbsolute, modes.absolute, 4], - - // CPY - 0xC0: ['CPY', cpy, readImmediate, modes.immediate, 2], - 0xC4: ['CPY', cpy, readZeroPage, modes.zeroPage, 3], - 0xCC: ['CPY', cpy, readAbsolute, modes.absolute, 4], - - // BIT - 0x24: ['BIT', bit, readZeroPage, modes.zeroPage, 3], - 0x2C: ['BIT', bit, readAbsolute, modes.absolute, 4], - - // BCC - 0x90: ['BCC', brc, flags.C, modes.relative, 2], - - // BCS - 0xB0: ['BCS', brs, flags.C, modes.relative, 2], - - // BEQ - 0xF0: ['BEQ', brs, flags.Z, modes.relative, 2], - - // BMI - 0x30: ['BMI', brs, flags.N, modes.relative, 2], - - // BNE - 0xD0: ['BNE', brc, flags.Z, modes.relative, 2], - - // BPL - 0x10: ['BPL', brc, flags.N, modes.relative, 2], - - // BVC - 0x50: ['BVC', brc, flags.V, modes.relative, 2], - - // BVS - 0x70: ['BVS', brs, flags.V, modes.relative, 2], - - // TAX - 0xAA: ['TAX', tax, null, modes.implied, 2], - - // TXA - 0x8A: ['TXA', txa, null, modes.implied, 2], - - // TAY - 0xA8: ['TAY', tay, null, modes.implied, 2], - - // TYA - 0x98: ['TYA', tya, null, modes.implied, 2], - - // TSX - 0xBA: ['TSX', tsx, null, modes.implied, 2], - - // TXS - 0x9A: ['TXS', txs, null, modes.implied, 2], - - // PHA - 0x48: ['PHA', pha, null, modes.implied, 3], - - // PLA - 0x68: ['PLA', pla, null, modes.implied, 4], - - // PHP - 0x08: ['PHP', php, null, modes.implied, 3], - - // PLP - 0x28: ['PLP', plp, null, modes.implied, 4], - - // JMP - 0x4C: [ - 'JMP', jmp, readAddrAbsolute, modes.absolute, 3 - ], - 0x6C: [ - 'JMP', jmp, readAddrAbsoluteIndirectBug, modes.absoluteIndirect, 5 - ], - // JSR - 0x20: ['JSR', jsr, readAddrAbsolute, modes.absolute, 6], - - // RTS - 0x60: ['RTS', rts, null, modes.implied, 6], - - // RTI - 0x40: ['RTI', rti, null, modes.implied, 6], - - // SEC - 0x38: ['SEC', set, flags.C, modes.implied, 2], - - // SED - 0xF8: ['SED', set, flags.D, modes.implied, 2], - - // SEI - 0x78: ['SEI', set, flags.I, modes.implied, 2], - - // CLC - 0x18: ['CLC', clr, flags.C, modes.implied, 2], - - // CLD - 0xD8: ['CLD', clr, flags.D, modes.implied, 2], - - // CLI - 0x58: ['CLI', clr, flags.I, modes.implied, 2], - - // CLV - 0xB8: ['CLV', clr, flags.V, modes.implied, 2], - - // NOP - 0xea: ['NOP', nop, readImplied, modes.implied, 2], - - // BRK - 0x00: ['BRK', brk, readImmediate, modes.immediate, 7] - }; - - /* 65C02 Instructions */ - - var cops = { - // INC / DEC A - 0x1A: ['INC', incA, null, modes.accumulator, 2], - 0x3A: ['DEC', decA, null, modes.accumulator, 2], - - // Indirect Zero Page for the masses - 0x12: ['ORA', ora, readZeroPageIndirect, modes.zeroPageIndirect, 5], - 0x32: ['AND', and, readZeroPageIndirect, modes.zeroPageIndirect, 5], - 0x52: ['EOR', eor, readZeroPageIndirect, modes.zeroPageIndirect, 5], - 0x72: ['ADC', adc, readZeroPageIndirect, modes.zeroPageIndirect, 5], - 0x92: ['STA', sta, writeZeroPageIndirect, modes.zeroPageIndirect, 5], - 0xB2: ['LDA', lda, readZeroPageIndirect, modes.zeroPageIndirect, 5], - 0xD2: ['CMP', cmp, readZeroPageIndirect, modes.zeroPageIndirect, 5], - 0xF2: ['SBC', sbc, readZeroPageIndirect, modes.zeroPageIndirect, 5], - - // Better BIT - 0x34: ['BIT', bit, readZeroPageX, modes.zeroPageX, 4], - 0x3C: ['BIT', bit, readAbsoluteX, modes.absoluteX, 4], - 0x89: ['BIT', bitI, readImmediate, modes.immediate, 2], - - // JMP absolute indirect indexed - 0x6C: [ - 'JMP', jmp, readAddrAbsoluteIndirect, modes.absoluteIndirect, 6 - ], - 0x7C: [ - 'JMP', jmp, readAddrAbsoluteXIndirect, modes.absoluteXIndirect, 6 - ], - - // BBR/BBS - 0x0F: ['BBR0', bbr, 0, modes.zeroPage_relative, 5], - 0x1F: ['BBR1', bbr, 1, modes.zeroPage_relative, 5], - 0x2F: ['BBR2', bbr, 2, modes.zeroPage_relative, 5], - 0x3F: ['BBR3', bbr, 3, modes.zeroPage_relative, 5], - 0x4F: ['BBR4', bbr, 4, modes.zeroPage_relative, 5], - 0x5F: ['BBR5', bbr, 5, modes.zeroPage_relative, 5], - 0x6F: ['BBR6', bbr, 6, modes.zeroPage_relative, 5], - 0x7F: ['BBR7', bbr, 7, modes.zeroPage_relative, 5], - - 0x8F: ['BBS0', bbs, 0, modes.zeroPage_relative, 5], - 0x9F: ['BBS1', bbs, 1, modes.zeroPage_relative, 5], - 0xAF: ['BBS2', bbs, 2, modes.zeroPage_relative, 5], - 0xBF: ['BBS3', bbs, 3, modes.zeroPage_relative, 5], - 0xCF: ['BBS4', bbs, 4, modes.zeroPage_relative, 5], - 0xDF: ['BBS5', bbs, 5, modes.zeroPage_relative, 5], - 0xEF: ['BBS6', bbs, 6, modes.zeroPage_relative, 5], - 0xFF: ['BBS7', bbs, 7, modes.zeroPage_relative, 5], - - // BRA - 0x80: ['BRA', brc, 0, modes.relative, 2], - - // NOP - 0x02: ['NOP', nop, readImmediate, modes.immediate, 2], - 0x22: ['NOP', nop, readImmediate, modes.immediate, 2], - 0x42: ['NOP', nop, readImmediate, modes.immediate, 2], - 0x44: ['NOP', nop, readImmediate, modes.immediate, 3], - 0x54: ['NOP', nop, readImmediate, modes.immediate, 4], - 0x62: ['NOP', nop, readImmediate, modes.immediate, 2], - 0x82: ['NOP', nop, readImmediate, modes.immediate, 2], - 0xC2: ['NOP', nop, readImmediate, modes.immediate, 2], - 0xD4: ['NOP', nop, readImmediate, modes.immediate, 4], - 0xE2: ['NOP', nop, readImmediate, modes.immediate, 2], - 0xF4: ['NOP', nop, readImmediate, modes.immediate, 4], - 0x5C: ['NOP', nop, readAbsolute, modes.absolute, 8], - 0xDC: ['NOP', nop, readAbsolute, modes.absolute, 4], - 0xFC: ['NOP', nop, readAbsolute, modes.absolute, 4], - - // PHX - 0xDA: ['PHX', phx, null, modes.implied, 3], - - // PHY - 0x5A: ['PHY', phy, null, modes.implied, 3], - - // PLX - 0xFA: ['PLX', plx, null, modes.implied, 4], - - // PLY - 0x7A: ['PLY', ply, null, modes.implied, 4], - - // RMB/SMB - - 0x07: ['RMB0', rmb, 0, modes.zeroPage, 5], - 0x17: ['RMB1', rmb, 1, modes.zeroPage, 5], - 0x27: ['RMB2', rmb, 2, modes.zeroPage, 5], - 0x37: ['RMB3', rmb, 3, modes.zeroPage, 5], - 0x47: ['RMB4', rmb, 4, modes.zeroPage, 5], - 0x57: ['RMB5', rmb, 5, modes.zeroPage, 5], - 0x67: ['RMB6', rmb, 6, modes.zeroPage, 5], - 0x77: ['RMB7', rmb, 7, modes.zeroPage, 5], - - 0x87: ['SMB0', smb, 0, modes.zeroPage, 5], - 0x97: ['SMB1', smb, 1, modes.zeroPage, 5], - 0xA7: ['SMB2', smb, 2, modes.zeroPage, 5], - 0xB7: ['SMB3', smb, 3, modes.zeroPage, 5], - 0xC7: ['SMB4', smb, 4, modes.zeroPage, 5], - 0xD7: ['SMB5', smb, 5, modes.zeroPage, 5], - 0xE7: ['SMB6', smb, 6, modes.zeroPage, 5], - 0xF7: ['SMB7', smb, 7, modes.zeroPage, 5], - - // STZ - 0x64: ['STZ', stz, writeZeroPage, modes.zeroPage, 3], - 0x74: ['STZ', stz, writeZeroPageX, modes.zeroPageX, 4], - 0x9C: ['STZ', stz, writeAbsolute, modes.absolute, 4], - 0x9E: ['STZ', stz, writeAbsoluteX, modes.absoluteX, 5], - - // TRB - 0x14: ['TRB', trb, readAddrZeroPage, modes.zeroPage, 5], - 0x1C: ['TRB', trb, readAddrAbsolute, modes.absolute, 6], - - // TSB - 0x04: ['TSB', tsb, readAddrZeroPage, modes.zeroPage, 5], - 0x0C: ['TSB', tsb, readAddrAbsolute, modes.absolute, 6] - }; - - if (is65C02) { - for (var key in cops) { - ops[key] = cops[key]; - } - } - - function unknown(b) { - var unk; - - if (is65C02) { - unk = [ - 'NOP', - nop, - readImplied, - modes.implied, - 2 - ]; - } else { - unk = [ - '???', - function() { - debug('Unknown OpCode: ' + toHex(b) + - ' at ' + toHex(pc - 1, 4)); - }, - readImplied, - modes.implied, - 1 - ]; - } - ops[b] = unk; - return unk; - } - - /* Certain browsers benefit from using arrays over maps */ - var opary = []; - - for (idx = 0; idx < 0x100; idx++) { - opary[idx] = ops[idx] || unknown(idx); - } - - function dumpArgs(addr, m, symbols) { - var val; - var off; - function toHexOrSymbol(v, n) { - if (symbols && symbols[v]) { - return symbols[v]; - } else { - return '$' + toHex(v, n); - } - } - var result = ''; - switch (m) { - case modes.implied: - break; - case modes.immediate: - result = '#' + toHexOrSymbol(readByte(addr, true)); - break; - case modes.absolute: - result = '' + toHexOrSymbol(readWord(addr, true), 4); - break; - case modes.zeroPage: - result = '' + toHexOrSymbol(readByte(addr, true)); - break; - case modes.relative: - { - off = readByte(addr, true); - if (off > 127) { - off -= 256; - } - addr += off + 1; - result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')'; - } - break; - case modes.absoluteX: - result = '' + toHexOrSymbol(readWord(addr, true), 4) + ',X'; - break; - case modes.absoluteY: - result = '' + toHexOrSymbol(readWord(addr, true), 4) + ',Y'; - break; - case modes.zeroPageX: - result = '' + toHexOrSymbol(readByte(addr, true)) + ',X'; - break; - case modes.zeroPageY: - result = '' + toHexOrSymbol(readByte(addr, true)) + ',Y'; - break; - case modes.absoluteIndirect: - result = '(' + toHexOrSymbol(readWord(addr, true), 4) + ')'; - break; - case modes.zeroPageXIndirect: - result = '(' + toHexOrSymbol(readByte(addr, true)) + ',X)'; - break; - case modes.zeroPageIndirectY: - result = '(' + toHexOrSymbol(readByte(addr, true)) + '),Y'; - break; - case modes.accumulator: - result = 'A'; - break; - case modes.zeroPageIndirect: - result = '(' + toHexOrSymbol(readByte(addr, true)) + ')'; - break; - case modes.absoluteXIndirect: - result = '(' + toHexOrSymbol(readWord(addr, true), 4) + ',X)'; - break; - case modes.zeroPage_relative: - val = readByte(addr, true); - off = readByte(addr + 1, true); - if (off > 127) { - off -= 256; - } - addr += off + 2; - result = '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(addr, 4) + ' (' + off + ')'; - break; - default: - break; - } - return result; - } - - return { - step: function cpu_step(cb) { - sync = true; - var op = opary[readBytePC()]; - sync = false; - op[1](op[2]); - - if (cb) { - cb(this); - } - }, - - stepDebug: function(n, cb) { - var op, idx; - - for (idx = 0; idx < n; idx++) { - sync = true; - op = opary[readBytePC()]; - sync = false; - op[1](op[2]); - - if (cb) { - cb(this); - } - } - }, - - stepCycles: function(c) { - var op, end = cycles + c; - - while (cycles < end) { - sync = true; - op = opary[readBytePC()]; - sync = false; - op[1](op[2]); - } - }, - - stepCyclesDebug: function(c, cb) - { - var op, end = cycles + c; - - while (cycles < end) { - sync = true; - op = opary[readBytePC()]; - sync = false; - op[1](op[2]); - - if (cb) { - cb(this); - } - } - }, - - addPageHandler: function(pho) { - for (var idx = pho.start(); idx <= pho.end(); idx++) { - if (pho.read) - readPages[idx] = pho; - if (pho.write) - writePages[idx] = pho; - } - if (pho.reset) - resetHandlers.push(pho); - }, - - reset: function cpu_reset() - { - // cycles = 0; - sr = 0x20; - sp = 0xff; - ar = 0; - yr = 0; - xr = 0; - pc = readWord(loc.RESET); - - for (var idx = 0; idx < resetHandlers.length; idx++) { - resetHandlers[idx].reset(); - } - }, - - /* IRQ - Interrupt Request */ - irq: function cpu_irq() - { - if ((sr & flags.I) === 0) { - pushWord(pc); - pushByte(sr & ~flags.B); - if (is65C02) { - setFlag(flags.D, false); - } - setFlag(flags.I, true); - pc = readWord(loc.BRK); - } - }, - - /* NMI Non-maskable Interrupt */ - nmi: function cpu_nmi() - { - pushWord(pc); - pushByte(sr & ~flags.B); - if (is65C02) { - setFlag(flags.D, false); - } - setFlag(flags.I, true); - pc = readWord(loc.NMI); - }, - - getPC: function () { - return pc; - }, - - setPC: function(_pc) { - pc = _pc; - }, - - dumpPC: function(_pc, symbols) { - if (_pc === undefined) { - _pc = pc; - } - var b = readByte(_pc, true), - op = ops[b], - size = sizes[op[3]], - result = toHex(_pc, 4) + '- '; - - if (symbols) { - if (symbols[_pc]) { - result += symbols[_pc] + - ' '.substring(symbols[_pc].length); - } else { - result += ' '; - } - } - - for (var idx = 0; idx < 4; idx++) { - if (idx < size) { - result += toHex(readByte(_pc + idx, true)) + ' '; - } else { - result += ' '; - } - } - - if (op === undefined) - result += '??? (' + toHex(b) + ')'; - else - result += op[0] + ' ' + dumpArgs(_pc + 1, op[3], symbols); - - return result; - }, - - dumpPage: function(start, end) { - var result = ''; - if (start === undefined) { - start = pc >> 8; - } - if (end === undefined) { - end = start; - } - for (var page = start; page <= end; page++) { - var b, idx, jdx; - for (idx = 0; idx < 16; idx++) { - result += toHex(page) + toHex(idx << 4) + ': '; - for (jdx = 0; jdx < 16; jdx++) { - b = readByte(page * 256 + idx * 16 + jdx, true); - result += toHex(b) + ' '; - } - result += ' '; - for (jdx = 0; jdx < 16; jdx++) { - b = readByte(page * 256 + idx * 16 + jdx, true) & 0x7f; - if (b >= 0x20 && b < 0x7f) { - result += String.fromCharCode(b); - } else { - result += '.'; - } - } - result += '\n'; - } - } - return result; - }, - - list: function(_pc, symbols) { - if (_pc === undefined) { - _pc = pc; - } - var results = []; - for (var jdx = 0; jdx < 20; jdx++) { - var b = readByte(_pc), op = ops[b]; - results.push(this.dumpPC(_pc, symbols)); - _pc += sizes[op[3]]; - } - return results; - }, - - sync: function() { - return sync; - }, - - cycles: function() { - return cycles; - }, - - registers: function() { - return [pc,ar,xr,yr,sr,sp]; - }, - - getState: function() { - return { - a: ar, - x: xr, - y: yr, - s: sr, - pc: pc, - sp: sp, - cycles: cycles - }; - }, - - setState: function(state) { - ar = state.a; - xr = state.x; - yr = state.y; - sr = state.s; - pc = state.pc; - sp = state.sp; - cycles = state.cycles; - }, - - dumpRegisters: function() { - return toHex(pc, 4) + - '- A=' + toHex(ar) + - ' X=' + toHex(xr) + - ' Y=' + toHex(yr) + - ' P=' + toHex(sr) + - ' S=' + toHex(sp) + - ' ' + - ((sr & flags.N) ? 'N' : '-') + - ((sr & flags.V) ? 'V' : '-') + - '-' + - ((sr & flags.B) ? 'B' : '-') + - ((sr & flags.D) ? 'D' : '-') + - ((sr & flags.I) ? 'I' : '-') + - ((sr & flags.Z) ? 'Z' : '-') + - ((sr & flags.C) ? 'C' : '-'); - }, - - read: function(page, off) { - return readPages[page].read(page, off, false); - }, - - write: function(page, off, val) { - writePages[page].write(page, off, val); - } - }; -} diff --git a/js/cpu6502.ts b/js/cpu6502.ts new file mode 100644 index 0000000..e4fc9f7 --- /dev/null +++ b/js/cpu6502.ts @@ -0,0 +1,3417 @@ +import { Memory, MemberOf, MemoryPages, byte, word } from "./types"; +import { toHex } from "./util"; + +export const FLAVOR_6502 = "6502"; +export const FLAVOR_ROCKWELL_65C02 = "rockwell65c02"; +export const FLAVOR_WDC_65C02 = "wdc65c02"; + +export const FLAVORS_65C02 = [FLAVOR_ROCKWELL_65C02, FLAVOR_WDC_65C02]; + +export const FLAVORS = [FLAVOR_6502, ...FLAVORS_65C02]; + +export type Flavor = MemberOf; + +export interface CpuOptions { + flavor?: Flavor; +} + +export interface CpuState { + /** Accumulator */ + a: byte; + /** X index */ + x: byte; + /** Y index */ + y: byte; + /** Status register */ + s: byte; + /** Program counter */ + pc: word; + /** Stack pointer */ + sp: byte; + /** Elapsed cycles */ + cycles: number; +} + +export type Mode = + | "accumulator" // A (Accumulator) + | "implied" // Implied + | "immediate" // # Immediate + | "absolute" // a Absolute + | "zeroPage" // zp Zero Page + | "relative" // r Relative + | "absoluteX" // a,X Absolute, X + | "absoluteY" // a,Y Absolute, Y + | "zeroPageX" // zp,X Zero Page, X + | "zeroPageY" // zp,Y Zero Page, Y + | "absoluteIndirect" // (a) Indirect + | "zeroPageXIndirect" // (zp,X) Zero Page Indexed Indirect + | "zeroPageIndirectY" // (zp),Y Zero Page Indexed with Y + | "zeroPageIndirect" // (zp), + | "absoluteXIndirect" // (a, X), + | "zeroPage_relative"; // zp, Relative + +export type Modes = Record; + +/** Addressing mode name to instruction size mapping. */ +export const sizes: Modes = { + accumulator: 1, + implied: 1, + immediate: 2, + absolute: 3, + zeroPage: 2, + relative: 2, + + absoluteX: 3, + absoluteY: 3, + zeroPageX: 2, + zeroPageY: 2, + + absoluteIndirect: 3, + zeroPageXIndirect: 2, + zeroPageIndirectY: 2, + + /* 65c02 */ + zeroPageIndirect: 2, + absoluteXIndirect: 3, + zeroPage_relative: 3, +}; + +/** Status register flag numbers. */ +export type flag = 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01; + +/** + * + */ +export type DebugInfo = { + /** Program counter */ + pc: word; + /** Accumulator */ + ar: byte; + /** X index */ + xr: byte; + /** Y index */ + yr: byte; + /** Status register */ + sr: byte; + /** Stack pointer */ + sp: byte; + /** Current command */ + cmd: byte[]; +}; + +/** Flags to status byte mask. */ +export const flags: { [key: string]: flag } = { + N: 0x80, // Negative + V: 0x40, // oVerflow + X: 0x20, // Unused, always 1 + B: 0x10, // Break + D: 0x08, // Decimal + I: 0x04, // Interrupt + Z: 0x02, // Zero + C: 0x01, // Carry +}; + +/** CPU-referenced memory locations. */ +const loc = { + STACK: 0x100, + NMI: 0xfffa, + RESET: 0xfffc, + BRK: 0xfffe, +}; + +interface ResettablePageHandler extends MemoryPages { + reset(): void; +} + +function isResettablePageHandler( + pageHandler: MemoryPages | ResettablePageHandler +): pageHandler is ResettablePageHandler { + return (pageHandler as ResettablePageHandler).reset !== undefined; +} + +const BLANK_PAGE: Memory = { + read: function () { + return 0; + }, + write: function () { + /* not writable */ + }, +}; + +interface Opts { + inc?: boolean; +} + +type ReadFn = () => byte; +type WriteFn = (val: byte) => void; +type ReadAddrFn = (opts?: Opts) => word; +type ImpliedFn = () => void; + +interface Instruction { + name: string; + mode: Mode; + op: (fn: T) => void; + modeFn: T; +} + +type StrictInstruction = + | Instruction + | Instruction + | Instruction + | Instruction + | Instruction + | Instruction + | Instruction; + +type Instructions = Record; + +type callback = (cpu: CPU6502) => boolean | void; + +export default class CPU6502 { + /** flavor */ + private readonly flavor: Flavor; + /** 65C02 emulation mode flag */ + private readonly is65C02: boolean; + + /** + * Registers + */ + + /** Program counter */ + private pc: word = 0; + /** Status register */ + private sr: byte = flags.X; + /** Accumulator */ + private ar: byte = 0; + /** X index */ + private xr: byte = 0; + /** Y index */ + private yr: byte = 0; + /** Stack pointer */ + private sp: byte = 0xff; + + /** Current instruction */ + private op: Instruction; + /** Last accessed memory address */ + private addr: word = 0; + + /** Filled array of memory handlers by address page */ + private memPages: Memory[] = new Array(0x100); + /** Callbacks invoked on reset signal */ + private resetHandlers: ResettablePageHandler[] = []; + /** Elapsed cycles */ + private cycles = 0; + /** Command being fetched signal */ + private sync = false; + + /** Processor is in WAI mode */ + private wait = false; + /** Processor is in STP mode */ + private stop = false; + + /** Filled array of CPU operations */ + private readonly opary: Instruction[]; + + constructor({ flavor }: CpuOptions = {}) { + this.flavor = flavor ?? FLAVOR_6502; + this.is65C02 = !!flavor && FLAVORS_65C02.includes(flavor); + + this.memPages.fill(BLANK_PAGE); + this.memPages.fill(BLANK_PAGE); + + // Create this CPU's instruction table + + let ops = { ...this.OPS_6502 }; + + switch (this.flavor) { + case FLAVOR_WDC_65C02: + ops = { + ...ops, + ...this.OPS_65C02, + ...this.OPS_WDC_65C02, + }; + break; + case FLAVOR_ROCKWELL_65C02: + ops = { + ...ops, + ...this.OPS_65C02, + ...this.OPS_ROCKWELL_65C02, + }; + break; + default: + ops = { + ...ops, + ...this.OPS_NMOS_6502, + }; + } + + // Certain browsers benefit from using arrays over maps + this.opary = new Array(0x100); + + for (let idx = 0; idx < 0x100; idx++) { + this.opary[idx] = ops[idx] || this.unknown(idx); + } + } + + /** + * Set or clears `f` in the status register. `f` must be a byte with a + * single bit set. + */ + private setFlag(f: flag, on: boolean) { + this.sr = on ? this.sr | f : this.sr & ~f; + } + + /** Updates the status register's zero flag and negative flag. */ + private testNZ(val: byte) { + this.sr = val === 0 ? this.sr | flags.Z : this.sr & ~flags.Z; + this.sr = val & 0x80 ? this.sr | flags.N : this.sr & ~flags.N; + + return val; + } + + /** Updates the status register's zero flag. */ + private testZ(val: byte) { + this.sr = val === 0 ? this.sr | flags.Z : this.sr & ~flags.Z; + + return val; + } + + /** + * Returns `a + b`, unless `sub` is true, in which case it performs + * `a - b`. The status register is updated according to the result. + */ + private add(a: byte, b: byte, sub: boolean): byte { + const a7 = a >> 7; + const b7 = b >> 7; + const ci = this.sr & flags.C; + let c; + let co; + let v; + let n; + let z; + + const updateFlags = (c: byte) => { + const bin = c & 0xff; + n = bin >> 7; + co = c >> 8; + z = !((a + b + ci) & 0xff); + v = a7 ^ b7 ^ n ^ co; + }; + + const updateBCDFlags = (c: byte) => { + if (this.is65C02) { + const bin = c & 0xff; + n = bin >> 7; + z = !bin; + if (this.op.mode === "immediate") { + if (this.flavor === FLAVOR_WDC_65C02) { + this.readByte(sub ? 0xb8 : 0x7f); + } else { + // rockwell65c02 + this.readByte(sub ? 0xb1 : 0x59); + } + } else { + this.readByte(this.addr); + } + } + if (!sub) { + co = c >> 8; + } + }; + + c = (a & 0x0f) + (b & 0x0f) + ci; + if ((this.sr & flags.D) !== 0) { + // BCD + if (sub) { + if (c < 0x10) { + c = (c - 0x06) & 0x0f; + } + c += (a & 0xf0) + (b & 0xf0); + updateFlags(c); + if (c < 0x100) { + c += 0xa0; + } + } else { + if (c > 0x9) { + c = 0x10 + ((c + 0x6) & 0xf); + } + c += (a & 0xf0) + (b & 0xf0); + updateFlags(c); + if (c >= 0xa0) { + c += 0x60; + } + } + updateBCDFlags(c); + } else { + c += (a & 0xf0) + (b & 0xf0); + updateFlags(c); + } + c = c & 0xff; + + this.setFlag(flags.N, !!n); + this.setFlag(flags.V, !!v); + this.setFlag(flags.Z, !!z); + this.setFlag(flags.C, !!co); + + return c; + } + + /** Increments `a` and returns the value, setting the status register. */ + private increment(a: byte) { + return this.testNZ((a + 0x01) & 0xff); + } + + private decrement(a: byte) { + return this.testNZ((a + 0xff) & 0xff); + } + + private readBytePC(): byte { + const result = this.readByte(this.pc); + + this.pc = (this.pc + 1) & 0xffff; + + return result; + } + + private readByte(addr: word): byte { + this.addr = addr; + const page = addr >> 8, + off = addr & 0xff; + + const result = this.memPages[page].read(page, off); + + this.cycles++; + + return result; + } + + private writeByte(addr: word, val: byte) { + this.addr = addr; + const page = addr >> 8, + off = addr & 0xff; + + this.memPages[page].write(page, off, val); + + this.cycles++; + } + + private readWord(addr: word): word { + return this.readByte(addr) | (this.readByte(addr + 1) << 8); + } + + private readWordPC(): word { + return this.readBytePC() | (this.readBytePC() << 8); + } + + private readZPWord(addr: byte): word { + const lsb = this.readByte(addr & 0xff); + const msb = this.readByte((addr + 1) & 0xff); + + return (msb << 8) | lsb; + } + + private pushByte(val: byte) { + this.writeByte(loc.STACK | this.sp, val); + this.sp = (this.sp + 0xff) & 0xff; + } + + private pushWord(val: word) { + this.pushByte(val >> 8); + this.pushByte(val & 0xff); + } + + private pullByte(): byte { + this.sp = (this.sp + 0x01) & 0xff; + return this.readByte(loc.STACK | this.sp); + } + + private pullWordRaw(): word { + const lsb = this.pullByte(); + const msb = this.pullByte(); + + return (msb << 8) | lsb; + } + + // Helpers that replicate false reads and writes during work cycles that + // vary between CPU versions + + private workCycle(addr: word, val: byte) { + if (this.is65C02) { + this.readByte(addr); + } else { + this.writeByte(addr, val); + } + } + + private workCycleIndexedWrite(pc: word, addr: word, addrIdx: word): void { + const oldPage = addr & 0xff00; + if (this.is65C02) { + this.readByte(pc); + } else { + const off = addrIdx & 0xff; + this.readByte(oldPage | off); + } + } + + private workCycleIndexedRead(pc: word, addr: word, addrIdx: word): void { + const oldPage = addr & 0xff00; + const newPage = addrIdx & 0xff00; + if (newPage !== oldPage) { + if (this.is65C02) { + this.readByte(pc); + } else { + const off = addrIdx & 0xff; + this.readByte(oldPage | off); + } + } + } + + /* + * Implied function + */ + + implied = () => { + this.readByte(this.pc); + }; + + /* + * Read functions + */ + + // #$00 + readImmediate = (): byte => { + return this.readBytePC(); + }; + + // $0000 + readAbsolute = (): byte => { + return this.readByte(this.readWordPC()); + }; + + // $00 + readZeroPage = (): byte => { + return this.readByte(this.readBytePC()); + }; + + // $0000,X + readAbsoluteX = (): byte => { + const addr = this.readWordPC(); + const pc = this.addr; + const addrIdx = (addr + this.xr) & 0xffff; + this.workCycleIndexedRead(pc, addr, addrIdx); + return this.readByte(addrIdx); + }; + + // $0000,Y + readAbsoluteY = (): byte => { + const addr = this.readWordPC(); + const pc = this.addr; + const addrIdx = (addr + this.yr) & 0xffff; + this.workCycleIndexedRead(pc, addr, addrIdx); + return this.readByte(addrIdx); + }; + + // $00,X + readZeroPageX = (): byte => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + return this.readByte((zpAddr + this.xr) & 0xff); + }; + + // $00,Y + readZeroPageY = (): byte => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + return this.readByte((zpAddr + this.yr) & 0xff); + }; + + // ($00,X) + readZeroPageXIndirect = (): byte => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + const addr = this.readZPWord((zpAddr + this.xr) & 0xff); + return this.readByte(addr); + }; + + // ($00),Y + readZeroPageIndirectY = (): byte => { + const zpAddr = this.readBytePC(); + const pc = this.addr; + const addr = this.readZPWord(zpAddr); + const addrIdx = (addr + this.yr) & 0xffff; + this.workCycleIndexedRead(pc, addr, addrIdx); + return this.readByte(addrIdx); + }; + + // ($00) (65C02) + readZeroPageIndirect = (): byte => { + return this.readByte(this.readZPWord(this.readBytePC())); + }; + + /* + * Write Functions + */ + + // $0000 + writeAbsolute = (val: byte) => { + this.writeByte(this.readWordPC(), val); + }; + + // $00 + writeZeroPage = (val: byte) => { + this.writeByte(this.readBytePC(), val); + }; + + // $0000,X + writeAbsoluteX = (val: byte) => { + const addr = this.readWordPC(); + const pc = this.addr; + const addrIdx = (addr + this.xr) & 0xffff; + this.workCycleIndexedWrite(pc, addr, addrIdx); + this.writeByte(addrIdx, val); + }; + + // $0000,Y + writeAbsoluteY = (val: byte) => { + const addr = this.readWordPC(); + const pc = this.addr; + const addrIdx = (addr + this.yr) & 0xffff; + this.workCycleIndexedWrite(pc, addr, addrIdx); + this.writeByte(addrIdx, val); + }; + + // $00,X + writeZeroPageX = (val: byte) => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + this.writeByte((zpAddr + this.xr) & 0xff, val); + }; + + // $00,Y + writeZeroPageY = (val: byte) => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + this.writeByte((zpAddr + this.yr) & 0xff, val); + }; + + // ($00,X) + writeZeroPageXIndirect = (val: byte) => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + const addr = this.readZPWord((zpAddr + this.xr) & 0xff); + this.writeByte(addr, val); + }; + + // ($00),Y + writeZeroPageIndirectY = (val: byte) => { + const zpAddr = this.readBytePC(); + const pc = this.addr; + const addr = this.readZPWord(zpAddr); + const addrIdx = (addr + this.yr) & 0xffff; + this.workCycleIndexedWrite(pc, addr, addrIdx); + this.writeByte(addrIdx, val); + }; + + // ($00) (65C02) + writeZeroPageIndirect = (val: byte) => { + this.writeByte(this.readZPWord(this.readBytePC()), val); + }; + + // $00 + readAddrZeroPage = () => { + return this.readBytePC(); + }; + + // $00,X + readAddrZeroPageX = () => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + return (zpAddr + this.xr) & 0xff; + }; + + // $0000 (65C02) + readAddrAbsolute = (): word => { + return this.readWordPC(); + }; + + // ($0000) (6502) + readAddrAbsoluteIndirectBug = (): word => { + const addr = this.readWordPC(); + const page = addr & 0xff00; + const off = addr & 0x00ff; + const lsb = this.readByte(addr); + const msb = this.readByte(page | ((off + 0x01) & 0xff)); + return (msb << 8) | lsb; + }; + + // ($0000) (65C02) + readAddrAbsoluteIndirect = (): word => { + const addr = this.readWord(this.readWordPC()); + this.readByte(this.addr); + return addr; + }; + + // $0000,X + readAddrAbsoluteX = (opts?: Opts): word => { + let addr = this.readWordPC(); + const page = addr & 0xff00; + addr = (addr + this.xr) & 0xffff; + if (this.is65C02) { + if (opts?.inc) { + this.readByte(this.addr); + } else { + const newPage = addr & 0xff00; + if (page !== newPage) { + this.readByte(this.addr); + } + } + } else { + const off = addr & 0x00ff; + this.readByte(page | off); + } + return addr; + }; + + // $0000,Y (NMOS 6502) + readAddrAbsoluteY = (): word => { + let addr = this.readWordPC(); + const page = addr & 0xff00; + addr = (addr + this.yr) & 0xffff; + const off = addr & 0x00ff; + this.readByte(page | off); + return addr; + }; + + // ($00,X) (NMOS 6502) + readAddrZeroPageXIndirect = () => { + const zpAddr = this.readBytePC(); + this.readByte(zpAddr); + return this.readZPWord((zpAddr + this.xr) & 0xff); + }; + + // ($00),Y (NMOS 6502) + readAddrZeroPageIndirectY = () => { + const zpAddr = this.readBytePC(); + const addr = this.readZPWord(zpAddr); + const addrIdx = (addr + this.yr) & 0xffff; + const oldPage = addr & 0xff00; + const off = addrIdx & 0xff; + this.readByte(oldPage | off); + return addrIdx; + }; + + // $(0000,X) (65C02) + readAddrAbsoluteXIndirect = (): word => { + const lsb = this.readBytePC(); + const pc = this.addr; + const msb = this.readBytePC(); + const addr = (((msb << 8) | lsb) + this.xr) & 0xffff; + this.readByte(pc); + return this.readWord(addr); + }; + + // 5C, DC, FC NOP (65C02) + readNop = (): void => { + this.readWordPC(); + this.readByte(this.addr); + }; + + // NOP (65C02) + readNopImplied = (): void => { + // Op is 1 cycle + }; + + /* Break */ + brk = (readFn: ReadFn) => { + readFn(); + this.pushWord(this.pc); + this.pushByte(this.sr | flags.B); + if (this.is65C02) { + this.setFlag(flags.D, false); + } + this.setFlag(flags.I, true); + this.pc = this.readWord(loc.BRK); + }; + + /* Stop (65C02) */ + stp = () => { + this.stop = true; + this.readByte(this.pc); + this.readByte(this.pc); + }; + + /* Wait (65C02) */ + wai = () => { + this.wait = true; + this.readByte(this.pc); + this.readByte(this.pc); + }; + + /* Load Accumulator */ + lda = (readFn: ReadFn) => { + this.ar = this.testNZ(readFn()); + }; + + /* Load X Register */ + ldx = (readFn: ReadFn) => { + this.xr = this.testNZ(readFn()); + }; + + /* Load Y Register */ + ldy = (readFn: ReadFn) => { + this.yr = this.testNZ(readFn()); + }; + + /* Store Accumulator */ + sta = (writeFn: WriteFn) => { + writeFn(this.ar); + }; + + /* Store X Register */ + stx = (writeFn: WriteFn) => { + writeFn(this.xr); + }; + + /* Store Y Register */ + sty = (writeFn: WriteFn) => { + writeFn(this.yr); + }; + + /* Store Zero */ + stz = (writeFn: WriteFn) => { + writeFn(0); + }; + + /* Add with Carry */ + adc = (readFn: ReadFn) => { + this.ar = this.add(this.ar, readFn(), /* sub= */ false); + }; + + /* Subtract with Carry */ + sbc = (readFn: ReadFn) => { + this.ar = this.add(this.ar, readFn() ^ 0xff, /* sub= */ true); + }; + + /* Increment Memory */ + incA = () => { + this.readByte(this.pc); + this.ar = this.increment(this.ar); + }; + + inc = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn({ inc: true }); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.increment(oldVal); + this.writeByte(addr, val); + }; + + /* Increment X */ + inx = () => { + this.readByte(this.pc); + this.xr = this.increment(this.xr); + }; + + /* Increment Y */ + iny = () => { + this.readByte(this.pc); + this.yr = this.increment(this.yr); + }; + + /* Decrement Memory */ + decA = () => { + this.readByte(this.pc); + this.ar = this.decrement(this.ar); + }; + + dec = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn({ inc: true }); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.decrement(oldVal); + this.writeByte(addr, val); + }; + + /* Decrement X */ + dex = () => { + this.readByte(this.pc); + this.xr = this.decrement(this.xr); + }; + + /* Decrement Y */ + dey = () => { + this.readByte(this.pc); + this.yr = this.decrement(this.yr); + }; + + shiftLeft = (val: byte) => { + this.setFlag(flags.C, !!(val & 0x80)); + return this.testNZ((val << 1) & 0xff); + }; + + /* Arithmetic Shift Left */ + aslA = () => { + this.readByte(this.pc); + this.ar = this.shiftLeft(this.ar); + }; + + asl = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.shiftLeft(oldVal); + this.writeByte(addr, val); + }; + + shiftRight = (val: byte) => { + this.setFlag(flags.C, !!(val & 0x01)); + return this.testNZ(val >> 1); + }; + + /* Logical Shift Right */ + lsrA = () => { + this.readByte(this.pc); + this.ar = this.shiftRight(this.ar); + }; + + lsr = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.shiftRight(oldVal); + this.writeByte(addr, val); + }; + + rotateLeft = (val: byte) => { + const c = this.sr & flags.C; + this.setFlag(flags.C, !!(val & 0x80)); + return this.testNZ(((val << 1) | (c ? 0x01 : 0x00)) & 0xff); + }; + + /* Rotate Left */ + rolA = () => { + this.readByte(this.pc); + this.ar = this.rotateLeft(this.ar); + }; + + rol = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.rotateLeft(oldVal); + this.writeByte(addr, val); + }; + + private rotateRight(a: byte) { + const c = this.sr & flags.C; + this.setFlag(flags.C, !!(a & 0x01)); + return this.testNZ((a >> 1) | (c ? 0x80 : 0x00)); + } + + /* Rotate Right */ + rorA = () => { + this.readByte(this.pc); + this.ar = this.rotateRight(this.ar); + }; + + ror = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.rotateRight(oldVal); + this.writeByte(addr, val); + }; + + /* Logical And Accumulator */ + and = (readFn: ReadFn) => { + this.ar = this.testNZ(this.ar & readFn()); + }; + + /* Logical Or Accumulator */ + ora = (readFn: ReadFn) => { + this.ar = this.testNZ(this.ar | readFn()); + }; + + /* Logical Exclusive Or Accumulator */ + eor = (readFn: ReadFn) => { + this.ar = this.testNZ(this.ar ^ readFn()); + }; + + /* Reset Bit */ + + rmb = (b: byte) => { + const bit = (0x1 << b) ^ 0xff; + const addr = this.readBytePC(); + let val = this.readByte(addr); + this.readByte(addr); + val &= bit; + this.writeByte(addr, val); + }; + + /* Set Bit */ + + smb = (b: byte) => { + const bit = 0x1 << b; + const addr = this.readBytePC(); + let val = this.readByte(addr); + this.readByte(addr); + val |= bit; + this.writeByte(addr, val); + }; + + /* Test and Reset Bits */ + trb = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const val = this.readByte(addr); + this.testZ(val & this.ar); + this.readByte(addr); + this.writeByte(addr, val & ~this.ar); + }; + + /* Test and Set Bits */ + tsb = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const val = this.readByte(addr); + this.testZ(val & this.ar); + this.readByte(addr); + this.writeByte(addr, val | this.ar); + }; + + /* Bit */ + bit = (readFn: ReadFn) => { + const val = readFn(); + this.setFlag(flags.Z, (val & this.ar) === 0); + this.setFlag(flags.N, !!(val & 0x80)); + this.setFlag(flags.V, !!(val & 0x40)); + }; + + /* Bit Immediate*/ + bitI = (readFn: ReadFn) => { + const val = readFn(); + this.setFlag(flags.Z, (val & this.ar) === 0); + }; + + private compare(a: byte, b: byte) { + b = b ^ 0xff; + const c = a + b + 1; + this.setFlag(flags.C, c > 0xff); + this.testNZ(c & 0xff); + } + + cmp = (readFn: ReadFn) => { + this.compare(this.ar, readFn()); + }; + + cpx = (readFn: ReadFn) => { + this.compare(this.xr, readFn()); + }; + + cpy = (readFn: ReadFn) => { + this.compare(this.yr, readFn()); + }; + + /* Branches */ + brs = (f: flag) => { + const off = this.readBytePC(); // changes pc + if ((f & this.sr) !== 0) { + this.readByte(this.pc); + const oldPage = this.pc & 0xff00; + this.pc += off > 127 ? off - 256 : off; + this.pc &= 0xffff; + const newPage = this.pc & 0xff00; + const newOff = this.pc & 0xff; + if (newPage !== oldPage) this.readByte(oldPage | newOff); + } + }; + + brc = (f: flag | 0) => { + const off = this.readBytePC(); // changes pc + if ((f & this.sr) === 0) { + this.readByte(this.pc); + const oldPage = this.pc & 0xff00; + this.pc += off > 127 ? off - 256 : off; + this.pc &= 0xffff; + const newPage = this.pc & 0xff00; + const newOff = this.pc & 0xff; + if (newPage !== oldPage) this.readByte(oldPage | newOff); + } + }; + + /* WDC 65C02 branches */ + + bbr = (b: byte) => { + const zpAddr = this.readBytePC(); + const val = this.readByte(zpAddr); + this.writeByte(zpAddr, val); + const off = this.readBytePC(); // changes pc + const oldPc = this.pc; + const oldPage = oldPc & 0xff00; + + let newPC = this.pc + (off > 127 ? off - 256 : off); + newPC &= 0xffff; + const newOff = newPC & 0xff; + this.readByte(oldPage | newOff); + if (((1 << b) & val) === 0) { + this.pc = newPC; + } + }; + + bbs = (b: byte) => { + const zpAddr = this.readBytePC(); + const val = this.readByte(zpAddr); + this.writeByte(zpAddr, val); + const off = this.readBytePC(); // changes pc + const oldPc = this.pc; + const oldPage = oldPc & 0xff00; + + let newPC = this.pc + (off > 127 ? off - 256 : off); + newPC &= 0xffff; + const newOff = newPC & 0xff; + this.readByte(oldPage | newOff); + if (((1 << b) & val) !== 0) { + this.pc = newPC; + } + }; + + /* Transfers and stack */ + tax = () => { + this.readByte(this.pc); + this.testNZ((this.xr = this.ar)); + }; + + txa = () => { + this.readByte(this.pc); + this.testNZ((this.ar = this.xr)); + }; + + tay = () => { + this.readByte(this.pc); + this.testNZ((this.yr = this.ar)); + }; + + tya = () => { + this.readByte(this.pc); + this.testNZ((this.ar = this.yr)); + }; + + tsx = () => { + this.readByte(this.pc); + this.testNZ((this.xr = this.sp)); + }; + + txs = () => { + this.readByte(this.pc); + this.sp = this.xr; + }; + + pha = () => { + this.readByte(this.pc); + this.pushByte(this.ar); + }; + + pla = () => { + this.readByte(this.pc); + this.readByte(0x0100 | this.sp); + this.testNZ((this.ar = this.pullByte())); + }; + + phx = () => { + this.readByte(this.pc); + this.pushByte(this.xr); + }; + + plx = () => { + this.readByte(this.pc); + this.readByte(0x0100 | this.sp); + this.testNZ((this.xr = this.pullByte())); + }; + + phy = () => { + this.readByte(this.pc); + this.pushByte(this.yr); + }; + + ply = () => { + this.readByte(this.pc); + this.readByte(0x0100 | this.sp); + this.testNZ((this.yr = this.pullByte())); + }; + + php = () => { + this.readByte(this.pc); + this.pushByte(this.sr | flags.B); + }; + + plp = () => { + this.readByte(this.pc); + this.readByte(0x0100 | this.sp); + this.sr = (this.pullByte() & ~flags.B) | flags.X; + }; + + /* Jump */ + jmp = (readAddrFn: ReadAddrFn) => { + this.pc = readAddrFn(); + }; + + /* Jump Subroutine */ + jsr = () => { + const lsb = this.readBytePC(); + this.readByte(0x0100 | this.sp); + this.pushWord(this.pc); + const msb = this.readBytePC(); + this.pc = ((msb << 8) | lsb) & 0xffff; + }; + + /* Return from Subroutine */ + rts = () => { + this.readByte(this.pc); + this.readByte(0x0100 | this.sp); + const addr = this.pullWordRaw(); + this.readByte(addr); + this.pc = (addr + 1) & 0xffff; + }; + + /* Return from Interrupt */ + rti = () => { + this.readByte(this.pc); + this.readByte(0x0100 | this.sp); + this.sr = (this.pullByte() & ~flags.B) | flags.X; + this.pc = this.pullWordRaw(); + }; + + /* Set and Clear */ + set = (flag: flag) => { + this.readByte(this.pc); + this.sr |= flag; + }; + + clr = (flag: flag) => { + this.readByte(this.pc); + this.sr &= ~flag; + }; + + /* No-Op */ + nop = (readFn: ImpliedFn | ReadFn) => { + readFn(); + }; + + /* NMOS 6502 Illegal opcodes */ + + /* ASO = ASL + ORA */ + aso = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.shiftLeft(oldVal); + this.writeByte(addr, val); + this.ar |= val; + this.testNZ(this.ar); + }; + + /* RLA = ROL + AND */ + rla = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.rotateLeft(oldVal); + this.writeByte(addr, val); + this.ar &= val; + this.testNZ(this.ar); + }; + + /* LSE = LSR + EOR */ + lse = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.shiftRight(oldVal); + this.writeByte(addr, val); + this.ar ^= val; + this.testNZ(this.ar); + }; + + /* RRA = ROR + ADC */ + rra = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.rotateRight(oldVal); + this.writeByte(addr, val); + this.ar = this.add(this.ar, val, false); + }; + + /* AXS = Store A & X */ + axs = (writeFn: WriteFn) => { + writeFn(this.ar & this.xr); + }; + + /* LAX = Load A & X */ + lax = (readFn: ReadFn) => { + const val = readFn(); + this.ar = val; + this.xr = val; + this.testNZ(val); + }; + + /* DCM = DEC + CMP */ + dcm = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn({ inc: true }); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.decrement(oldVal); + this.writeByte(addr, val); + this.compare(this.ar, val); + }; + + /* INS = INC + SBC */ + ins = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn({ inc: true }); + const oldVal = this.readByte(addr); + this.workCycle(addr, oldVal); + const val = this.increment(oldVal); + this.writeByte(addr, val); + this.ar = this.add(this.ar, val ^ 0xff, true); + }; + + /* ALR = AND + LSR */ + alr = (readFn: ReadFn) => { + const val = readFn() & this.ar; + this.ar = this.shiftRight(val); + }; + + /* ARR = AND + ROR */ + arr = (readFn: ReadFn) => { + const val = readFn() & this.ar; + const ah = val >> 4; + const al = val & 0xf; + const b7 = val >> 7; + const b6 = (val >> 6) & 0x1; + this.ar = this.rotateRight(val); + let c = !!b7; + const v = !!(b7 ^ b6); + if (this.sr & flags.D) { + if (al + (al & 0x1) > 0x5) { + this.ar = (this.ar & 0xf0) | ((this.ar + 0x6) & 0xf); + } + if (ah + (ah & 0x1) > 5) { + c = true; + this.ar = (this.ar + 0x60) & 0xff; + } + } + this.setFlag(flags.V, v); + this.setFlag(flags.C, c); + }; + + /* XAA = TAX + AND */ + xaa = (readFn: ReadFn) => { + const val = readFn(); + this.ar = (this.xr & 0xee) | (this.xr & this.ar & 0x11); + this.ar = this.testNZ(this.ar & val); + }; + + /** OAL = ORA + AND */ + oal = (readFn: ReadFn) => { + this.ar |= 0xee; + const val = this.testNZ(this.ar & readFn()); + this.ar = val; + this.xr = val; + }; + + /* SAX = A & X + SBC */ + sax = (readFn: ReadFn) => { + const a = this.xr & this.ar; + let b = readFn(); + b = b ^ 0xff; + const c = a + b + 1; + this.setFlag(flags.C, c > 0xff); + this.xr = this.testNZ(c & 0xff); + }; + + /* TAS = X & Y -> S */ + tas = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + let val = this.xr & this.ar; + this.sp = val; + const msb = addr >> 8; + val = val & ((msb + 1) & 0xff); + this.writeByte(addr, val); + }; + + /* SAY = Y & AH + 1 */ + say = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const msb = addr >> 8; + const val = this.yr & ((msb + 1) & 0xff); + this.writeByte(addr, val); + }; + + /* XAS = X & AH + 1 */ + xas = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + const msb = addr >> 8; + const val = this.xr & ((msb + 1) & 0xff); + this.writeByte(addr, val); + }; + + /* AXA = X & AH + 1 */ + axa = (readAddrFn: ReadAddrFn) => { + const addr = readAddrFn(); + let val = this.xr & this.ar; + const msb = addr >> 8; + val = val & ((msb + 1) & 0xff); + this.writeByte(addr, val); + }; + + /* ANC = AND with carry */ + anc = (readFn: ReadFn) => { + this.ar = this.testNZ(this.ar & readFn()); + const c = !!(this.ar & 0x80); + this.setFlag(flags.C, c); + }; + + /* LAS = RD & SP -> A, X, S */ + las = (readFn: ReadFn) => { + const val = this.sp & readFn(); + this.sp = val; + this.xr = val; + this.ar = this.testNZ(val); + }; + + /* SKB/SKW */ + skp = (readFn: ReadFn) => { + readFn(); + }; + + /* HLT */ + hlt = (_impliedFn: ImpliedFn) => { + this.readByte(this.pc); + this.readByte(this.pc); + // PC shouldn't have advanced + this.pc = --this.pc & 0xffff; + this.stop = true; + }; + + private unknown(b: byte) { + let unk: StrictInstruction; + if (this.is65C02) { + // Default behavior is a 1 cycle NOP + unk = { + name: "NOP", + op: this.nop, + modeFn: this.readNopImplied, + mode: "implied", + }; + } else { + // All 6502 Instructions should be defined + throw new Error(`Missing ${toHex(b)}`); + } + return unk; + } + + public step(cb?: callback) { + this.sync = true; + this.op = this.opary[this.readBytePC()]; + this.sync = false; + this.op.op(this.op.modeFn); + + cb?.(this); + } + + public stepN(n: number, cb?: callback) { + for (let idx = 0; idx < n; idx++) { + this.sync = true; + this.op = this.opary[this.readBytePC()]; + this.sync = false; + this.op.op(this.op.modeFn); + + if (cb?.(this)) { + return; + } + } + } + + public stepCycles(c: number) { + const end = this.cycles + c; + + while (this.cycles < end) { + this.sync = true; + this.op = this.opary[this.readBytePC()]; + this.sync = false; + this.op.op(this.op.modeFn); + } + } + + public stepCyclesDebug(c: number, cb?: callback): void { + const end = this.cycles + c; + + while (this.cycles < end) { + this.sync = true; + this.op = this.opary[this.readBytePC()]; + this.sync = false; + this.op.op(this.op.modeFn); + + if (cb?.(this)) { + return; + } + } + } + + public addPageHandler(pho: MemoryPages | ResettablePageHandler) { + for (let idx = pho.start(); idx <= pho.end(); idx++) { + this.memPages[idx] = pho; + } + if (isResettablePageHandler(pho)) this.resetHandlers.push(pho); + } + + public reset() { + // cycles = 0; + this.sr = flags.X; + this.sp = 0xff; + this.ar = 0; + this.yr = 0; + this.xr = 0; + this.pc = this.readWord(loc.RESET); + this.wait = false; + this.stop = false; + + for (let idx = 0; idx < this.resetHandlers.length; idx++) { + this.resetHandlers[idx].reset(); + } + } + + /* IRQ - Interrupt Request */ + public irq() { + if ((this.sr & flags.I) === 0) { + this.pushWord(this.pc); + this.pushByte(this.sr & ~flags.B); + if (this.is65C02) { + this.setFlag(flags.D, false); + } + this.setFlag(flags.I, true); + this.pc = this.readWord(loc.BRK); + this.wait = false; + } + } + + /* NMI Non-maskable Interrupt */ + public nmi() { + this.pushWord(this.pc); + this.pushByte(this.sr & ~flags.B); + if (this.is65C02) { + this.setFlag(flags.D, false); + } + this.setFlag(flags.I, true); + this.pc = this.readWord(loc.NMI); + this.wait = false; + } + + public getPC() { + return this.pc; + } + + public setPC(pc: word) { + this.pc = pc; + } + + public getDebugInfo(): DebugInfo { + const b = this.read(this.pc); + const op = this.opary[b]; + const size = sizes[op.mode]; + const cmd = new Array(size); + cmd[0] = b; + for (let idx = 1; idx < size; idx++) { + cmd[idx] = this.read(this.pc + idx); + } + + return { + pc: this.pc, + ar: this.ar, + xr: this.xr, + yr: this.yr, + sr: this.sr, + sp: this.sp, + cmd, + }; + } + public getSync() { + return this.sync; + } + + public getStop() { + return this.stop; + } + + public getWait() { + return this.wait; + } + + public getCycles() { + return this.cycles; + } + + public getOpInfo(opcode: byte) { + return this.opary[opcode]; + } + + public getState(): CpuState { + return { + a: this.ar, + x: this.xr, + y: this.yr, + s: this.sr, + pc: this.pc, + sp: this.sp, + cycles: this.cycles, + }; + } + + public setState(state: CpuState) { + this.ar = state.a; + this.xr = state.x; + this.yr = state.y; + this.sr = state.s; + this.pc = state.pc; + this.sp = state.sp; + this.cycles = state.cycles; + } + + public read(addr: word): byte; + public read(page: byte, off: byte): byte; + + public read(a: number, b?: number): byte { + let page, off; + if (b !== undefined) { + page = a & 0xff; + off = b & 0xff; + } else { + page = (a >> 8) & 0xff; + off = a & 0xff; + } + return this.memPages[page].read(page, off); + } + + public write(addr: word, val: byte): void; + public write(page: byte, off: byte, val: byte): void; + + public write(a: number, b: number, c?: byte): void { + let page, off, val; + + if (c !== undefined) { + page = a & 0xff; + off = b & 0xff; + val = c & 0xff; + } else { + page = (a >> 8) & 0xff; + off = a & 0xff; + val = b & 0xff; + } + this.memPages[page].write(page, off, val); + } + + OPS_6502: Instructions = { + // LDA + 0xa9: { + name: "LDA", + op: this.lda, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xa5: { + name: "LDA", + op: this.lda, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xb5: { + name: "LDA", + op: this.lda, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0xad: { + name: "LDA", + op: this.lda, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0xbd: { + name: "LDA", + op: this.lda, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0xb9: { + name: "LDA", + op: this.lda, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0xa1: { + name: "LDA", + op: this.lda, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0xb1: { + name: "LDA", + op: this.lda, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // LDX + 0xa2: { + name: "LDX", + op: this.ldx, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xa6: { + name: "LDX", + op: this.ldx, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xb6: { + name: "LDX", + op: this.ldx, + modeFn: this.readZeroPageY, + mode: "zeroPageY", + }, + 0xae: { + name: "LDX", + op: this.ldx, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0xbe: { + name: "LDX", + op: this.ldx, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + + // LDY + 0xa0: { + name: "LDY", + op: this.ldy, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xa4: { + name: "LDY", + op: this.ldy, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xb4: { + name: "LDY", + op: this.ldy, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0xac: { + name: "LDY", + op: this.ldy, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0xbc: { + name: "LDY", + op: this.ldy, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + + // STA + 0x85: { + name: "STA", + op: this.sta, + modeFn: this.writeZeroPage, + mode: "zeroPage", + }, + 0x95: { + name: "STA", + op: this.sta, + modeFn: this.writeZeroPageX, + mode: "zeroPageX", + }, + 0x8d: { + name: "STA", + op: this.sta, + modeFn: this.writeAbsolute, + mode: "absolute", + }, + 0x9d: { + name: "STA", + op: this.sta, + modeFn: this.writeAbsoluteX, + mode: "absoluteX", + }, + 0x99: { + name: "STA", + op: this.sta, + modeFn: this.writeAbsoluteY, + mode: "absoluteY", + }, + 0x81: { + name: "STA", + op: this.sta, + modeFn: this.writeZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x91: { + name: "STA", + op: this.sta, + modeFn: this.writeZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // STX + 0x86: { + name: "STX", + op: this.stx, + modeFn: this.writeZeroPage, + mode: "zeroPage", + }, + 0x96: { + name: "STX", + op: this.stx, + modeFn: this.writeZeroPageY, + mode: "zeroPageY", + }, + 0x8e: { + name: "STX", + op: this.stx, + modeFn: this.writeAbsolute, + mode: "absolute", + }, + + // STY + 0x84: { + name: "STY", + op: this.sty, + modeFn: this.writeZeroPage, + mode: "zeroPage", + }, + 0x94: { + name: "STY", + op: this.sty, + modeFn: this.writeZeroPageX, + mode: "zeroPageX", + }, + 0x8c: { + name: "STY", + op: this.sty, + modeFn: this.writeAbsolute, + mode: "absolute", + }, + + // ADC + 0x69: { + name: "ADC", + op: this.adc, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x65: { + name: "ADC", + op: this.adc, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x75: { + name: "ADC", + op: this.adc, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x6d: { + name: "ADC", + op: this.adc, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0x7d: { + name: "ADC", + op: this.adc, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0x79: { + name: "ADC", + op: this.adc, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0x61: { + name: "ADC", + op: this.adc, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x71: { + name: "ADC", + op: this.adc, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // SBC + 0xe9: { + name: "SBC", + op: this.sbc, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xe5: { + name: "SBC", + op: this.sbc, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xf5: { + name: "SBC", + op: this.sbc, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0xed: { + name: "SBC", + op: this.sbc, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0xfd: { + name: "SBC", + op: this.sbc, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0xf9: { + name: "SBC", + op: this.sbc, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0xe1: { + name: "SBC", + op: this.sbc, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0xf1: { + name: "SBC", + op: this.sbc, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // INC + 0xe6: { + name: "INC", + op: this.inc, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0xf6: { + name: "INC", + op: this.inc, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0xee: { + name: "INC", + op: this.inc, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0xfe: { + name: "INC", + op: this.inc, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // INX + 0xe8: { name: "INX", op: this.inx, modeFn: this.implied, mode: "implied" }, + + // INY + 0xc8: { name: "INY", op: this.iny, modeFn: this.implied, mode: "implied" }, + + // DEC + 0xc6: { + name: "DEC", + op: this.dec, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0xd6: { + name: "DEC", + op: this.dec, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0xce: { + name: "DEC", + op: this.dec, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0xde: { + name: "DEC", + op: this.dec, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // DEX + 0xca: { name: "DEX", op: this.dex, modeFn: this.implied, mode: "implied" }, + + // DEY + 0x88: { name: "DEY", op: this.dey, modeFn: this.implied, mode: "implied" }, + + // ASL + 0x0a: { + name: "ASL", + op: this.aslA, + modeFn: this.implied, + mode: "accumulator", + }, + 0x06: { + name: "ASL", + op: this.asl, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x16: { + name: "ASL", + op: this.asl, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x0e: { + name: "ASL", + op: this.asl, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x1e: { + name: "ASL", + op: this.asl, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // LSR + 0x4a: { + name: "LSR", + op: this.lsrA, + modeFn: this.implied, + mode: "accumulator", + }, + 0x46: { + name: "LSR", + op: this.lsr, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x56: { + name: "LSR", + op: this.lsr, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x4e: { + name: "LSR", + op: this.lsr, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x5e: { + name: "LSR", + op: this.lsr, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // ROL + 0x2a: { + name: "ROL", + op: this.rolA, + modeFn: this.implied, + mode: "accumulator", + }, + 0x26: { + name: "ROL", + op: this.rol, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x36: { + name: "ROL", + op: this.rol, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x2e: { + name: "ROL", + op: this.rol, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x3e: { + name: "ROL", + op: this.rol, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // ROR + 0x6a: { + name: "ROR", + op: this.rorA, + modeFn: this.implied, + mode: "accumulator", + }, + 0x66: { + name: "ROR", + op: this.ror, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x76: { + name: "ROR", + op: this.ror, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x6e: { + name: "ROR", + op: this.ror, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x7e: { + name: "ROR", + op: this.ror, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // AND + 0x29: { + name: "AND", + op: this.and, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x25: { + name: "AND", + op: this.and, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x35: { + name: "AND", + op: this.and, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x2d: { + name: "AND", + op: this.and, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0x3d: { + name: "AND", + op: this.and, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0x39: { + name: "AND", + op: this.and, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0x21: { + name: "AND", + op: this.and, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x31: { + name: "AND", + op: this.and, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // ORA + 0x09: { + name: "ORA", + op: this.ora, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x05: { + name: "ORA", + op: this.ora, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x15: { + name: "ORA", + op: this.ora, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x0d: { + name: "ORA", + op: this.ora, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0x1d: { + name: "ORA", + op: this.ora, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0x19: { + name: "ORA", + op: this.ora, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0x01: { + name: "ORA", + op: this.ora, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x11: { + name: "ORA", + op: this.ora, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // EOR + 0x49: { + name: "EOR", + op: this.eor, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x45: { + name: "EOR", + op: this.eor, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x55: { + name: "EOR", + op: this.eor, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x4d: { + name: "EOR", + op: this.eor, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0x5d: { + name: "EOR", + op: this.eor, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0x59: { + name: "EOR", + op: this.eor, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0x41: { + name: "EOR", + op: this.eor, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x51: { + name: "EOR", + op: this.eor, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // CMP + 0xc9: { + name: "CMP", + op: this.cmp, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xc5: { + name: "CMP", + op: this.cmp, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xd5: { + name: "CMP", + op: this.cmp, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0xcd: { + name: "CMP", + op: this.cmp, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0xdd: { + name: "CMP", + op: this.cmp, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0xd9: { + name: "CMP", + op: this.cmp, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0xc1: { + name: "CMP", + op: this.cmp, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0xd1: { + name: "CMP", + op: this.cmp, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // CPX + 0xe0: { + name: "CPX", + op: this.cpx, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xe4: { + name: "CPX", + op: this.cpx, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xec: { + name: "CPX", + op: this.cpx, + modeFn: this.readAbsolute, + mode: "absolute", + }, + + // CPY + 0xc0: { + name: "CPY", + op: this.cpy, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xc4: { + name: "CPY", + op: this.cpy, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xcc: { + name: "CPY", + op: this.cpy, + modeFn: this.readAbsolute, + mode: "absolute", + }, + + // BIT + 0x24: { + name: "BIT", + op: this.bit, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x2c: { + name: "BIT", + op: this.bit, + modeFn: this.readAbsolute, + mode: "absolute", + }, + + // BCC + 0x90: { name: "BCC", op: this.brc, modeFn: flags.C, mode: "relative" }, + + // BCS + 0xb0: { name: "BCS", op: this.brs, modeFn: flags.C, mode: "relative" }, + + // BEQ + 0xf0: { name: "BEQ", op: this.brs, modeFn: flags.Z, mode: "relative" }, + + // BMI + 0x30: { name: "BMI", op: this.brs, modeFn: flags.N, mode: "relative" }, + + // BNE + 0xd0: { name: "BNE", op: this.brc, modeFn: flags.Z, mode: "relative" }, + + // BPL + 0x10: { name: "BPL", op: this.brc, modeFn: flags.N, mode: "relative" }, + + // BVC + 0x50: { name: "BVC", op: this.brc, modeFn: flags.V, mode: "relative" }, + + // BVS + 0x70: { name: "BVS", op: this.brs, modeFn: flags.V, mode: "relative" }, + + // TAX + 0xaa: { name: "TAX", op: this.tax, modeFn: this.implied, mode: "implied" }, + + // TXA + 0x8a: { name: "TXA", op: this.txa, modeFn: this.implied, mode: "implied" }, + + // TAY + 0xa8: { name: "TAY", op: this.tay, modeFn: this.implied, mode: "implied" }, + + // TYA + 0x98: { name: "TYA", op: this.tya, modeFn: this.implied, mode: "implied" }, + + // TSX + 0xba: { name: "TSX", op: this.tsx, modeFn: this.implied, mode: "implied" }, + + // TXS + 0x9a: { name: "TXS", op: this.txs, modeFn: this.implied, mode: "implied" }, + + // PHA + 0x48: { name: "PHA", op: this.pha, modeFn: this.implied, mode: "implied" }, + + // PLA + 0x68: { name: "PLA", op: this.pla, modeFn: this.implied, mode: "implied" }, + + // PHP + 0x08: { name: "PHP", op: this.php, modeFn: this.implied, mode: "implied" }, + + // PLP + 0x28: { name: "PLP", op: this.plp, modeFn: this.implied, mode: "implied" }, + + // JMP + 0x4c: { + name: "JMP", + op: this.jmp, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x6c: { + name: "JMP", + op: this.jmp, + modeFn: this.readAddrAbsoluteIndirectBug, + mode: "absoluteIndirect", + }, + // JSR + 0x20: { + name: "JSR", + op: this.jsr, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + + // RTS + 0x60: { name: "RTS", op: this.rts, modeFn: this.implied, mode: "implied" }, + + // RTI + 0x40: { name: "RTI", op: this.rti, modeFn: this.implied, mode: "implied" }, + + // SEC + 0x38: { name: "SEC", op: this.set, modeFn: flags.C, mode: "implied" }, + + // SED + 0xf8: { name: "SED", op: this.set, modeFn: flags.D, mode: "implied" }, + + // SEI + 0x78: { name: "SEI", op: this.set, modeFn: flags.I, mode: "implied" }, + + // CLC + 0x18: { name: "CLC", op: this.clr, modeFn: flags.C, mode: "implied" }, + + // CLD + 0xd8: { name: "CLD", op: this.clr, modeFn: flags.D, mode: "implied" }, + + // CLI + 0x58: { name: "CLI", op: this.clr, modeFn: flags.I, mode: "implied" }, + + // CLV + 0xb8: { name: "CLV", op: this.clr, modeFn: flags.V, mode: "implied" }, + + // NOP + 0xea: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + + // BRK + 0x00: { + name: "BRK", + op: this.brk, + modeFn: this.readImmediate, + mode: "immediate", + }, + }; + + /* 65C02 Instructions */ + + OPS_65C02: Instructions = { + // INC / DEC A + 0x1a: { + name: "INC", + op: this.incA, + modeFn: this.implied, + mode: "accumulator", + }, + 0x3a: { + name: "DEC", + op: this.decA, + modeFn: this.implied, + mode: "accumulator", + }, + + // Indirect Zero Page for the masses + 0x12: { + name: "ORA", + op: this.ora, + modeFn: this.readZeroPageIndirect, + mode: "zeroPageIndirect", + }, + 0x32: { + name: "AND", + op: this.and, + modeFn: this.readZeroPageIndirect, + mode: "zeroPageIndirect", + }, + 0x52: { + name: "EOR", + op: this.eor, + modeFn: this.readZeroPageIndirect, + mode: "zeroPageIndirect", + }, + 0x72: { + name: "ADC", + op: this.adc, + modeFn: this.readZeroPageIndirect, + mode: "zeroPageIndirect", + }, + 0x92: { + name: "STA", + op: this.sta, + modeFn: this.writeZeroPageIndirect, + mode: "zeroPageIndirect", + }, + 0xb2: { + name: "LDA", + op: this.lda, + modeFn: this.readZeroPageIndirect, + mode: "zeroPageIndirect", + }, + 0xd2: { + name: "CMP", + op: this.cmp, + modeFn: this.readZeroPageIndirect, + mode: "zeroPageIndirect", + }, + 0xf2: { + name: "SBC", + op: this.sbc, + modeFn: this.readZeroPageIndirect, + mode: "zeroPageIndirect", + }, + + // Better BIT + 0x34: { + name: "BIT", + op: this.bit, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x3c: { + name: "BIT", + op: this.bit, + modeFn: this.readAbsoluteX, + mode: "absoluteX", + }, + 0x89: { + name: "BIT", + op: this.bitI, + modeFn: this.readImmediate, + mode: "immediate", + }, + + // JMP absolute indirect indexed + 0x6c: { + name: "JMP", + op: this.jmp, + modeFn: this.readAddrAbsoluteIndirect, + mode: "absoluteIndirect", + }, + 0x7c: { + name: "JMP", + op: this.jmp, + modeFn: this.readAddrAbsoluteXIndirect, + mode: "absoluteXIndirect", + }, + + // BBR/BBS + 0x0f: { name: "BBR0", op: this.bbr, modeFn: 0, mode: "zeroPage_relative" }, + 0x1f: { name: "BBR1", op: this.bbr, modeFn: 1, mode: "zeroPage_relative" }, + 0x2f: { name: "BBR2", op: this.bbr, modeFn: 2, mode: "zeroPage_relative" }, + 0x3f: { name: "BBR3", op: this.bbr, modeFn: 3, mode: "zeroPage_relative" }, + 0x4f: { name: "BBR4", op: this.bbr, modeFn: 4, mode: "zeroPage_relative" }, + 0x5f: { name: "BBR5", op: this.bbr, modeFn: 5, mode: "zeroPage_relative" }, + 0x6f: { name: "BBR6", op: this.bbr, modeFn: 6, mode: "zeroPage_relative" }, + 0x7f: { name: "BBR7", op: this.bbr, modeFn: 7, mode: "zeroPage_relative" }, + + 0x8f: { name: "BBS0", op: this.bbs, modeFn: 0, mode: "zeroPage_relative" }, + 0x9f: { name: "BBS1", op: this.bbs, modeFn: 1, mode: "zeroPage_relative" }, + 0xaf: { name: "BBS2", op: this.bbs, modeFn: 2, mode: "zeroPage_relative" }, + 0xbf: { name: "BBS3", op: this.bbs, modeFn: 3, mode: "zeroPage_relative" }, + 0xcf: { name: "BBS4", op: this.bbs, modeFn: 4, mode: "zeroPage_relative" }, + 0xdf: { name: "BBS5", op: this.bbs, modeFn: 5, mode: "zeroPage_relative" }, + 0xef: { name: "BBS6", op: this.bbs, modeFn: 6, mode: "zeroPage_relative" }, + 0xff: { name: "BBS7", op: this.bbs, modeFn: 7, mode: "zeroPage_relative" }, + + // BRA + 0x80: { name: "BRA", op: this.brc, modeFn: 0, mode: "relative" }, + + // NOP + 0x02: { + name: "NOP", + op: this.nop, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x22: { + name: "NOP", + op: this.nop, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x42: { + name: "NOP", + op: this.nop, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x44: { + name: "NOP", + op: this.nop, + modeFn: this.readZeroPage, + mode: "immediate", + }, + 0x54: { + name: "NOP", + op: this.nop, + modeFn: this.readZeroPageX, + mode: "immediate", + }, + 0x62: { + name: "NOP", + op: this.nop, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x82: { + name: "NOP", + op: this.nop, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xc2: { + name: "NOP", + op: this.nop, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xd4: { + name: "NOP", + op: this.nop, + modeFn: this.readZeroPageX, + mode: "immediate", + }, + 0xe2: { + name: "NOP", + op: this.nop, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xf4: { + name: "NOP", + op: this.nop, + modeFn: this.readZeroPageX, + mode: "immediate", + }, + 0x5c: { name: "NOP", op: this.nop, modeFn: this.readNop, mode: "absolute" }, + 0xdc: { name: "NOP", op: this.nop, modeFn: this.readNop, mode: "absolute" }, + 0xfc: { name: "NOP", op: this.nop, modeFn: this.readNop, mode: "absolute" }, + + // PHX + 0xda: { name: "PHX", op: this.phx, modeFn: this.implied, mode: "implied" }, + + // PHY + 0x5a: { name: "PHY", op: this.phy, modeFn: this.implied, mode: "implied" }, + + // PLX + 0xfa: { name: "PLX", op: this.plx, modeFn: this.implied, mode: "implied" }, + + // PLY + 0x7a: { name: "PLY", op: this.ply, modeFn: this.implied, mode: "implied" }, + + // RMB/SMB + + 0x07: { name: "RMB0", op: this.rmb, modeFn: 0, mode: "zeroPage" }, + 0x17: { name: "RMB1", op: this.rmb, modeFn: 1, mode: "zeroPage" }, + 0x27: { name: "RMB2", op: this.rmb, modeFn: 2, mode: "zeroPage" }, + 0x37: { name: "RMB3", op: this.rmb, modeFn: 3, mode: "zeroPage" }, + 0x47: { name: "RMB4", op: this.rmb, modeFn: 4, mode: "zeroPage" }, + 0x57: { name: "RMB5", op: this.rmb, modeFn: 5, mode: "zeroPage" }, + 0x67: { name: "RMB6", op: this.rmb, modeFn: 6, mode: "zeroPage" }, + 0x77: { name: "RMB7", op: this.rmb, modeFn: 7, mode: "zeroPage" }, + + 0x87: { name: "SMB0", op: this.smb, modeFn: 0, mode: "zeroPage" }, + 0x97: { name: "SMB1", op: this.smb, modeFn: 1, mode: "zeroPage" }, + 0xa7: { name: "SMB2", op: this.smb, modeFn: 2, mode: "zeroPage" }, + 0xb7: { name: "SMB3", op: this.smb, modeFn: 3, mode: "zeroPage" }, + 0xc7: { name: "SMB4", op: this.smb, modeFn: 4, mode: "zeroPage" }, + 0xd7: { name: "SMB5", op: this.smb, modeFn: 5, mode: "zeroPage" }, + 0xe7: { name: "SMB6", op: this.smb, modeFn: 6, mode: "zeroPage" }, + 0xf7: { name: "SMB7", op: this.smb, modeFn: 7, mode: "zeroPage" }, + + // STZ + 0x64: { + name: "STZ", + op: this.stz, + modeFn: this.writeZeroPage, + mode: "zeroPage", + }, + 0x74: { + name: "STZ", + op: this.stz, + modeFn: this.writeZeroPageX, + mode: "zeroPageX", + }, + 0x9c: { + name: "STZ", + op: this.stz, + modeFn: this.writeAbsolute, + mode: "absolute", + }, + 0x9e: { + name: "STZ", + op: this.stz, + modeFn: this.writeAbsoluteX, + mode: "absoluteX", + }, + + // TRB + 0x14: { + name: "TRB", + op: this.trb, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x1c: { + name: "TRB", + op: this.trb, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + + // TSB + 0x04: { + name: "TSB", + op: this.tsb, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x0c: { + name: "TSB", + op: this.tsb, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + }; + + OPS_NMOS_6502: Instructions = { + // ASO + 0x0f: { + name: "ASO", + op: this.aso, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x1f: { + name: "ASO", + op: this.aso, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0x1b: { + name: "ASO", + op: this.aso, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + 0x07: { + name: "ASO", + op: this.aso, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x17: { + name: "ASO", + op: this.aso, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x03: { + name: "ASO", + op: this.aso, + modeFn: this.readAddrZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x13: { + name: "ASO", + op: this.aso, + modeFn: this.readAddrZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // RLA + 0x2f: { + name: "RLA", + op: this.rla, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x3f: { + name: "RLA", + op: this.rla, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0x3b: { + name: "RLA", + op: this.rla, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + 0x27: { + name: "RLA", + op: this.rla, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x37: { + name: "RLA", + op: this.rla, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x23: { + name: "RLA", + op: this.rla, + modeFn: this.readAddrZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x33: { + name: "RLA", + op: this.rla, + modeFn: this.readAddrZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // LSE + 0x4f: { + name: "LSE", + op: this.lse, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x5f: { + name: "LSE", + op: this.lse, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0x5b: { + name: "LSE", + op: this.lse, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + 0x47: { + name: "LSE", + op: this.lse, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x57: { + name: "LSE", + op: this.lse, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x43: { + name: "LSE", + op: this.lse, + modeFn: this.readAddrZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x53: { + name: "LSE", + op: this.lse, + modeFn: this.readAddrZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // RRA + 0x6f: { + name: "RRA", + op: this.rra, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x7f: { + name: "RRA", + op: this.rra, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0x7b: { + name: "RRA", + op: this.rra, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + 0x67: { + name: "RRA", + op: this.rra, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0x77: { + name: "RRA", + op: this.rra, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0x63: { + name: "RRA", + op: this.rra, + modeFn: this.readAddrZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0x73: { + name: "RRA", + op: this.rra, + modeFn: this.readAddrZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // AXS + 0x8f: { + name: "AXS", + op: this.axs, + modeFn: this.writeAbsolute, + mode: "absolute", + }, + 0x87: { + name: "AXS", + op: this.axs, + modeFn: this.writeZeroPage, + mode: "zeroPage", + }, + 0x97: { + name: "AXS", + op: this.axs, + modeFn: this.writeZeroPageY, + mode: "zeroPageY", + }, + 0x83: { + name: "AXS", + op: this.axs, + modeFn: this.writeZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + + // LAX + 0xaf: { + name: "LAX", + op: this.lax, + modeFn: this.readAbsolute, + mode: "absolute", + }, + 0xbf: { + name: "LAX", + op: this.lax, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + 0xa7: { + name: "LAX", + op: this.lax, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0xb7: { + name: "LAX", + op: this.lax, + modeFn: this.readZeroPageY, + mode: "zeroPageY", + }, + 0xa3: { + name: "LAX", + op: this.lax, + modeFn: this.readZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0xb3: { + name: "LAX", + op: this.lax, + modeFn: this.readZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // DCM + 0xcf: { + name: "DCM", + op: this.dcm, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0xdf: { + name: "DCM", + op: this.dcm, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0xdb: { + name: "DCM", + op: this.dcm, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + 0xc7: { + name: "DCM", + op: this.dcm, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0xd7: { + name: "DCM", + op: this.dcm, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0xc3: { + name: "DCM", + op: this.dcm, + modeFn: this.readAddrZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0xd3: { + name: "DCM", + op: this.dcm, + modeFn: this.readAddrZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // INS + 0xef: { + name: "INS", + op: this.ins, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0xff: { + name: "INS", + op: this.ins, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0xfb: { + name: "INS", + op: this.ins, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + 0xe7: { + name: "INS", + op: this.ins, + modeFn: this.readAddrZeroPage, + mode: "zeroPage", + }, + 0xf7: { + name: "INS", + op: this.ins, + modeFn: this.readAddrZeroPageX, + mode: "zeroPageX", + }, + 0xe3: { + name: "INS", + op: this.ins, + modeFn: this.readAddrZeroPageXIndirect, + mode: "zeroPageXIndirect", + }, + 0xf3: { + name: "INS", + op: this.ins, + modeFn: this.readAddrZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // ALR + 0x4b: { + name: "ALR", + op: this.alr, + modeFn: this.readImmediate, + mode: "immediate", + }, + + // ARR + 0x6b: { + name: "ARR", + op: this.arr, + modeFn: this.readImmediate, + mode: "immediate", + }, + + // XAA + 0x8b: { + name: "XAA", + op: this.xaa, + modeFn: this.readImmediate, + mode: "immediate", + }, + + // OAL + 0xab: { + name: "OAL", + op: this.oal, + modeFn: this.readImmediate, + mode: "immediate", + }, + + // SAX + 0xcb: { + name: "SAX", + op: this.sax, + modeFn: this.readImmediate, + mode: "immediate", + }, + + // NOP + 0x1a: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + 0x3a: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + 0x5a: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + 0x7a: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + 0xda: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + 0xfa: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + + // SKB + 0x80: { + name: "SKB", + op: this.skp, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x82: { + name: "SKB", + op: this.skp, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x89: { + name: "SKB", + op: this.skp, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xc2: { + name: "SKB", + op: this.skp, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0xe2: { + name: "SKB", + op: this.skp, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x04: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x14: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x34: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x44: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x54: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0x64: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPage, + mode: "zeroPage", + }, + 0x74: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0xd4: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + 0xf4: { + name: "SKB", + op: this.skp, + modeFn: this.readZeroPageX, + mode: "zeroPageX", + }, + + // SKW + 0x0c: { + name: "SKW", + op: this.skp, + modeFn: this.readAddrAbsolute, + mode: "absolute", + }, + 0x1c: { + name: "SKW", + op: this.skp, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0x3c: { + name: "SKW", + op: this.skp, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0x5c: { + name: "SKW", + op: this.skp, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0x7c: { + name: "SKW", + op: this.skp, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0xdc: { + name: "SKW", + op: this.skp, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + 0xfc: { + name: "SKW", + op: this.skp, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // HLT + 0x02: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x12: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x22: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x32: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x42: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x52: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x62: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x72: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0x92: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0xb2: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0xd2: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + 0xf2: { + name: "HLT", + op: this.hlt, + modeFn: this.readNopImplied, + mode: "implied", + }, + + // TAS + 0x9b: { + name: "TAS", + op: this.tas, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + + // SAY + 0x9c: { + name: "SAY", + op: this.say, + modeFn: this.readAddrAbsoluteX, + mode: "absoluteX", + }, + + // XAS + 0x9e: { + name: "XAS", + op: this.xas, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + + // AXA + 0x9f: { + name: "AXA", + op: this.axa, + modeFn: this.readAddrAbsoluteY, + mode: "absoluteY", + }, + 0x93: { + name: "AXA", + op: this.axa, + modeFn: this.readAddrZeroPageIndirectY, + mode: "zeroPageIndirectY", + }, + + // ANC + 0x2b: { + name: "ANC", + op: this.anc, + modeFn: this.readImmediate, + mode: "immediate", + }, + 0x0b: { + name: "ANC", + op: this.anc, + modeFn: this.readImmediate, + mode: "immediate", + }, + + // LAS + 0xbb: { + name: "LAS", + op: this.las, + modeFn: this.readAbsoluteY, + mode: "absoluteY", + }, + + // SBC + 0xeb: { + name: "SBC", + op: this.sbc, + modeFn: this.readImmediate, + mode: "immediate", + }, + }; + + OPS_ROCKWELL_65C02: Instructions = { + 0xcb: { name: "NOP", op: this.nop, modeFn: this.implied, mode: "implied" }, + 0xdb: { + name: "NOP", + op: this.nop, + modeFn: this.readZeroPageX, + mode: "immediate", + }, + }; + + /* WDC 65C02 Instructions */ + + OPS_WDC_65C02: Instructions = { + 0xcb: { name: "WAI", op: this.wai, modeFn: this.implied, mode: "implied" }, + 0xdb: { name: "STP", op: this.stp, modeFn: this.implied, mode: "implied" }, + }; +} diff --git a/js/types.ts b/js/types.ts index 96e0bc9..33f2818 100644 --- a/js/types.ts +++ b/js/types.ts @@ -16,3 +16,28 @@ export type word = number; export type address = word; export type memory = Uint8Array | byte[]; + +export interface Memory { + /** Read a byte. */ + read(page: byte, offset: byte): byte; + /** Write a byte. */ + write(page: byte, offset: byte, value: byte): void; +} + +/** A mapped region of memory. */ +export interface MemoryPages extends Memory { + /** Start page. */ + start(): byte; + /** End page, inclusive. */ + end(): byte; +} + +/** + * Extracts the members of a constant array as a type. Used as: + * + * @example + * const SOME_VALUES = ['a', 'b', 1, 2] as const; + * type SomeValues = MemberOf; // 'a' | 'b' | 1 | 2 + */ +export type MemberOf> = + T extends ReadonlyArray ? E : never;