From c4df78cf06b501269c599f0c8a0b993644c0f761 Mon Sep 17 00:00:00 2001 From: Ian Flanigan Date: Sun, 1 Nov 2020 17:43:48 +0100 Subject: [PATCH] Typescript conversion of several files, including js/cpu6502 (#38) * Convert `js/util.js` to Typescript and add tests Besides converting `js/util.js` to Typescript, this change also adds `js/types.ts` that defines common types used in apple2js. Some of these types, like `byte` and `word` are for information only. * Convert `js/base64.js` to Typescript This also adds a new type, `memory`, that is either an array of numbers, or a Uint8Array. * Convert `js/ram.js` to Typescript This change does not convert `RAM` to a class; it just introduces types. * Basic typing of cpu6502 This is a really rough first pass. There are some problems that can't be fixed until this is turned into a real class, but at least all of the function arguments are now typed. This caught a few cases where extra arguments were being passed in. * Convert `js/cpu6502` to a class In theory, idiomatic classes should be better than the previous closure-based classes. However, this conversion shows that the instruction table does not fit well with idiomatic classes as method referenced in the table need to be called with the correct `this` everywhere. This should, at best, be considered a first attempt. --- js/{base64.js => base64.ts} | 31 +- js/cpu6502.js | 1633 -------------------------------- js/cpu6502.ts | 1759 +++++++++++++++++++++++++++++++++++ js/{ram.js => ram.ts} | 39 +- js/symbols.js | 2 +- js/types.ts | 27 + js/{util.js => util.ts} | 81 +- test/js/util.test.ts | 99 ++ 8 files changed, 1989 insertions(+), 1682 deletions(-) rename js/{base64.js => base64.ts} (75%) delete mode 100644 js/cpu6502.js create mode 100644 js/cpu6502.ts rename js/{ram.js => ram.ts} (54%) create mode 100644 js/types.ts rename js/{util.js => util.ts} (50%) create mode 100644 test/js/util.test.ts diff --git a/js/base64.js b/js/base64.ts similarity index 75% rename from js/base64.js rename to js/base64.ts index f40cf2e..62325b8 100644 --- a/js/base64.js +++ b/js/base64.ts @@ -1,4 +1,9 @@ -export function base64_encode (data) { +import { byte, memory } from "./types"; + +const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + +/** Encode an array of bytes in base64. */ +export function base64_encode(data: memory) { // Twacked by Will Scullin to handle arrays of "bytes" // http://kevin.vanzonneveld.net @@ -18,7 +23,7 @@ export function base64_encode (data) { // return atob(data); //} - var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc='', tmp_arr = []; if (!data) { @@ -38,7 +43,7 @@ export function base64_encode (data) { h4 = bits & 0x3f; // use hexets to index into b64, and append result to encoded string - tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); + tmp_arr[ac++] = B64.charAt(h1) + B64.charAt(h2) + B64.charAt(h3) + B64.charAt(h4); } while (i < data.length); enc = tmp_arr.join(''); @@ -55,7 +60,12 @@ export function base64_encode (data) { return enc; } -export function base64_decode(data) { +/** Returns undefined if the input is null or undefined. */ +export function base64_decode(data: null | undefined): undefined; +/** Returns an array of bytes from the given base64-encoded string. */ +export function base64_decode(data: string): memory; +/** Returns an array of bytes from the given base64-encoded string. */ +export function base64_decode(data: string | null | undefined): memory | undefined { // Twacked by Will Scullin to handle arrays of "bytes" // http://kevin.vanzonneveld.net @@ -78,18 +88,17 @@ export function base64_decode(data) { // return btoa(data); //} - var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = []; if (!data) { - return data; + return undefined; } - do { // unpack four hexets into three octets using index points in b64 - h1 = b64.indexOf(data.charAt(i++)); - h2 = b64.indexOf(data.charAt(i++)); - h3 = b64.indexOf(data.charAt(i++)); - h4 = b64.indexOf(data.charAt(i++)); + do { // unpack four hexets into three octets using index points in B64 + h1 = B64.indexOf(data.charAt(i++)); + h2 = B64.indexOf(data.charAt(i++)); + h3 = B64.indexOf(data.charAt(i++)); + h4 = B64.indexOf(data.charAt(i++)); bits = h1<<18 | h2<<12 | h3<<6 | h4; diff --git a/js/cpu6502.js b/js/cpu6502.js deleted file mode 100644 index fa97340..0000000 --- a/js/cpu6502.js +++ /dev/null @@ -1,1633 +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() { - var addr = pc, - page = addr >> 8, - off = addr & 0xff; - - var result = readPages[page].read(page, off); - - pc = (pc + 1) & 0xffff; - - cycles++; - - return result; - } - - function readByte(addr) { - var page = addr >> 8, - off = addr & 0xff; - - var result = readPages[page].read(page, off); - - cycles++; - - return result; - } - - function readByteDebug(addr) { - var page = addr >> 8, - off = addr & 0xff; - - return readPages[page].read(page, off); - } - - function writeByte(addr, val) { - var page = addr >> 8, - off = addr & 0xff; - - writePages[page].write(page, off, val); - - cycles++; - } - - function readWord(addr) { - return readByte(addr) | (readByte(addr + 1) << 8); - } - - function readWordDebug(addr) { - return readByteDebug(addr) | (readByteDebug(addr + 1) << 8); - } - - function readWordPC() { - return readBytePC() | (readBytePC() << 8); - } - - function readZPWord(addr) { - var lsb, msb; - - lsb = readByte(addr & 0xff); - msb = readByte((addr + 1) & 0xff); - - 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) { - Object.assign(ops, cops); - } - - 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(readByteDebug(addr)); - break; - case modes.absolute: - result = '' + toHexOrSymbol(readWordDebug(addr), 4); - break; - case modes.zeroPage: - result = '' + toHexOrSymbol(readByteDebug(addr)); - break; - case modes.relative: - { - off = readByteDebug(addr); - if (off > 127) { - off -= 256; - } - addr += off + 1; - result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')'; - } - break; - case modes.absoluteX: - result = '' + toHexOrSymbol(readWordDebug(addr), 4) + ',X'; - break; - case modes.absoluteY: - result = '' + toHexOrSymbol(readWordDebug(addr), 4) + ',Y'; - break; - case modes.zeroPageX: - result = '' + toHexOrSymbol(readByteDebug(addr)) + ',X'; - break; - case modes.zeroPageY: - result = '' + toHexOrSymbol(readByteDebug(addr)) + ',Y'; - break; - case modes.absoluteIndirect: - result = '(' + toHexOrSymbol(readWordDebug(addr), 4) + ')'; - break; - case modes.zeroPageXIndirect: - result = '(' + toHexOrSymbol(readByteDebug(addr)) + ',X)'; - break; - case modes.zeroPageIndirectY: - result = '(' + toHexOrSymbol(readByteDebug(addr)) + '),Y'; - break; - case modes.accumulator: - result = 'A'; - break; - case modes.zeroPageIndirect: - result = '(' + toHexOrSymbol(readByteDebug(addr)) + ')'; - break; - case modes.absoluteXIndirect: - result = '(' + toHexOrSymbol(readWordDebug(addr), 4) + ',X)'; - break; - case modes.zeroPage_relative: - val = readByteDebug(addr); - off = readByteDebug(addr + 1); - 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 = readByteDebug(page * 256 + idx * 16 + jdx); - 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); - }, - - 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..56eefa5 --- /dev/null +++ b/js/cpu6502.ts @@ -0,0 +1,1759 @@ +/* + * 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 { byte, word } from './types'; +import { debug, toHex } from './util'; + +type symbols = { [key: number]: string }; + +export interface CpuOptions { + '65C02'?: boolean; +} + +export interface CpuState { + a: byte, + x: byte, + y: byte, + s: byte, + pc: word, + sp: byte, + cycles: number +} + +/** Range of mode numbers. */ +type mode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15; + +/** Addressing mode name to number mapping. */ +const modes: { [key: string]: mode } = { + 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 +}; + +/** Instruction size by addressing mode. */ +const 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 +}; + +/** Status register flag numbers. */ +type flag = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + +/** Flags to status byte mask. */ +const flags = { + N: 0x80, // Negative + V: 0x40, // oVerflow + 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 ReadablePage { + read(page: byte, offset: byte): byte; +} + +function isReadablePage(page: ReadablePage | any): page is ReadablePage { + return (page as ReadablePage).read !== undefined; +} + +interface WriteablePage { + write(page: byte, offset: byte, value: byte): void; +} + +function isWriteablePage(page: WriteablePage | any): page is WriteablePage { + return (page as WriteablePage).write !== undefined; +} + +interface PageHandler { + start(): byte; + end(): byte; +} + +function isResettablePageHandler(pageHandler: PageHandler | ResettablePageHandler): pageHandler is ResettablePageHandler { + return (pageHandler as ResettablePageHandler).reset !== undefined; +} + +interface ResettablePageHandler extends PageHandler { + reset(): void; +} + +const BLANK_PAGE: ReadablePage & WriteablePage = { + read: function() { return 0; }, + write: function() {} +}; + +interface Opts { + rwm?: boolean; +} + +type ReadFn = () => byte; +type WriteFn = (val: byte) => void; +type ReadAddrFn = (opts?: Opts) => word; + +type readInstruction = [ + desc: string, + op: (readFn: ReadFn) => void, + modeFn: ReadFn, + mode: mode, // really 0-15 + cycles: number, +]; + +type writeInstruction = [ + desc: string, + op: (writeFn: WriteFn) => void, + modeFn: WriteFn, + mode: mode, // really 0-15 + cycles: number, +]; + +type impliedInstruction = [ + desc: string, + op: () => void, + modeFn: null, + mode: mode, // really 0-15 + cycles: number, +]; + +type relativeInstruction = [ + desc: string, + op: (f: byte) => void, + modeFn: byte, + mode: mode, // really 0-15 + cycles: number, +]; + +type noopInstruction = [ + desc: string, + op: (readAddrFn: ReadAddrFn) => void, + modeFn: () => void, + mode: mode, // really 0-15 + cycles: number, +]; + +type instruction = + readInstruction | writeInstruction | + impliedInstruction | relativeInstruction | noopInstruction; + +interface Instructions { + [key: number]: instruction; +} + +type callback = (cpu: any) => void; // TODO(flan): Hack until there is better typing. + +function bind(op: instruction, o: CPU6502): instruction { + let op2: instruction[2] = typeof op[2] === "function" ? op[2].bind(o) : op[2]; + return [op[0], op[1].bind(o), op2, op[3], op[4]]; +} + +export default class CPU6502 { + private readonly is65C02; + + /* Registers */ + private pc = 0; // Program Counter + private sr = 0x20; // Process Status Register + private ar = 0; // Accumulator + private xr = 0; // X Register + private yr = 0; // Y Register + private sp = 0xff; // Stack Pointer + + private readPages: ReadablePage[] = []; + private writePages: WriteablePage[] = []; + private resetHandlers: ResettablePageHandler[] = []; + private cycles = 0; + private sync = false; + + private readonly ops: Instructions; + private readonly opary: instruction[]; + + constructor(options: CpuOptions = {}) { + this.is65C02 = options['65C02'] ? true : false; + + for (let idx = 0; idx < 0x100; idx++) { + this.readPages[idx] = BLANK_PAGE; + this.writePages[idx] = BLANK_PAGE; + } + + // Create this CPU's instruction table + let ops: Instructions = []; + Object.assign(ops, OPS_6502); + if (this.is65C02) { + Object.assign(ops, OPS_65C02); + } + this.ops = ops; + + // Certain browsers benefit from using arrays over maps + let opary: instruction[] = []; + + for (let idx = 0; idx < 0x100; idx++) { + opary[idx] = bind(ops[idx] || this.unknown(idx), this); + } + this.opary = opary; + } + + /** + * Set or clears `f` in the status register. `f` must be a byte with a + * single bit set. + */ + private setFlag(f: byte, 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) { + if (sub) + b ^= 0xff; + + // KEGS + var c, v; + if ((this.sr & flags.D) !== 0) { + // BCD + c = (a & 0x0f) + (b & 0x0f) + (this.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 + (this.sr & flags.C); + v = (c ^ a) & 0x80; + } + + if (((a ^ b) & 0x80) !== 0) { + v = 0; + } + + this.setFlag(flags.C, c > 0xff); + this.setFlag(flags.V, !!v); + + return this.testNZ(c & 0xff); + } + + /** 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 { + let addr = this.pc, + page = addr >> 8, + off = addr & 0xff; + + var result = this.readPages[page].read(page, off); + + this.pc = (this.pc + 1) & 0xffff; + + this.cycles++; + + return result; + } + + private readByte(addr: word): byte { + var page = addr >> 8, + off = addr & 0xff; + + var result = this.readPages[page].read(page, off); + + this.cycles++; + + return result; + } + + private readByteDebug(addr: word) { + var page = addr >> 8, + off = addr & 0xff; + + return this.readPages[page].read(page, off); + } + + private writeByte(addr: word, val: byte) { + var page = addr >> 8, + off = addr & 0xff; + + this.writePages[page].write(page, off, val); + + this.cycles++; + } + + private readWord(addr: word): word { + return this.readByte(addr) | (this.readByte(addr + 1) << 8); + } + + private readWordDebug(addr: word): word { + return this.readByteDebug(addr) | (this.readByteDebug(addr + 1) << 8); + } + + private readWordPC(): word { + return this.readBytePC() | (this.readBytePC() << 8); + } + + private readZPWord(addr: byte): word { + var lsb, msb; + + lsb = this.readByte(addr & 0xff); + 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 { + var lsb = this.pullByte(); + var msb = this.pullByte(); + + return (msb << 8) | lsb; + } + + /* + * Read functions + */ + + readImplied() { + } + + // #$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 { + var addr = this.readWordPC(); + var oldPage = addr >> 8; + addr = (addr + this.xr) & 0xffff; + var newPage = addr >> 8; + if (newPage != oldPage) { + var off = addr & 0xff; + this.readByte(oldPage << 8 | off); + } + return this.readByte(addr); + } + + // $0000,Y + readAbsoluteY(): byte { + var addr = this.readWordPC(); + var oldPage = addr >> 8; + addr = (addr + this.yr) & 0xffff; + var newPage = addr >> 8; + if (newPage != oldPage) { + var off = addr & 0xff; + this.readByte(oldPage << 8 | off); + } + return this.readByte(addr); + } + + // $00,X + readZeroPageX(): byte { + var zpAddr = this.readBytePC(); + this.readByte(zpAddr); + return this.readByte((zpAddr + this.xr) & 0xff); + } + + // $00,Y + readZeroPageY(): byte { + var zpAddr = this.readBytePC(); + this.readByte(zpAddr); + return this.readByte((zpAddr + this.yr) & 0xff); + } + + // ($00,X) + readZeroPageXIndirect(): byte { + var zpAddr = this.readBytePC(); + this.readByte(zpAddr); + var addr = this.readZPWord((zpAddr + this.xr) & 0xff); + return this.readByte(addr); + } + + // ($00),Y + readZeroPageIndirectY(): byte { + var addr = this.readZPWord(this.readBytePC()); + var oldPage = addr >> 8; + addr = (addr + this.yr) & 0xffff; + var newPage = addr >> 8; + if (newPage != oldPage) { + var off = addr & 0xff; + this.readByte(oldPage << 8 | off); + } + return this.readByte(addr); + } + + // ($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) { + var addr = this.readWordPC(), oldPage = addr >> 8; + addr = (addr + this.xr) & 0xffff; + var off = addr & 0xff; + this.readByte(oldPage << 8 | off); + this.writeByte(addr, val); + } + + // $0000,Y + writeAbsoluteY(val: byte) { + var addr = this.readWordPC(), oldPage = addr >> 8; + addr = (addr + this.yr) & 0xffff; + var off = addr & 0xff; + this.readByte(oldPage << 8 | off); + this.writeByte(addr, val); + } + + // $00,X + writeZeroPageX(val: byte) { + var zpAddr = this.readBytePC(); + this.readByte(zpAddr); + this.writeByte((zpAddr + this.xr) & 0xff, val); + } + + // $00,Y + writeZeroPageY(val: byte) { + var zpAddr = this.readBytePC(); + this.readByte(zpAddr); + this.writeByte((zpAddr + this.yr) & 0xff, val); + } + + // ($00,X) + writeZeroPageXIndirect(val: byte) { + var zpAddr = this.readBytePC(); + this.readByte(zpAddr); + var addr = this.readZPWord((zpAddr + this.xr) & 0xff); + this.writeByte(addr, val); + } + + // ($00),Y + writeZeroPageIndirectY(val: byte) { + var addr = this.readZPWord(this.readBytePC()), oldPage = addr >> 8; + addr = (addr + this.yr) & 0xffff; + var off = addr & 0xff; + this.readByte(oldPage << 8 | off); + this.writeByte(addr, val); + } + + // ($00) (65C02) + writeZeroPageIndirect(val: byte) { + this.writeByte(this.readZPWord(this.readBytePC()), val); + } + + // $00 + readAddrZeroPage(): byte { + return this.readBytePC(); + } + + // $00,X + readAddrZeroPageX() { + var zpAddr = this.readBytePC(); + this.readByte(zpAddr); + return (zpAddr + this.xr) & 0xff; + } + + // $0000 (65C02) + readAddrAbsolute(): word { + return this.readWordPC(); + } + + // ($0000) (6502) + readAddrAbsoluteIndirectBug(): word { + var addr = this.readWordPC(); + var page = addr & 0xff00; + var off = addr & 0x00ff; + var lsb = this.readByte(addr); + var msb = this.readByte(page | ((off + 0x01) & 0xff)); + return msb << 8 | lsb; + } + + // ($0000) (65C02) + readAddrAbsoluteIndirect(): word { + var lsb = this.readBytePC(); + var msb = this.readBytePC(); + this.readByte(this.pc); + return this.readWord(msb << 8 | lsb); + } + + // $0000,X + readAddrAbsoluteX(opts: Opts = {}): word { + var addr = this.readWordPC(); + if (!this.is65C02 || opts.rwm) { + this.readByte(addr); + } else { + this.readByte(this.pc); + } + return (addr + this.xr) & 0xffff; + } + + // $(0000,X) (65C02) + readAddrAbsoluteXIndirect(): word { + var address = this.readWordPC(); + this.readByte(this.pc); + return this.readWord((address + this.xr) & 0xffff); + } + + /* 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); + } + + /* 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(), /* sub= */ true); + } + + /* Increment Memory */ + incA() { + this.readByte(this.pc); + this.ar = this.increment(this.ar); + } + + inc(readAddrFn: ReadAddrFn) { + var addr = readAddrFn({rwm: true}); + var oldVal = this.readByte(addr); + this.writeByte(addr, oldVal); + var 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) { + var addr = readAddrFn({rwm: true}); + var oldVal = this.readByte(addr); + this.writeByte(addr, oldVal); + var 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) { + var addr = readAddrFn({rwm: true}); + var oldVal = this.readByte(addr); + this.writeByte(addr, oldVal); + var 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) { + var addr = readAddrFn({rwm: true}); + var oldVal = this.readByte(addr); + this.writeByte(addr, oldVal); + var val = this.shiftRight(oldVal); + this.writeByte(addr, val); + } + + rotateLeft(val: byte) { + var 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) { + var addr = readAddrFn({rwm: true}); + var oldVal = this.readByte(addr); + this.writeByte(addr, oldVal); + var val = this.rotateLeft(oldVal); + this.writeByte(addr, val); + } + + private rotateRight(a: byte) { + var 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) { + var addr = readAddrFn({rwm: true}); + var oldVal = this.readByte(addr); + this.writeByte(addr, oldVal); + var 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) { + var bit = (0x1 << b) ^ 0xFF; + var addr = this.readBytePC(); + var val = this.readByte(addr); + this.readByte(addr); + val &= bit; + this.writeByte(addr, val); + } + + /* Set Bit */ + + smb(b: byte) { + var bit = 0x1 << b; + var addr = this.readBytePC(); + var val = this.readByte(addr); + this.readByte(addr); + val |= bit; + this.writeByte(addr, val); + } + + /* Test and Reset Bits */ + trb(readAddrFn: ReadAddrFn) { + var addr = readAddrFn(); + var 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) { + var addr = readAddrFn(); + var val = this.readByte(addr); + this.testZ(val & this.ar); + this.readByte(addr); + this.writeByte(addr, val | this.ar); + } + + /* Bit */ + bit(readFn: ReadFn) { + var 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) { + var val = readFn(); + this.setFlag(flags.Z, (val & this.ar) === 0); + } + + private compare(a: byte, b: byte) { + b = (b ^ 0xff); + var 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) { + let off = this.readBytePC(); // changes pc + if ((f & this.sr) !== 0) { + this.readByte(this.pc); + let oldPage = this.pc >> 8; + this.pc += off > 127 ? off - 256 : off; + let newPage = this.pc >> 8; + let newOff = this.pc & 0xff; + if (newPage != oldPage) this.readByte(oldPage << 8 | newOff); + } + } + + brc(f: flag) { + let off = this.readBytePC(); // changes pc + if ((f & this.sr) === 0) { + this.readByte(this.pc); + let oldPage = this.pc >> 8; + this.pc += off > 127 ? off - 256 : off; + let newPage = this.pc >> 8; + let newOff = this.pc & 0xff; + if (newPage != oldPage) this.readByte(oldPage << 8 | newOff); + } + } + + /* WDC 65C02 branches */ + + bbr(b: flag) { + let zpAddr = this.readBytePC(); + let val = this.readByte(zpAddr); + this.readByte(zpAddr); + let off = this.readBytePC(); // changes pc + + if (((1 << b) & val) === 0) { + let oldPc = this.pc; + let oldPage = oldPc >> 8; + this.readByte(oldPc); + this.pc += off > 127 ? off - 256 : off; + let newPage = this.pc >> 8; + if (oldPage != newPage) { + this.readByte(oldPc); + } + } + } + + bbs(b: flag) { + let zpAddr = this.readBytePC(); + let val = this.readByte(zpAddr); + this.readByte(zpAddr); + let off = this.readBytePC(); // changes pc + + if (((1 << b) & val) !== 0) { + let oldPc = this.pc; + let oldPage = oldPc >> 8; + this.readByte(oldPc); + this.pc += off > 127 ? off - 256 : off; + let newPage = this.pc >> 8; + if (oldPage != newPage) { + this.readByte(oldPc); + } + } + } + + /* 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) | 0x20; } + + /* Jump */ + jmp(readAddrFn: ReadAddrFn) { + this.pc = readAddrFn(); + } + + /* Jump Subroutine */ + jsr() { + let lsb = this.readBytePC(); + this.readByte(0x0100 | this.sp); + this.pushWord(this.pc); + let msb = this.readBytePC(); + this.pc = (msb << 8 | lsb) & 0xffff; + } + + /* Return from Subroutine */ + rts() { + this.readByte(this.pc); + this.readByte(0x0100 | this.sp); + let 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; + 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(readAddrFn: ReadAddrFn) { + this.readByte(this.pc); + readAddrFn(); + } + + private unknown(b: byte) { + let unk: noopInstruction; + + if (this.is65C02) { + unk = [ + 'NOP', + this.nop, + this.readImplied, + modes.implied, + 2 + ]; + } else { + unk = [ + '???', + function() { + debug('Unknown OpCode: ' + toHex(b) + + ' at ' + toHex(this.pc - 1, 4)); + }, + this.readImplied, + modes.implied, + 1 + ]; + } + this.ops[b] = unk; + return unk; + } + + private dumpArgs(addr: word, m: mode, symbols: symbols) { + function toHexOrSymbol(v: word, n?: number) { + if (symbols && symbols[v]) { + return symbols[v]; + } else { + return '$' + toHex(v, n); + } + } + + let result = ''; + switch (m) { + case modes.implied: + break; + case modes.immediate: + result = '#' + toHexOrSymbol(this.readByteDebug(addr)); + break; + case modes.absolute: + result = '' + toHexOrSymbol(this.readWordDebug(addr), 4); + break; + case modes.zeroPage: + result = '' + toHexOrSymbol(this.readByteDebug(addr)); + break; + case modes.relative: + { + let off = this.readByteDebug(addr); + if (off > 127) { + off -= 256; + } + addr += off + 1; + result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')'; + } + break; + case modes.absoluteX: + result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X'; + break; + case modes.absoluteY: + result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',Y'; + break; + case modes.zeroPageX: + result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',X'; + break; + case modes.zeroPageY: + result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',Y'; + break; + case modes.absoluteIndirect: + result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ')'; + break; + case modes.zeroPageXIndirect: + result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ',X)'; + break; + case modes.zeroPageIndirectY: + result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + '),Y'; + break; + case modes.accumulator: + result = 'A'; + break; + case modes.zeroPageIndirect: + result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ')'; + break; + case modes.absoluteXIndirect: + result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X)'; + break; + case modes.zeroPage_relative: + let val = this.readByteDebug(addr); + let off = this.readByteDebug(addr + 1); + if (off > 127) { + off -= 256; + } + addr += off + 2; + result = '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(addr, 4) + ' (' + off + ')'; + break; + default: + break; + } + return result; + } + + public step(cb: callback) { + this.sync = true; + let op = this.opary[this.readBytePC()]; + this.sync = false; + op[1](op[2] as any); // TODO(flan): Hack until there is better typing. + + if (cb) { + cb(this); + } + } + + public stepDebug(n: number, cb: callback) { + for (let idx = 0; idx < n; idx++) { + this.sync = true; + let op = this.opary[this.readBytePC()]; + this.sync = false; + op[1](op[2] as any); // TODO(flan): Hack until there is better typing. + + if (cb) { + cb(this); + } + } + } + + public stepCycles(c: number) { + let end = this.cycles + c; + + while (this.cycles < end) { + this.sync = true; + let op = this.opary[this.readBytePC()]; + this.sync = false; + op[1](op[2] as any); // TODO(flan): Hack until there is better typing. + } + } + + public stepCyclesDebug(c: number, cb: callback): void { + var op, end = this.cycles + c; + + while (this.cycles < end) { + this.sync = true; + op = this.opary[this.readBytePC()]; + this.sync = false; + op[1](op[2] as any); // TODO(flan): Hack until there is better typing. + + if (cb) { + cb(this); + } + } + } + + public addPageHandler(pho: (PageHandler | ResettablePageHandler) & (ReadablePage | WriteablePage)) { + for (let idx = pho.start(); idx <= pho.end(); idx++) { + if (isReadablePage(pho)) + this.readPages[idx] = pho; + if (isWriteablePage(pho)) + this.writePages[idx] = pho; + } + if (isResettablePageHandler(pho)) + this.resetHandlers.push(pho); + } + + public reset() { + // cycles = 0; + this.sr = 0x20; + this.sp = 0xff; + this.ar = 0; + this.yr = 0; + this.xr = 0; + this.pc = this.readWord(loc.RESET); + + 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); + } + } + + /* 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); + } + + public getPC() { + return this.pc; + } + + public setPC(pc: word) { + this.pc = pc; + } + + public dumpPC(pc: word, symbols: symbols) { + if (pc === undefined) { + pc = this.pc; + } + let b = this.readByte(pc), + op = this.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(this.readByte(pc + idx)) + ' '; + } else { + result += ' '; + } + } + + if (op === undefined) + result += '??? (' + toHex(b) + ')'; + else + result += op[0] + ' ' + this.dumpArgs(pc + 1, op[3], symbols); + + return result; + } + + public dumpPage(start?: word, end?: word) { + var result = ''; + if (start === undefined) { + start = this.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 = this.readByteDebug(page * 256 + idx * 16 + jdx); + result += toHex(b) + ' '; + } + result += ' '; + for (jdx = 0; jdx < 16; jdx++) { + b = this.readByte(page * 256 + idx * 16 + jdx) & 0x7f; + if (b >= 0x20 && b < 0x7f) { + result += String.fromCharCode(b); + } else { + result += '.'; + } + } + result += '\n'; + } + } + return result; + } + + public list(_pc: word, symbols: symbols) { + if (_pc === undefined) { + _pc = this.pc; + } + var results = []; + for (var jdx = 0; jdx < 20; jdx++) { + var b = this.readByte(_pc), op = this.ops[b]; + results.push(this.dumpPC(_pc, symbols)); + _pc += sizes[op[3]]; + } + return results; + } + + public sync_() { + return this.sync; + } + + public cycles_() { + return this.cycles; + } + + public registers() { + return [this.pc,this.ar,this.xr,this.yr,this.sr,this.sp]; + } + + 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 dumpRegisters() { + return toHex(this.pc, 4) + + '- A=' + toHex(this.ar) + + ' X=' + toHex(this.xr) + + ' Y=' + toHex(this.yr) + + ' P=' + toHex(this.sr) + + ' S=' + toHex(this.sp) + + ' ' + + ((this.sr & flags.N) ? 'N' : '-') + + ((this.sr & flags.V) ? 'V' : '-') + + '-' + + ((this.sr & flags.B) ? 'B' : '-') + + ((this.sr & flags.D) ? 'D' : '-') + + ((this.sr & flags.I) ? 'I' : '-') + + ((this.sr & flags.Z) ? 'Z' : '-') + + ((this.sr & flags.C) ? 'C' : '-'); + } + + public read(page: byte, off: byte): byte { + return this.readPages[page].read(page, off); + } + + public write(page: byte, off: byte, val: byte) { + this.writePages[page].write(page, off, val); + } +} + +const c = CPU6502.prototype; +const OPS_6502: Instructions = { + // LDA + 0xa9: ['LDA', c.lda, c.readImmediate, modes.immediate, 2], + 0xa5: ['LDA', c.lda, c.readZeroPage, modes.zeroPage, 3], + 0xb5: ['LDA', c.lda, c.readZeroPageX, modes.zeroPageX, 4], + 0xad: ['LDA', c.lda, c.readAbsolute, modes.absolute, 4], + 0xbd: ['LDA', c.lda, c.readAbsoluteX, modes.absoluteX, 4], + 0xb9: ['LDA', c.lda, c.readAbsoluteY, modes.absoluteY, 4], + 0xa1: ['LDA', c.lda, c.readZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0xb1: ['LDA', c.lda, c.readZeroPageIndirectY, modes.zeroPageIndirectY, 5], + + // LDX + 0xa2: ['LDX', c.ldx, c.readImmediate, modes.immediate, 2], + 0xa6: ['LDX', c.ldx, c.readZeroPage, modes.zeroPage, 3], + 0xb6: ['LDX', c.ldx, c.readZeroPageY, modes.zeroPageY, 4], + 0xae: ['LDX', c.ldx, c.readAbsolute, modes.absolute, 4], + 0xbe: ['LDX', c.ldx, c.readAbsoluteY, modes.absoluteY, 4], + + // LDY + 0xa0: ['LDY', c.ldy, c.readImmediate, modes.immediate, 2], + 0xa4: ['LDY', c.ldy, c.readZeroPage, modes.zeroPage, 3], + 0xb4: ['LDY', c.ldy, c.readZeroPageX, modes.zeroPageX, 4], + 0xac: ['LDY', c.ldy, c.readAbsolute, modes.absolute, 4], + 0xbc: ['LDY', c.ldy, c.readAbsoluteX, modes.absoluteX, 4], + + // STA + 0x85: ['STA', c.sta, c.writeZeroPage, modes.zeroPage, 3], + 0x95: ['STA', c.sta, c.writeZeroPageX, modes.zeroPageX, 4], + 0x8d: ['STA', c.sta, c.writeAbsolute, modes.absolute, 4], + 0x9d: ['STA', c.sta, c.writeAbsoluteX, modes.absoluteX, 5], + 0x99: ['STA', c.sta, c.writeAbsoluteY, modes.absoluteY, 5], + 0x81: ['STA', c.sta, c.writeZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0x91: ['STA', c.sta, c.writeZeroPageIndirectY, modes.zeroPageIndirectY, 6], + + // STX + 0x86: ['STX', c.stx, c.writeZeroPage, modes.zeroPage, 3], + 0x96: ['STX', c.stx, c.writeZeroPageY, modes.zeroPageY, 4], + 0x8e: ['STX', c.stx, c.writeAbsolute, modes.absolute, 4], + + // STY + 0x84: ['STY', c.sty, c.writeZeroPage, modes.zeroPage, 3], + 0x94: ['STY', c.sty, c.writeZeroPageX, modes.zeroPageX, 4], + 0x8c: ['STY', c.sty, c.writeAbsolute, modes.absolute, 4], + + // ADC + 0x69: ['ADC', c.adc, c.readImmediate, modes.immediate, 2], + 0x65: ['ADC', c.adc, c.readZeroPage, modes.zeroPage, 3], + 0x75: ['ADC', c.adc, c.readZeroPageX, modes.zeroPageX, 4], + 0x6D: ['ADC', c.adc, c.readAbsolute, modes.absolute, 4], + 0x7D: ['ADC', c.adc, c.readAbsoluteX, modes.absoluteX, 4], + 0x79: ['ADC', c.adc, c.readAbsoluteY, modes.absoluteY, 4], + 0x61: ['ADC', c.adc, c.readZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0x71: ['ADC', c.adc, c.readZeroPageIndirectY, modes.zeroPageIndirectY, 5], + + // SBC + 0xe9: ['SBC', c.sbc, c.readImmediate, modes.immediate, 2], + 0xe5: ['SBC', c.sbc, c.readZeroPage, modes.zeroPage, 3], + 0xf5: ['SBC', c.sbc, c.readZeroPageX, modes.zeroPageX, 4], + 0xeD: ['SBC', c.sbc, c.readAbsolute, modes.absolute, 4], + 0xfD: ['SBC', c.sbc, c.readAbsoluteX, modes.absoluteX, 4], + 0xf9: ['SBC', c.sbc, c.readAbsoluteY, modes.absoluteY, 4], + 0xe1: ['SBC', c.sbc, c.readZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0xf1: ['SBC', c.sbc, c.readZeroPageIndirectY, modes.zeroPageIndirectY, 5], + + // INC + 0xe6: ['INC', c.inc, c.readAddrZeroPage, modes.zeroPage, 5], + 0xf6: ['INC', c.inc, c.readAddrZeroPageX, modes.zeroPageX, 6], + 0xee: ['INC', c.inc, c.readAddrAbsolute, modes.absolute, 6], + 0xfe: ['INC', c.inc, c.readAddrAbsoluteX, modes.absoluteX, 7], + + // INX + 0xe8: ['INX', c.inx, null, modes.implied, 2], + + // INY + 0xc8: ['INY', c.iny, null, modes.implied, 2], + + // DEC + 0xc6: ['DEC', c.dec, c.readAddrZeroPage, modes.zeroPage, 5], + 0xd6: ['DEC', c.dec, c.readAddrZeroPageX, modes.zeroPageX, 6], + 0xce: ['DEC', c.dec, c.readAddrAbsolute, modes.absolute, 6], + 0xde: ['DEC', c.dec, c.readAddrAbsoluteX, modes.absoluteX, 7], + + // DEX + 0xca: ['DEX', c.dex, null, modes.implied, 2], + + // DEY + 0x88: ['DEY', c.dey, null, modes.implied, 2], + + // ASL + 0x0A: ['ASL', c.aslA, null, modes.accumulator, 2], + 0x06: ['ASL', c.asl, c.readAddrZeroPage, modes.zeroPage, 5], + 0x16: ['ASL', c.asl, c.readAddrZeroPageX, modes.zeroPageX, 6], + 0x0E: ['ASL', c.asl, c.readAddrAbsolute, modes.absolute, 6], + 0x1E: ['ASL', c.asl, c.readAddrAbsoluteX, modes.absoluteX, 7], + + // LSR + 0x4A: ['LSR', c.lsrA, null, modes.accumulator, 2], + 0x46: ['LSR', c.lsr, c.readAddrZeroPage, modes.zeroPage, 5], + 0x56: ['LSR', c.lsr, c.readAddrZeroPageX, modes.zeroPageX, 6], + 0x4E: ['LSR', c.lsr, c.readAddrAbsolute, modes.absolute, 6], + 0x5E: ['LSR', c.lsr, c.readAddrAbsoluteX, modes.absoluteX, 7], + + // ROL + 0x2A: ['ROL', c.rolA, null, modes.accumulator, 2], + 0x26: ['ROL', c.rol, c.readAddrZeroPage, modes.zeroPage, 5], + 0x36: ['ROL', c.rol, c.readAddrZeroPageX, modes.zeroPageX, 6], + 0x2E: ['ROL', c.rol, c.readAddrAbsolute, modes.absolute, 6], + 0x3E: ['ROL', c.rol, c.readAddrAbsoluteX, modes.absoluteX, 7], + + // ROR + 0x6A: ['ROR', c.rorA, null, modes.accumulator, 2], + 0x66: ['ROR', c.ror, c.readAddrZeroPage, modes.zeroPage, 5], + 0x76: ['ROR', c.ror, c.readAddrZeroPageX, modes.zeroPageX, 6], + 0x6E: ['ROR', c.ror, c.readAddrAbsolute, modes.absolute, 6], + 0x7E: ['ROR', c.ror, c.readAddrAbsoluteX, modes.absoluteX, 7], + + // AND + 0x29: ['AND', c.and, c.readImmediate, modes.immediate, 2], + 0x25: ['AND', c.and, c.readZeroPage, modes.zeroPage, 3], + 0x35: ['AND', c.and, c.readZeroPageX, modes.zeroPageX, 4], + 0x2D: ['AND', c.and, c.readAbsolute, modes.absolute, 4], + 0x3D: ['AND', c.and, c.readAbsoluteX, modes.absoluteX, 4], + 0x39: ['AND', c.and, c.readAbsoluteY, modes.absoluteY, 4], + 0x21: ['AND', c.and, c.readZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0x31: ['AND', c.and, c.readZeroPageIndirectY, modes.zeroPageIndirectY, 5], + + // ORA + 0x09: ['ORA', c.ora, c.readImmediate, modes.immediate, 2], + 0x05: ['ORA', c.ora, c.readZeroPage, modes.zeroPage, 3], + 0x15: ['ORA', c.ora, c.readZeroPageX, modes.zeroPageX, 4], + 0x0D: ['ORA', c.ora, c.readAbsolute, modes.absolute, 4], + 0x1D: ['ORA', c.ora, c.readAbsoluteX, modes.absoluteX, 4], + 0x19: ['ORA', c.ora, c.readAbsoluteY, modes.absoluteY, 4], + 0x01: ['ORA', c.ora, c.readZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0x11: ['ORA', c.ora, c.readZeroPageIndirectY, modes.zeroPageIndirectY, 5], + + // EOR + 0x49: ['EOR', c.eor, c.readImmediate, modes.immediate, 2], + 0x45: ['EOR', c.eor, c.readZeroPage, modes.zeroPage, 3], + 0x55: ['EOR', c.eor, c.readZeroPageX, modes.zeroPageX, 4], + 0x4D: ['EOR', c.eor, c.readAbsolute, modes.absolute, 4], + 0x5D: ['EOR', c.eor, c.readAbsoluteX, modes.absoluteX, 4], + 0x59: ['EOR', c.eor, c.readAbsoluteY, modes.absoluteY, 4], + 0x41: ['EOR', c.eor, c.readZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0x51: ['EOR', c.eor, c.readZeroPageIndirectY, modes.zeroPageIndirectY, 5], + + // CMP + 0xc9: ['CMP', c.cmp, c.readImmediate, modes.immediate, 2], + 0xc5: ['CMP', c.cmp, c.readZeroPage, modes.zeroPage, 3], + 0xd5: ['CMP', c.cmp, c.readZeroPageX, modes.zeroPageX, 4], + 0xcD: ['CMP', c.cmp, c.readAbsolute, modes.absolute, 4], + 0xdD: ['CMP', c.cmp, c.readAbsoluteX, modes.absoluteX, 4], + 0xd9: ['CMP', c.cmp, c.readAbsoluteY, modes.absoluteY, 4], + 0xc1: ['CMP', c.cmp, c.readZeroPageXIndirect, modes.zeroPageXIndirect, 6], + 0xd1: ['CMP', c.cmp, c.readZeroPageIndirectY, modes.zeroPageIndirectY, 5], + + // CPX + 0xE0: ['CPX', c.cpx, c.readImmediate, modes.immediate, 2], + 0xE4: ['CPX', c.cpx, c.readZeroPage, modes.zeroPage, 3], + 0xEC: ['CPX', c.cpx, c.readAbsolute, modes.absolute, 4], + + // CPY + 0xC0: ['CPY', c.cpy, c.readImmediate, modes.immediate, 2], + 0xC4: ['CPY', c.cpy, c.readZeroPage, modes.zeroPage, 3], + 0xCC: ['CPY', c.cpy, c.readAbsolute, modes.absolute, 4], + + // BIT + 0x24: ['BIT', c.bit, c.readZeroPage, modes.zeroPage, 3], + 0x2C: ['BIT', c.bit, c.readAbsolute, modes.absolute, 4], + + // BCC + 0x90: ['BCC', c.brc, flags.C, modes.relative, 2], + + // BCS + 0xB0: ['BCS', c.brs, flags.C, modes.relative, 2], + + // BEQ + 0xF0: ['BEQ', c.brs, flags.Z, modes.relative, 2], + + // BMI + 0x30: ['BMI', c.brs, flags.N, modes.relative, 2], + + // BNE + 0xD0: ['BNE', c.brc, flags.Z, modes.relative, 2], + + // BPL + 0x10: ['BPL', c.brc, flags.N, modes.relative, 2], + + // BVC + 0x50: ['BVC', c.brc, flags.V, modes.relative, 2], + + // BVS + 0x70: ['BVS', c.brs, flags.V, modes.relative, 2], + + // TAX + 0xAA: ['TAX', c.tax, null, modes.implied, 2], + + // TXA + 0x8A: ['TXA', c.txa, null, modes.implied, 2], + + // TAY + 0xA8: ['TAY', c.tay, null, modes.implied, 2], + + // TYA + 0x98: ['TYA', c.tya, null, modes.implied, 2], + + // TSX + 0xBA: ['TSX', c.tsx, null, modes.implied, 2], + + // TXS + 0x9A: ['TXS', c.txs, null, modes.implied, 2], + + // PHA + 0x48: ['PHA', c.pha, null, modes.implied, 3], + + // PLA + 0x68: ['PLA', c.pla, null, modes.implied, 4], + + // PHP + 0x08: ['PHP', c.php, null, modes.implied, 3], + + // PLP + 0x28: ['PLP', c.plp, null, modes.implied, 4], + + // JMP + 0x4C: [ + 'JMP', c.jmp, c.readAddrAbsolute, modes.absolute, 3 + ], + 0x6C: [ + 'JMP', c.jmp, c.readAddrAbsoluteIndirectBug, modes.absoluteIndirect, 5 + ], + // JSR + 0x20: ['JSR', c.jsr, c.readAddrAbsolute, modes.absolute, 6], + + // RTS + 0x60: ['RTS', c.rts, null, modes.implied, 6], + + // RTI + 0x40: ['RTI', c.rti, null, modes.implied, 6], + + // SEC + 0x38: ['SEC', c.set, flags.C, modes.implied, 2], + + // SED + 0xF8: ['SED', c.set, flags.D, modes.implied, 2], + + // SEI + 0x78: ['SEI', c.set, flags.I, modes.implied, 2], + + // CLC + 0x18: ['CLC', c.clr, flags.C, modes.implied, 2], + + // CLD + 0xD8: ['CLD', c.clr, flags.D, modes.implied, 2], + + // CLI + 0x58: ['CLI', c.clr, flags.I, modes.implied, 2], + + // CLV + 0xB8: ['CLV', c.clr, flags.V, modes.implied, 2], + + // NOP + 0xea: ['NOP', c.nop, c.readImplied, modes.implied, 2], + + // BRK + 0x00: ['BRK', c.brk, c.readImmediate, modes.immediate, 7] +}; + +/* 65C02 Instructions */ + +const OPS_65C02: Instructions = { + // INC / DEC A + 0x1A: ['INC', c.incA, null, modes.accumulator, 2], + 0x3A: ['DEC', c.decA, null, modes.accumulator, 2], + + // Indirect Zero Page for the masses + 0x12: ['ORA', c.ora, c.readZeroPageIndirect, modes.zeroPageIndirect, 5], + 0x32: ['AND', c.and, c.readZeroPageIndirect, modes.zeroPageIndirect, 5], + 0x52: ['EOR', c.eor, c.readZeroPageIndirect, modes.zeroPageIndirect, 5], + 0x72: ['ADC', c.adc, c.readZeroPageIndirect, modes.zeroPageIndirect, 5], + 0x92: ['STA', c.sta, c.writeZeroPageIndirect, modes.zeroPageIndirect, 5], + 0xB2: ['LDA', c.lda, c.readZeroPageIndirect, modes.zeroPageIndirect, 5], + 0xD2: ['CMP', c.cmp, c.readZeroPageIndirect, modes.zeroPageIndirect, 5], + 0xF2: ['SBC', c.sbc, c.readZeroPageIndirect, modes.zeroPageIndirect, 5], + + // Better BIT + 0x34: ['BIT', c.bit, c.readZeroPageX, modes.zeroPageX, 4], + 0x3C: ['BIT', c.bit, c.readAbsoluteX, modes.absoluteX, 4], + 0x89: ['BIT', c.bitI, c.readImmediate, modes.immediate, 2], + + // JMP absolute indirect indexed + 0x6C: [ + 'JMP', c.jmp, c.readAddrAbsoluteIndirect, modes.absoluteIndirect, 6 + ], + 0x7C: [ + 'JMP', c.jmp, c.readAddrAbsoluteXIndirect, modes.absoluteXIndirect, 6 + ], + + // BBR/BBS + 0x0F: ['BBR0', c.bbr, 0, modes.zeroPage_relative, 5], + 0x1F: ['BBR1', c.bbr, 1, modes.zeroPage_relative, 5], + 0x2F: ['BBR2', c.bbr, 2, modes.zeroPage_relative, 5], + 0x3F: ['BBR3', c.bbr, 3, modes.zeroPage_relative, 5], + 0x4F: ['BBR4', c.bbr, 4, modes.zeroPage_relative, 5], + 0x5F: ['BBR5', c.bbr, 5, modes.zeroPage_relative, 5], + 0x6F: ['BBR6', c.bbr, 6, modes.zeroPage_relative, 5], + 0x7F: ['BBR7', c.bbr, 7, modes.zeroPage_relative, 5], + + 0x8F: ['BBS0', c.bbs, 0, modes.zeroPage_relative, 5], + 0x9F: ['BBS1', c.bbs, 1, modes.zeroPage_relative, 5], + 0xAF: ['BBS2', c.bbs, 2, modes.zeroPage_relative, 5], + 0xBF: ['BBS3', c.bbs, 3, modes.zeroPage_relative, 5], + 0xCF: ['BBS4', c.bbs, 4, modes.zeroPage_relative, 5], + 0xDF: ['BBS5', c.bbs, 5, modes.zeroPage_relative, 5], + 0xEF: ['BBS6', c.bbs, 6, modes.zeroPage_relative, 5], + 0xFF: ['BBS7', c.bbs, 7, modes.zeroPage_relative, 5], + + // BRA + 0x80: ['BRA', c.brc, 0, modes.relative, 2], + + // NOP + 0x02: ['NOP', c.nop, c.readImmediate, modes.immediate, 2], + 0x22: ['NOP', c.nop, c.readImmediate, modes.immediate, 2], + 0x42: ['NOP', c.nop, c.readImmediate, modes.immediate, 2], + 0x44: ['NOP', c.nop, c.readImmediate, modes.immediate, 3], + 0x54: ['NOP', c.nop, c.readImmediate, modes.immediate, 4], + 0x62: ['NOP', c.nop, c.readImmediate, modes.immediate, 2], + 0x82: ['NOP', c.nop, c.readImmediate, modes.immediate, 2], + 0xC2: ['NOP', c.nop, c.readImmediate, modes.immediate, 2], + 0xD4: ['NOP', c.nop, c.readImmediate, modes.immediate, 4], + 0xE2: ['NOP', c.nop, c.readImmediate, modes.immediate, 2], + 0xF4: ['NOP', c.nop, c.readImmediate, modes.immediate, 4], + 0x5C: ['NOP', c.nop, c.readAbsolute, modes.absolute, 8], + 0xDC: ['NOP', c.nop, c.readAbsolute, modes.absolute, 4], + 0xFC: ['NOP', c.nop, c.readAbsolute, modes.absolute, 4], + + // PHX + 0xDA: ['PHX', c.phx, null, modes.implied, 3], + + // PHY + 0x5A: ['PHY', c.phy, null, modes.implied, 3], + + // PLX + 0xFA: ['PLX', c.plx, null, modes.implied, 4], + + // PLY + 0x7A: ['PLY', c.ply, null, modes.implied, 4], + + // RMB/SMB + + 0x07: ['RMB0', c.rmb, 0, modes.zeroPage, 5], + 0x17: ['RMB1', c.rmb, 1, modes.zeroPage, 5], + 0x27: ['RMB2', c.rmb, 2, modes.zeroPage, 5], + 0x37: ['RMB3', c.rmb, 3, modes.zeroPage, 5], + 0x47: ['RMB4', c.rmb, 4, modes.zeroPage, 5], + 0x57: ['RMB5', c.rmb, 5, modes.zeroPage, 5], + 0x67: ['RMB6', c.rmb, 6, modes.zeroPage, 5], + 0x77: ['RMB7', c.rmb, 7, modes.zeroPage, 5], + + 0x87: ['SMB0', c.smb, 0, modes.zeroPage, 5], + 0x97: ['SMB1', c.smb, 1, modes.zeroPage, 5], + 0xA7: ['SMB2', c.smb, 2, modes.zeroPage, 5], + 0xB7: ['SMB3', c.smb, 3, modes.zeroPage, 5], + 0xC7: ['SMB4', c.smb, 4, modes.zeroPage, 5], + 0xD7: ['SMB5', c.smb, 5, modes.zeroPage, 5], + 0xE7: ['SMB6', c.smb, 6, modes.zeroPage, 5], + 0xF7: ['SMB7', c.smb, 7, modes.zeroPage, 5], + + // STZ + 0x64: ['STZ', c.stz, c.writeZeroPage, modes.zeroPage, 3], + 0x74: ['STZ', c.stz, c.writeZeroPageX, modes.zeroPageX, 4], + 0x9C: ['STZ', c.stz, c.writeAbsolute, modes.absolute, 4], + 0x9E: ['STZ', c.stz, c.writeAbsoluteX, modes.absoluteX, 5], + + // TRB + 0x14: ['TRB', c.trb, c.readAddrZeroPage, modes.zeroPage, 5], + 0x1C: ['TRB', c.trb, c.readAddrAbsolute, modes.absolute, 6], + + // TSB + 0x04: ['TSB', c.tsb, c.readAddrZeroPage, modes.zeroPage, 5], + 0x0C: ['TSB', c.tsb, c.readAddrAbsolute, modes.absolute, 6] +}; diff --git a/js/ram.js b/js/ram.ts similarity index 54% rename from js/ram.js rename to js/ram.ts index a196041..5a9a194 100644 --- a/js/ram.js +++ b/js/ram.ts @@ -10,30 +10,43 @@ */ import { base64_decode, base64_encode } from './base64'; +import { byte, memory } from './types'; import { allocMemPages } from './util'; -export default function RAM(sp, ep) { - var mem; - var start_page = sp; - var end_page = ep; +export interface State { + /** Start of memory region. */ + start: byte; + /** End of memory region. */ + end: byte; + /** Base64-encoded contents. */ + mem: string; +}; - mem = allocMemPages(ep - sp + 1); +/** + * Represents RAM from the start page `sp` to end page `ep`. The memory + * is addressed by `page` and `offset`. + */ +export default function RAM(sp: byte, ep: byte) { + let start_page = sp; + let end_page = ep; + + let mem = allocMemPages(ep - sp + 1); return { - start: function() { + start: function () { return start_page; }, - end: function() { + end: function () { return end_page; }, - read: function(page, off) { - return mem[(page - start_page) << 8 | off]; + read: function (page: byte, offset: byte) { + return mem[(page - start_page) << 8 | offset]; }, - write: function(page, off, val) { - mem[(page - start_page) << 8 | off] = val; + write: function (page: byte, offset: byte, val: byte) { + mem[(page - start_page) << 8 | offset] = val; }, - getState: function() { + getState: function (): State { return { start: start_page, end: end_page, @@ -41,7 +54,7 @@ export default function RAM(sp, ep) { }; }, - setState: function(state) { + setState: function (state: State) { start_page = state.start; end_page = state.end; mem = base64_decode(state.mem); diff --git a/js/symbols.js b/js/symbols.js index e54f589..805f843 100644 --- a/js/symbols.js +++ b/js/symbols.js @@ -1,4 +1,4 @@ -var SYMBOLS = { +SYMBOLS = { /* 0x00: 'GOWARM', 0x03: 'GOSTROUT', diff --git a/js/types.ts b/js/types.ts new file mode 100644 index 0000000..d093317 --- /dev/null +++ b/js/types.ts @@ -0,0 +1,27 @@ + +/** A byte (0..255). This is not enforced by the compiler. */ +export type byte = number; + +/** A word (0..65535). This is not enforced by the compiler. */ +export type word = number; + +/** A region of memory. */ +export type memory = number[] | Uint8Array; + +export type DiskFormat = '2mg' | 'd13' | 'do' | 'dsk' | 'hdv' | 'po' | 'nib' | 'woz'; + +export interface Drive { + format: DiskFormat, + volume: number, + tracks: Array, + trackMap: unknown, +}; + +export interface DiskIIDrive extends Drive { + rawTracks: unknown, + track: number, + head: number, + phase: number, + readOnly: boolean, + dirty: boolean, +}; diff --git a/js/util.js b/js/util.ts similarity index 50% rename from js/util.js rename to js/util.ts index e68a9af..cef9f47 100644 --- a/js/util.js +++ b/js/util.ts @@ -9,27 +9,39 @@ * implied warranty. */ +import { byte, memory, word } from "./types"; + /*eslint no-console: 0*/ -var hex_digits = '0123456789ABCDEF'; -var bin_digits = '01'; +const hex_digits = '0123456789ABCDEF'; +const bin_digits = '01'; -export function allocMem(size) { - function garbage() { - return (Math.random() * 0x100) & 0xff; - } - var result; +/** Returns a random byte. */ +function garbage(): byte { + return (Math.random() * 0x100) & 0xff; +} + +export const testables = { + garbage +}; + +/** + * Returns an array or Uint8Array of `size` bytes filled as if the computer + * was just powered on. + */ +export function allocMem(size: number) { + let result: number[] | Uint8Array; if (window.Uint8Array) { result = new Uint8Array(size); } else { result = new Array(size); } - var idx; - for (idx = 0; idx < size; idx++) { + + for (let idx = 0; idx < size; idx++) { result[idx] = (idx & 0x02) ? 0x00 : 0xff; } // Borrowed from AppleWin (https://github.com/AppleWin/AppleWin) - for(idx = 0; idx < size; idx += 0x200 ) { + for (let idx = 0; idx < size; idx += 0x200) { result[idx + 0x28] = garbage(); result[idx + 0x29] = garbage(); result[idx + 0x68] = garbage(); @@ -38,23 +50,33 @@ export function allocMem(size) { return result; } -export function allocMemPages(pages) { +/** Returns an array or Uint8Array of 256 * `pages` bytes. */ +export function allocMemPages(pages: number): memory { return allocMem(pages << 8); } -export function bytify(ary) { - var result = ary; +/** Returns a new Uint8Array for the input array. */ +export function bytify(ary: number[]): memory { + let result: number[] | Uint8Array = ary; if (window.Uint8Array) { result = new Uint8Array(ary); } return result; } +/** Writes to the console. */ +export function debug(...args: any[]): void; export function debug() { console.log.apply(console, arguments); } -export function toHex(v, n) { +/** + * Returns a string of hex digits (all caps). + * @param v the value to encode + * @param n the number of nibbles. If `n` is missing, it is guessed from the value + * of `v`. If `v` < 256, it is assumed to be 2 nibbles, otherwise 4. + */ +export function toHex(v: byte | word | number, n?: number) { if (!n) { n = v < 256 ? 2 : 4; } @@ -66,7 +88,11 @@ export function toHex(v, n) { return result; } -export function toBinary(v) { +/** + * Returns a string of 8 binary digits. + * @param v the value to encode + */ +export function toBinary(v: byte) { var result = ''; for (var idx = 0; idx < 8; idx++) { result = bin_digits[v & 0x01] + result; @@ -75,29 +101,36 @@ export function toBinary(v) { return result; } +/** + * Returns the value of a query parameter or the empty string if it does not + * exist. + * @param name the parameter name. Note that `name` must not have any RegExp + * meta-characters except '[' and ']' or it will fail. + */ // From http://www.netlobo.com/url_query_string_javascript.html -export function gup( name ) -{ - name = name.replace(/[[]/,'\\[').replace(/[\]]/,'\\]'); - var regexS = '[\\?&]'+name+'=([^&#]*)'; - var regex = new RegExp( regexS ); - var results = regex.exec( window.location.href ); - if( !results ) +export function gup(name: string) { + name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]'); + var regexS = '[\\?&]' + name + '=([^&#]*)'; + var regex = new RegExp(regexS); + var results = regex.exec(window.location.href); + if (!results) return ''; else return results[1]; } +/** Returns the URL fragment. */ export function hup() { var regex = new RegExp('#(.*)'); var results = regex.exec(window.location.hash); - if ( !results ) + if (!results) return ''; else return results[1]; } -export function numToString(num) { +/** Packs a 32-bit integer into a string in little-endian order. */ +export function numToString(num: number) { let result = ''; for (let idx = 0; idx < 4; idx++) { result += String.fromCharCode(num & 0xff); diff --git a/test/js/util.test.ts b/test/js/util.test.ts new file mode 100644 index 0000000..8a136c8 --- /dev/null +++ b/test/js/util.test.ts @@ -0,0 +1,99 @@ +/** @fileoverview Test for utils.ts. */ + +import { allocMem, allocMemPages, numToString, testables, toBinary, toHex } from "../../js/util"; + +describe('garbage', () => { + it('returns 0 <= x <= 255', () => { + for (let i = 0; i < 1024; i++) { + expect(testables.garbage()).toBeGreaterThanOrEqual(0); + } + }); +}); + +describe('allocMem', () => { + it('returns an array of the correct size', () => { + expect(allocMem(2048).length).toBe(2048); + + }); + it('has 0xff and 0x00 patterns', () => { + let memory = allocMem(2048); + expect(memory[0]).toBe(0xff); + expect(memory[1]).toBe(0xff); + expect(memory[2]).toBe(0x00); + expect(memory[3]).toBe(0x00); + expect(memory[4]).toBe(0xff); + }); + it('has garbage in the right places', () => { + let memory = allocMem(0x800); + for (let i = 0; i < 0x800; i += 0x200) { + let passed = memory[i + 0x28] != 0xff + && memory[i + 0x29] != 0xff + && memory[i + 0x68] != 0xff + && memory[i + 0x69] != 0xff; + if (passed) { + return; + } + } + fail('garbage not found'); + }); +}); + +describe('allocMemPages', () => { + it('allocates 256 * the size', () => { + expect(allocMemPages(5).length).toBe(5 * 256); + }); +}); + +describe('toHex', () => { + it('converts an odd number of characters', () => { + expect(toHex(0xfedcb, 5)).toEqual("FEDCB"); + }); + it('correctly guesses byte values', () => { + expect(toHex(0xa5)).toEqual("A5"); + }); + it('correctly guesses word values', () => { + expect(toHex(0x1abc)).toEqual("1ABC"); + }); + it('only uses the bottom work of larger values', () => { + expect(toHex(0xabcdef)).toEqual("CDEF"); + }); + it('correctly prepends zeros', () => { + expect(toHex(0xa5, 4)).toEqual("00A5"); + }); +}); + +describe('toBinary', () => { + it('has 8 digits for zero', () => { + expect(toBinary(0x00)).toEqual("00000000"); + }); + it('correctly sets bits', () => { + expect(toBinary(0xa5)).toEqual("10100101"); + }); +}); + +describe('gup', () => { + // untestable due to direct reference to window.location +}); + +describe('hup', () => { + // untestable due to direct reference to window.location +}); + +describe('numToString', () => { + it('packs a zero byte into a string of all zeros', () => { + expect(numToString(0x00)).toEqual("\0\0\0\0"); + }); + it('packs a byte in the printable ASCII range into a zero-padded string', + () => { + expect(numToString(0x41)).toEqual("A\0\0\0"); + }); + it('packs a word into a string', () => { + expect(numToString(0x4142)).toEqual("BA\0\0"); + }); + it('packs a 32-bit value into a string', () => { + expect(numToString(0x41424344)).toEqual("DCBA"); + }); + it('ignores more than 32 bits', () => { + expect(numToString(0x4142434445)).toEqual("EDCB"); + }); +}); \ No newline at end of file