mirror of
https://github.com/whscullin/apple1js.git
synced 2024-12-01 17:50:00 +00:00
1629 lines
45 KiB
JavaScript
1629 lines
45 KiB
JavaScript
/*
|
|
* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
|
*
|
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
|
* documentation for any purpose is hereby granted without fee, provided that
|
|
* the above copyright notice appear in all copies and that both that
|
|
* copyright notice and this permission notice appear in supporting
|
|
* documentation. No representations are made about the suitability of this
|
|
* software for any purpose. It is provided "as is" without express or
|
|
* implied warranty.
|
|
*/
|
|
|
|
import { debug, toHex } from './util';
|
|
|
|
export default function CPU6502(options)
|
|
{
|
|
options = options || {};
|
|
|
|
var is65C02 = options['65C02'] ? true : false;
|
|
|
|
/* Registers */
|
|
var pc = 0, // Program Counter
|
|
sr = 0x20, // Process Status Register
|
|
ar = 0, // Accumulator
|
|
xr = 0, // X Register
|
|
yr = 0, // Y Register
|
|
sp = 0xff; // Stack Pointer
|
|
|
|
/* Addressing Mode */
|
|
var modes = {
|
|
accumulator: 0, // A (Accumulator)
|
|
implied: 1, // Implied
|
|
immediate: 2, // # Immediate
|
|
absolute: 3, // a Absolute
|
|
zeroPage: 4, // zp Zero Page
|
|
relative: 5, // r Relative
|
|
|
|
absoluteX: 6, // a,X Absolute, X
|
|
absoluteY: 7, // a,Y Absolute, Y
|
|
zeroPageX: 8, // zp,X Zero Page, X
|
|
zeroPageY: 9, // zp,Y Zero Page, Y
|
|
|
|
absoluteIndirect: 10, // (a) Indirect
|
|
zeroPageXIndirect: 11, // (zp,X) Zero Page Indexed Indirect
|
|
zeroPageIndirectY: 12, // (zp),Y Zero Page Indexed with Y
|
|
|
|
/* 65c02 */
|
|
zeroPageIndirect: 13, // (zp),
|
|
absoluteXIndirect: 14, // (a, X),
|
|
zeroPage_relative: 15 // zp, Relative
|
|
};
|
|
|
|
var sizes = {
|
|
0 /* modes.accumulator */: 1,
|
|
1 /* modes.implied */: 1,
|
|
2 /* modes.immediate */: 2,
|
|
3 /* modes.absolute */: 3,
|
|
4 /* modes.zeroPage */: 2,
|
|
5 /* modes.relative */: 2,
|
|
6 /* modes.absoluteX */: 3,
|
|
7 /* modes.absoluteY */: 3,
|
|
8 /* modes.zeroPageX */: 2,
|
|
9 /* modes.zeroPageY */: 2,
|
|
10 /* modes.indirect */: 3,
|
|
11 /* modes.zeroPageXIndirect */: 2,
|
|
12 /* modes.zeroPageYIndirect */: 2,
|
|
|
|
13 /* mode.zeroPageIndirect */: 2,
|
|
14 /* mode.absoluteXIndirect */: 3,
|
|
15 /* mode.zeroPage_relative */: 3
|
|
};
|
|
|
|
/* Flags */
|
|
var flags = {
|
|
N: 0x80, // Negative
|
|
V: 0x40, // oVerflow
|
|
B: 0x10, // Break
|
|
D: 0x08, // Decimal
|
|
I: 0x04, // Interrupt
|
|
Z: 0x02, // Zero
|
|
C: 0x01 // Carry
|
|
};
|
|
|
|
/* Memory Locations */
|
|
var loc = {
|
|
STACK: 0x100,
|
|
NMI: 0xFFFA,
|
|
RESET: 0xFFFC,
|
|
BRK: 0xFFFE
|
|
};
|
|
|
|
var idx;
|
|
|
|
var readPages = [];
|
|
var writePages = [];
|
|
var resetHandlers = [];
|
|
var cycles = 0;
|
|
var sync = false;
|
|
|
|
var blankPage = {
|
|
read: function() { return 0; },
|
|
write: function() {}
|
|
};
|
|
|
|
for (idx = 0; idx < 0x100; idx++) {
|
|
readPages[idx] = blankPage;
|
|
writePages[idx] = blankPage;
|
|
}
|
|
|
|
function setFlag(f, on) {
|
|
sr = on ? (sr | f) : (sr & ~f);
|
|
}
|
|
|
|
function testNZ(val) {
|
|
sr = val === 0 ? (sr | flags.Z) : (sr & ~flags.Z);
|
|
sr = (val & 0x80) ? (sr | flags.N) : (sr & ~flags.N);
|
|
|
|
return val;
|
|
}
|
|
|
|
function testZ(val) {
|
|
sr = val === 0 ? (sr | flags.Z) : (sr & ~flags.Z);
|
|
|
|
return val;
|
|
}
|
|
|
|
function add(a, b, sub) {
|
|
if (sub)
|
|
b ^= 0xff;
|
|
|
|
// KEGS
|
|
var c, v;
|
|
if ((sr & flags.D) !== 0) {
|
|
c = (a & 0x0f) + (b & 0x0f) + (sr & flags.C);
|
|
if (sub) {
|
|
if (c < 0x10)
|
|
c = (c - 0x06) & 0x0f;
|
|
c += (a & 0xf0) + (b & 0xf0);
|
|
v = (c >> 1) ^ c;
|
|
if (c < 0x100)
|
|
c = (c + 0xa0) & 0xff;
|
|
} else {
|
|
if (c > 0x09)
|
|
c = (c - 0x0a) | 0x10; // carry to MSN
|
|
c += (a & 0xf0) + (b & 0xf0);
|
|
v = (c >> 1) ^ c;
|
|
if (c > 0x99)
|
|
c += 0x60;
|
|
}
|
|
} else {
|
|
c = a + b + (sr & flags.C);
|
|
v = (c ^ a) & 0x80;
|
|
}
|
|
|
|
if (((a ^ b) & 0x80) !== 0) {
|
|
v = 0;
|
|
}
|
|
|
|
setFlag(flags.C, c > 0xff);
|
|
setFlag(flags.V, v);
|
|
|
|
return testNZ(c & 0xff);
|
|
}
|
|
|
|
function increment(a) {
|
|
return testNZ((a + 0x01) & 0xff);
|
|
}
|
|
|
|
function decrement(a) {
|
|
return testNZ((a + 0xff) & 0xff);
|
|
}
|
|
|
|
function readBytePC(dbg) {
|
|
var addr = (pc++) & 0xffff,
|
|
page = addr >> 8,
|
|
off = addr & 0xff;
|
|
|
|
var result = readPages[page].read(page, off, dbg);
|
|
|
|
if (!dbg) {
|
|
cycles++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function readByte(addr, dbg) {
|
|
var page = addr >> 8,
|
|
off = addr & 0xff;
|
|
|
|
var result = readPages[page].read(page, off, dbg);
|
|
|
|
if (!dbg) {
|
|
cycles++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function writeByte(addr, val) {
|
|
var page = addr >> 8,
|
|
off = addr & 0xff;
|
|
|
|
writePages[page].write(page, off, val);
|
|
|
|
cycles++;
|
|
}
|
|
|
|
function readWord(addr, dbg) {
|
|
return readByte(addr, dbg) | (readByte(addr + 1, dbg) << 8);
|
|
}
|
|
|
|
function readWordPC(dbg) {
|
|
return readBytePC(dbg) | (readBytePC(dbg) << 8);
|
|
}
|
|
|
|
function readZPWord(addr, dbg) {
|
|
var lsb, msb;
|
|
|
|
lsb = readByte(addr & 0xff, dbg);
|
|
msb = readByte((addr + 1) & 0xff, dbg);
|
|
|
|
return (msb << 8) | lsb;
|
|
}
|
|
|
|
function pushByte(val) {
|
|
writeByte(loc.STACK | sp, val);
|
|
sp = (sp + 0xff) & 0xff;
|
|
}
|
|
|
|
function pushWord(val) {
|
|
pushByte(val >> 8);
|
|
pushByte(val & 0xff);
|
|
}
|
|
|
|
function pullByte() {
|
|
sp = (sp + 0x01) & 0xff;
|
|
return readByte(loc.STACK | sp);
|
|
}
|
|
|
|
function pullWordRaw() {
|
|
var lsb = pullByte(loc.STACK | sp);
|
|
var msb = pullByte(loc.STACK | sp);
|
|
|
|
return (msb << 8) | lsb;
|
|
}
|
|
|
|
/*
|
|
* Read functions
|
|
*/
|
|
|
|
function readImplied() {
|
|
}
|
|
|
|
// #$00
|
|
function readImmediate() {
|
|
return readBytePC();
|
|
}
|
|
|
|
// $0000
|
|
function readAbsolute() {
|
|
return readByte(readWordPC());
|
|
}
|
|
|
|
// $00
|
|
function readZeroPage() {
|
|
return readByte(readBytePC());
|
|
}
|
|
|
|
// $0000,X
|
|
function readAbsoluteX() {
|
|
var addr = readWordPC();
|
|
var oldPage = addr >> 8;
|
|
addr = (addr + xr) & 0xffff;
|
|
var newPage = addr >> 8;
|
|
if (newPage != oldPage) {
|
|
var off = addr & 0xff;
|
|
readByte(oldPage << 8 | off);
|
|
}
|
|
return readByte(addr);
|
|
}
|
|
|
|
// $0000,Y
|
|
function readAbsoluteY() {
|
|
var addr = readWordPC();
|
|
var oldPage = addr >> 8;
|
|
addr = (addr + yr) & 0xffff;
|
|
var newPage = addr >> 8;
|
|
if (newPage != oldPage) {
|
|
var off = addr & 0xff;
|
|
readByte(oldPage << 8 | off);
|
|
}
|
|
return readByte(addr);
|
|
}
|
|
|
|
// $00,X
|
|
function readZeroPageX() {
|
|
var zpAddr = readBytePC();
|
|
readByte(zpAddr);
|
|
return readByte((zpAddr + xr) & 0xff);
|
|
}
|
|
|
|
// $00,Y
|
|
function readZeroPageY() {
|
|
var zpAddr = readBytePC();
|
|
readByte(zpAddr);
|
|
return readByte((zpAddr + yr) & 0xff);
|
|
}
|
|
|
|
// ($00,X)
|
|
function readZeroPageXIndirect() {
|
|
var zpAddr = readBytePC();
|
|
readByte(zpAddr);
|
|
var addr = readZPWord((zpAddr + xr) & 0xff);
|
|
return readByte(addr);
|
|
}
|
|
|
|
// ($00),Y
|
|
function readZeroPageIndirectY() {
|
|
var addr = readZPWord(readBytePC());
|
|
var oldPage = addr >> 8;
|
|
addr = (addr + yr) & 0xffff;
|
|
var newPage = addr >> 8;
|
|
if (newPage != oldPage) {
|
|
var off = addr & 0xff;
|
|
readByte(oldPage << 8 | off);
|
|
}
|
|
return readByte(addr);
|
|
}
|
|
|
|
// ($00) (65C02)
|
|
function readZeroPageIndirect() {
|
|
return readByte(readZPWord(readBytePC()));
|
|
}
|
|
|
|
/*
|
|
* Write Functions
|
|
*/
|
|
|
|
// $0000
|
|
function writeAbsolute(val) {
|
|
writeByte(readWordPC(), val);
|
|
}
|
|
|
|
// $00
|
|
function writeZeroPage(val) {
|
|
writeByte(readBytePC(), val);
|
|
}
|
|
|
|
// $0000,X
|
|
function writeAbsoluteX(val) {
|
|
var addr = readWordPC(), oldPage = addr >> 8;
|
|
addr = (addr + xr) & 0xffff;
|
|
var off = addr & 0xff;
|
|
readByte(oldPage << 8 | off);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
// $0000,Y
|
|
function writeAbsoluteY(val) {
|
|
var addr = readWordPC(), oldPage = addr >> 8;
|
|
addr = (addr + yr) & 0xffff;
|
|
var off = addr & 0xff;
|
|
readByte(oldPage << 8 | off);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
// $00,X
|
|
function writeZeroPageX(val) {
|
|
var zpAddr = readBytePC();
|
|
readByte(zpAddr);
|
|
writeByte((zpAddr + xr) & 0xff, val);
|
|
}
|
|
|
|
// $00,Y
|
|
function writeZeroPageY(val) {
|
|
var zpAddr = readBytePC();
|
|
readByte(zpAddr);
|
|
writeByte((zpAddr + yr) & 0xff, val);
|
|
}
|
|
|
|
// ($00,X)
|
|
function writeZeroPageXIndirect(val) {
|
|
var zpAddr = readBytePC();
|
|
readByte(zpAddr);
|
|
var addr = readZPWord((zpAddr + xr) & 0xff);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
// ($00),Y
|
|
function writeZeroPageIndirectY(val) {
|
|
var addr = readZPWord(readBytePC()), oldPage = addr >> 8;
|
|
addr = (addr + yr) & 0xffff;
|
|
var off = addr & 0xff;
|
|
readByte(oldPage << 8 | off);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
// ($00) (65C02)
|
|
function writeZeroPageIndirect(val) {
|
|
writeByte(readZPWord(readBytePC()), val);
|
|
}
|
|
|
|
// $00
|
|
function readAddrZeroPage() {
|
|
return readBytePC();
|
|
}
|
|
|
|
// $00,X
|
|
function readAddrZeroPageX() {
|
|
var zpAddr = readBytePC();
|
|
readByte(zpAddr);
|
|
return (zpAddr + xr) & 0xff;
|
|
}
|
|
|
|
// $0000 (65C02)
|
|
function readAddrAbsolute() {
|
|
return readWordPC();
|
|
}
|
|
|
|
// ($0000) (6502)
|
|
function readAddrAbsoluteIndirectBug() {
|
|
var addr = readWordPC();
|
|
var page = addr & 0xff00;
|
|
var off = addr & 0x00ff;
|
|
var lsb = readByte(addr);
|
|
var msb = readByte(page | ((off + 0x01) & 0xff));
|
|
return msb << 8 | lsb;
|
|
}
|
|
|
|
// ($0000) (65C02)
|
|
function readAddrAbsoluteIndirect() {
|
|
var lsb = readBytePC();
|
|
var msb = readBytePC();
|
|
readByte(pc);
|
|
return readWord(msb << 8 | lsb);
|
|
}
|
|
|
|
// $0000,X
|
|
function readAddrAbsoluteX(opts) {
|
|
var addr = readWordPC();
|
|
if (!is65C02 || (opts && opts.rwm)) {
|
|
readByte(addr);
|
|
} else {
|
|
readByte(pc);
|
|
}
|
|
return (addr + xr) & 0xffff;
|
|
}
|
|
|
|
// $(0000,X) (65C02)
|
|
function readAddrAbsoluteXIndirect() {
|
|
var address = readWordPC();
|
|
readByte(pc);
|
|
return readWord((address + xr) & 0xffff);
|
|
}
|
|
|
|
/* Break */
|
|
function brk(readFn) {
|
|
readFn();
|
|
pushWord(pc);
|
|
pushByte(sr | flags.B);
|
|
if (is65C02) {
|
|
setFlag(flags.D, false);
|
|
}
|
|
setFlag(flags.I, true);
|
|
pc = readWord(loc.BRK);
|
|
}
|
|
|
|
/* Load Accumulator */
|
|
function lda(readFn) {
|
|
ar = testNZ(readFn());
|
|
}
|
|
|
|
/* Load X Register */
|
|
function ldx(readFn) {
|
|
xr = testNZ(readFn());
|
|
}
|
|
|
|
/* Load Y Register */
|
|
function ldy(readFn) {
|
|
yr = testNZ(readFn());
|
|
}
|
|
|
|
/* Store Accumulator */
|
|
function sta(writeFn) {
|
|
writeFn(ar);
|
|
}
|
|
|
|
/* Store X Register */
|
|
function stx(writeFn) {
|
|
writeFn(xr);
|
|
}
|
|
|
|
/* Store Y Register */
|
|
function sty(writeFn) {
|
|
writeFn(yr);
|
|
}
|
|
|
|
/* Store Zero */
|
|
function stz(writeFn) {
|
|
writeFn(0);
|
|
}
|
|
|
|
/* Add with Carry */
|
|
function adc(readFn) {
|
|
ar = add(ar, readFn(), false);
|
|
}
|
|
|
|
/* Subtract with Carry */
|
|
function sbc(readFn) {
|
|
ar = add(ar, readFn(), true);
|
|
}
|
|
|
|
/* Increment Memory */
|
|
function incA() {
|
|
readByte(pc);
|
|
ar = increment(ar);
|
|
}
|
|
|
|
function inc(readAddrFn) {
|
|
var addr = readAddrFn({rwm: true});
|
|
var oldVal = readByte(addr);
|
|
writeByte(addr, oldVal);
|
|
var val = increment(oldVal);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
/* Increment X */
|
|
function inx() {
|
|
readByte(pc);
|
|
xr = increment(xr);
|
|
}
|
|
|
|
/* Increment Y */
|
|
function iny() {
|
|
readByte(pc);
|
|
yr = increment(yr);
|
|
}
|
|
|
|
/* Decrement Memory */
|
|
function decA() {
|
|
readByte(pc);
|
|
ar = decrement(ar);
|
|
}
|
|
|
|
function dec(readAddrFn) {
|
|
var addr = readAddrFn({rwm: true});
|
|
var oldVal = readByte(addr);
|
|
writeByte(addr, oldVal);
|
|
var val = decrement(oldVal);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
/* Decrement X */
|
|
function dex() {
|
|
readByte(pc);
|
|
xr = decrement(xr);
|
|
}
|
|
|
|
/* Decrement Y */
|
|
function dey() {
|
|
readByte(pc);
|
|
yr = decrement(yr);
|
|
}
|
|
|
|
function shiftLeft(val) {
|
|
setFlag(flags.C, val & 0x80);
|
|
return testNZ((val << 1) & 0xff);
|
|
}
|
|
|
|
/* Arithmetic Shift Left */
|
|
function aslA() {
|
|
readByte(pc);
|
|
ar = shiftLeft(ar);
|
|
}
|
|
|
|
function asl(readAddrFn) {
|
|
var addr = readAddrFn({rwm: true});
|
|
var oldVal = readByte(addr);
|
|
writeByte(addr, oldVal);
|
|
var val = shiftLeft(oldVal);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
function shiftRight(val) {
|
|
setFlag(flags.C, val & 0x01);
|
|
return testNZ(val >> 1);
|
|
}
|
|
|
|
/* Logical Shift Right */
|
|
function lsrA() {
|
|
readByte(pc);
|
|
ar = shiftRight(ar);
|
|
}
|
|
|
|
function lsr(readAddrFn) {
|
|
var addr = readAddrFn({rwm: true});
|
|
var oldVal = readByte(addr);
|
|
writeByte(addr, oldVal);
|
|
var val = shiftRight(oldVal);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
function rotateLeft(val) {
|
|
var c = (sr & flags.C);
|
|
setFlag(flags.C, val & 0x80);
|
|
return testNZ(((val << 1) | (c ? 0x01 : 0x00)) & 0xff);
|
|
}
|
|
|
|
/* Rotate Left */
|
|
function rolA() {
|
|
readByte(pc);
|
|
ar = rotateLeft(ar);
|
|
}
|
|
|
|
function rol(readAddrFn) {
|
|
var addr = readAddrFn({rwm: true});
|
|
var oldVal = readByte(addr);
|
|
writeByte(addr, oldVal);
|
|
var val = rotateLeft(oldVal);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
function rotateRight(a) {
|
|
var c = (sr & flags.C);
|
|
setFlag(flags.C, a & 0x01);
|
|
return testNZ((a >> 1) | (c ? 0x80 : 0x00));
|
|
}
|
|
|
|
/* Rotate Right */
|
|
function rorA() {
|
|
readByte(pc);
|
|
ar = rotateRight(ar);
|
|
}
|
|
|
|
function ror(readAddrFn) {
|
|
var addr = readAddrFn({rwm: true});
|
|
var oldVal = readByte(addr);
|
|
writeByte(addr, oldVal);
|
|
var val = rotateRight(oldVal);
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
/* Logical And Accumulator */
|
|
function and(readFn) {
|
|
ar = testNZ(ar & readFn());
|
|
}
|
|
|
|
/* Logical Or Accumulator */
|
|
function ora(readFn) {
|
|
ar = testNZ(ar | readFn());
|
|
}
|
|
|
|
/* Logical Exclusive Or Accumulator */
|
|
function eor(readFn) {
|
|
ar = testNZ(ar ^ readFn());
|
|
}
|
|
|
|
/* Reset Bit */
|
|
|
|
function rmb(b) {
|
|
var bit = (0x1 << b) ^ 0xFF;
|
|
var addr = readBytePC();
|
|
var val = readByte(addr);
|
|
readByte(addr);
|
|
val &= bit;
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
/* Set Bit */
|
|
|
|
function smb(b) {
|
|
var bit = 0x1 << b;
|
|
var addr = readBytePC();
|
|
var val = readByte(addr);
|
|
readByte(addr);
|
|
val |= bit;
|
|
writeByte(addr, val);
|
|
}
|
|
|
|
/* Test and Reset Bits */
|
|
function trb(readAddrFn) {
|
|
var addr = readAddrFn();
|
|
var val = readByte(addr);
|
|
testZ(val & ar);
|
|
readByte(addr);
|
|
writeByte(addr, val & ~ar);
|
|
}
|
|
|
|
/* Test and Set Bits */
|
|
function tsb(readAddrFn) {
|
|
var addr = readAddrFn();
|
|
var val = readByte(addr);
|
|
testZ(val & ar);
|
|
readByte(addr);
|
|
writeByte(addr, val | ar);
|
|
}
|
|
|
|
/* Bit */
|
|
function bit(readFn) {
|
|
var val = readFn();
|
|
setFlag(flags.Z, (val & ar) === 0);
|
|
setFlag(flags.N, val & 0x80);
|
|
setFlag(flags.V, val & 0x40);
|
|
}
|
|
|
|
/* Bit Immediate*/
|
|
function bitI(readFn) {
|
|
var val = readFn();
|
|
setFlag(flags.Z, (val & ar) === 0);
|
|
}
|
|
|
|
function compare(a, b)
|
|
{
|
|
b = (b ^ 0xff);
|
|
var c = a + b + 1;
|
|
setFlag(flags.C, c > 0xff);
|
|
testNZ(c & 0xff);
|
|
}
|
|
|
|
function cmp(readFn) {
|
|
compare(ar, readFn());
|
|
}
|
|
|
|
function cpx(readFn) {
|
|
compare(xr, readFn());
|
|
}
|
|
|
|
function cpy(readFn) {
|
|
compare(yr, readFn());
|
|
}
|
|
|
|
/* Branches */
|
|
function brs(f) {
|
|
var off = readBytePC(); // changes pc
|
|
if ((f & sr) !== 0) {
|
|
readByte(pc);
|
|
var oldPage = pc >> 8;
|
|
pc += off > 127 ? off - 256 : off;
|
|
var newPage = pc >> 8;
|
|
var newOff = pc & 0xff;
|
|
if (newPage != oldPage) readByte(oldPage << 8 | newOff);
|
|
}
|
|
}
|
|
|
|
function brc(f) {
|
|
var off = readBytePC(); // changes pc
|
|
if ((f & sr) === 0) {
|
|
readByte(pc);
|
|
var oldPage = pc >> 8;
|
|
pc += off > 127 ? off - 256 : off;
|
|
var newPage = pc >> 8;
|
|
var newOff = pc & 0xff;
|
|
if (newPage != oldPage) readByte(oldPage << 8 | newOff);
|
|
}
|
|
}
|
|
|
|
/* WDC 65C02 branches */
|
|
|
|
function bbr(b) {
|
|
var zpAddr = readBytePC();
|
|
var val = readByte(zpAddr);
|
|
readByte(zpAddr);
|
|
var off = readBytePC(); // changes pc
|
|
|
|
if (((1 << b) & val) === 0) {
|
|
var oldPc = pc;
|
|
var oldPage = oldPc >> 8;
|
|
readByte(oldPc);
|
|
pc += off > 127 ? off - 256 : off;
|
|
var newPage = pc >> 8;
|
|
if (oldPage != newPage) {
|
|
readByte(oldPc);
|
|
}
|
|
}
|
|
}
|
|
|
|
function bbs(b) {
|
|
var zpAddr = readBytePC();
|
|
var val = readByte(zpAddr);
|
|
readByte(zpAddr);
|
|
var off = readBytePC(); // changes pc
|
|
|
|
if (((1 << b) & val) !== 0) {
|
|
var oldPc = pc;
|
|
var oldPage = oldPc >> 8;
|
|
readByte(oldPc);
|
|
pc += off > 127 ? off - 256 : off;
|
|
var newPage = pc >> 8;
|
|
if (oldPage != newPage) {
|
|
readByte(oldPc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Transfers and stack */
|
|
function tax() { readByte(pc); testNZ(xr = ar); }
|
|
|
|
function txa() { readByte(pc); testNZ(ar = xr); }
|
|
|
|
function tay() { readByte(pc); testNZ(yr = ar); }
|
|
|
|
function tya() { readByte(pc); testNZ(ar = yr); }
|
|
|
|
function tsx() { readByte(pc); testNZ(xr = sp); }
|
|
|
|
function txs() { readByte(pc); sp = xr; }
|
|
|
|
function pha() { readByte(pc); pushByte(ar); }
|
|
|
|
function pla() { readByte(pc); readByte(0x0100 | sp); testNZ(ar = pullByte()); }
|
|
|
|
function phx() { readByte(pc); pushByte(xr); }
|
|
|
|
function plx() { readByte(pc); readByte(0x0100 | sp);testNZ(xr = pullByte()); }
|
|
|
|
function phy() { readByte(pc); pushByte(yr); }
|
|
|
|
function ply() { readByte(pc); readByte(0x0100 | sp); testNZ(yr = pullByte()); }
|
|
|
|
function php() { readByte(pc); pushByte(sr | flags.B); }
|
|
|
|
function plp() { readByte(pc); readByte(0x0100 | sp); sr = (pullByte() & ~flags.B) | 0x20; }
|
|
|
|
/* Jump */
|
|
function jmp(readAddrFn) {
|
|
pc = readAddrFn();
|
|
}
|
|
|
|
/* Jump Subroutine */
|
|
function jsr() {
|
|
var lsb = readBytePC();
|
|
readByte(0x0100 | sp);
|
|
pushWord(pc);
|
|
var msb = readBytePC();
|
|
pc = (msb << 8 | lsb) & 0xffff;
|
|
}
|
|
|
|
/* Return from Subroutine */
|
|
function rts() {
|
|
readByte(pc);
|
|
readByte(0x0100 | sp);
|
|
var addr = pullWordRaw();
|
|
readByte(addr);
|
|
pc = (addr + 1) & 0xffff;
|
|
}
|
|
|
|
/* Return from Subroutine */
|
|
function rti() {
|
|
readByte(pc);
|
|
readByte(0x0100 | sp);
|
|
sr = pullByte() & ~flags.B;
|
|
pc = pullWordRaw();
|
|
}
|
|
|
|
/* Set and Clear */
|
|
function set(flag) {
|
|
readByte(pc);
|
|
sr |= flag;
|
|
}
|
|
|
|
function clr(flag) {
|
|
readByte(pc);
|
|
sr &= ~flag;
|
|
}
|
|
|
|
/* No-Op */
|
|
function nop(readAddrFn) {
|
|
readByte(pc);
|
|
readAddrFn();
|
|
}
|
|
|
|
var ops = {
|
|
// LDA
|
|
0xa9: ['LDA', lda, readImmediate, modes.immediate, 2],
|
|
0xa5: ['LDA', lda, readZeroPage, modes.zeroPage, 3],
|
|
0xb5: ['LDA', lda, readZeroPageX, modes.zeroPageX, 4],
|
|
0xad: ['LDA', lda, readAbsolute, modes.absolute, 4],
|
|
0xbd: ['LDA', lda, readAbsoluteX, modes.absoluteX, 4],
|
|
0xb9: ['LDA', lda, readAbsoluteY, modes.absoluteY, 4],
|
|
0xa1: ['LDA', lda, readZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0xb1: ['LDA', lda, readZeroPageIndirectY, modes.zeroPageIndirectY, 5],
|
|
|
|
// LDX
|
|
0xa2: ['LDX', ldx, readImmediate, modes.immediate, 2],
|
|
0xa6: ['LDX', ldx, readZeroPage, modes.zeroPage, 3],
|
|
0xb6: ['LDX', ldx, readZeroPageY, modes.zeroPageY, 4],
|
|
0xae: ['LDX', ldx, readAbsolute, modes.absolute, 4],
|
|
0xbe: ['LDX', ldx, readAbsoluteY, modes.absoluteY, 4],
|
|
|
|
// LDY
|
|
0xa0: ['LDY', ldy, readImmediate, modes.immediate, 2],
|
|
0xa4: ['LDY', ldy, readZeroPage, modes.zeroPage, 3],
|
|
0xb4: ['LDY', ldy, readZeroPageX, modes.zeroPageX, 4],
|
|
0xac: ['LDY', ldy, readAbsolute, modes.absolute, 4],
|
|
0xbc: ['LDY', ldy, readAbsoluteX, modes.absoluteX, 4],
|
|
|
|
// STA
|
|
0x85: ['STA', sta, writeZeroPage, modes.zeroPage, 3],
|
|
0x95: ['STA', sta, writeZeroPageX, modes.zeroPageX, 4],
|
|
0x8d: ['STA', sta, writeAbsolute, modes.absolute, 4],
|
|
0x9d: ['STA', sta, writeAbsoluteX, modes.absoluteX, 5],
|
|
0x99: ['STA', sta, writeAbsoluteY, modes.absoluteY, 5],
|
|
0x81: ['STA', sta, writeZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0x91: ['STA', sta, writeZeroPageIndirectY, modes.zeroPageIndirectY, 6],
|
|
|
|
// STX
|
|
0x86: ['STX', stx, writeZeroPage, modes.zeroPage, 3],
|
|
0x96: ['STX', stx, writeZeroPageY, modes.zeroPageY, 4],
|
|
0x8e: ['STX', stx, writeAbsolute, modes.absolute, 4],
|
|
|
|
// STY
|
|
0x84: ['STY', sty, writeZeroPage, modes.zeroPage, 3],
|
|
0x94: ['STY', sty, writeZeroPageX, modes.zeroPageX, 4],
|
|
0x8c: ['STY', sty, writeAbsolute, modes.absolute, 4],
|
|
|
|
// ADC
|
|
0x69: ['ADC', adc, readImmediate, modes.immediate, 2],
|
|
0x65: ['ADC', adc, readZeroPage, modes.zeroPage, 3],
|
|
0x75: ['ADC', adc, readZeroPageX, modes.zeroPageX, 4],
|
|
0x6D: ['ADC', adc, readAbsolute, modes.absolute, 4],
|
|
0x7D: ['ADC', adc, readAbsoluteX, modes.absoluteX, 4],
|
|
0x79: ['ADC', adc, readAbsoluteY, modes.absoluteY, 4],
|
|
0x61: ['ADC', adc, readZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0x71: ['ADC', adc, readZeroPageIndirectY, modes.zeroPageIndirectY, 5],
|
|
|
|
// SBC
|
|
0xe9: ['SBC', sbc, readImmediate, modes.immediate, 2],
|
|
0xe5: ['SBC', sbc, readZeroPage, modes.zeroPage, 3],
|
|
0xf5: ['SBC', sbc, readZeroPageX, modes.zeroPageX, 4],
|
|
0xeD: ['SBC', sbc, readAbsolute, modes.absolute, 4],
|
|
0xfD: ['SBC', sbc, readAbsoluteX, modes.absoluteX, 4],
|
|
0xf9: ['SBC', sbc, readAbsoluteY, modes.absoluteY, 4],
|
|
0xe1: ['SBC', sbc, readZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0xf1: ['SBC', sbc, readZeroPageIndirectY, modes.zeroPageIndirectY, 5],
|
|
|
|
// INC
|
|
0xe6: ['INC', inc, readAddrZeroPage, modes.zeroPage, 5],
|
|
0xf6: ['INC', inc, readAddrZeroPageX, modes.zeroPageX, 6],
|
|
0xee: ['INC', inc, readAddrAbsolute, modes.absolute, 6],
|
|
0xfe: ['INC', inc, readAddrAbsoluteX, modes.absoluteX, 7],
|
|
|
|
// INX
|
|
0xe8: ['INX', inx, null, modes.implied, 2],
|
|
|
|
// INY
|
|
0xc8: ['INY', iny, null, modes.implied, 2],
|
|
|
|
// DEC
|
|
0xc6: ['DEC', dec, readAddrZeroPage, modes.zeroPage, 5],
|
|
0xd6: ['DEC', dec, readAddrZeroPageX, modes.zeroPageX, 6],
|
|
0xce: ['DEC', dec, readAddrAbsolute, modes.absolute, 6],
|
|
0xde: ['DEC', dec, readAddrAbsoluteX, modes.absoluteX, 7],
|
|
|
|
// DEX
|
|
0xca: ['DEX', dex, null, modes.implied, 2],
|
|
|
|
// DEY
|
|
0x88: ['DEY', dey, null, modes.implied, 2],
|
|
|
|
// ASL
|
|
0x0A: ['ASL', aslA, null, modes.accumulator, 2],
|
|
0x06: ['ASL', asl, readAddrZeroPage, modes.zeroPage, 5],
|
|
0x16: ['ASL', asl, readAddrZeroPageX, modes.zeroPageX, 6],
|
|
0x0E: ['ASL', asl, readAddrAbsolute, modes.absolute, 6],
|
|
0x1E: ['ASL', asl, readAddrAbsoluteX, modes.absoluteX, 7],
|
|
|
|
// LSR
|
|
0x4A: ['LSR', lsrA, null, modes.accumulator, 2],
|
|
0x46: ['LSR', lsr, readAddrZeroPage, modes.zeroPage, 5],
|
|
0x56: ['LSR', lsr, readAddrZeroPageX, modes.zeroPageX, 6],
|
|
0x4E: ['LSR', lsr, readAddrAbsolute, modes.absolute, 6],
|
|
0x5E: ['LSR', lsr, readAddrAbsoluteX, modes.absoluteX, 7],
|
|
|
|
// ROL
|
|
0x2A: ['ROL', rolA, null, modes.accumulator, 2],
|
|
0x26: ['ROL', rol, readAddrZeroPage, modes.zeroPage, 5],
|
|
0x36: ['ROL', rol, readAddrZeroPageX, modes.zeroPageX, 6],
|
|
0x2E: ['ROL', rol, readAddrAbsolute, modes.absolute, 6],
|
|
0x3E: ['ROL', rol, readAddrAbsoluteX, modes.absoluteX, 7],
|
|
|
|
// ROR
|
|
0x6A: ['ROR', rorA, null, modes.accumulator, 2],
|
|
0x66: ['ROR', ror, readAddrZeroPage, modes.zeroPage, 5],
|
|
0x76: ['ROR', ror, readAddrZeroPageX, modes.zeroPageX, 6],
|
|
0x6E: ['ROR', ror, readAddrAbsolute, modes.absolute, 6],
|
|
0x7E: ['ROR', ror, readAddrAbsoluteX, modes.absoluteX, 7],
|
|
|
|
// AND
|
|
0x29: ['AND', and, readImmediate, modes.immediate, 2],
|
|
0x25: ['AND', and, readZeroPage, modes.zeroPage, 3],
|
|
0x35: ['AND', and, readZeroPageX, modes.zeroPageX, 4],
|
|
0x2D: ['AND', and, readAbsolute, modes.absolute, 4],
|
|
0x3D: ['AND', and, readAbsoluteX, modes.absoluteX, 4],
|
|
0x39: ['AND', and, readAbsoluteY, modes.absoluteY, 4],
|
|
0x21: ['AND', and, readZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0x31: ['AND', and, readZeroPageIndirectY, modes.zeroPageIndirectY, 5],
|
|
|
|
// ORA
|
|
0x09: ['ORA', ora, readImmediate, modes.immediate, 2],
|
|
0x05: ['ORA', ora, readZeroPage, modes.zeroPage, 3],
|
|
0x15: ['ORA', ora, readZeroPageX, modes.zeroPageX, 4],
|
|
0x0D: ['ORA', ora, readAbsolute, modes.absolute, 4],
|
|
0x1D: ['ORA', ora, readAbsoluteX, modes.absoluteX, 4],
|
|
0x19: ['ORA', ora, readAbsoluteY, modes.absoluteY, 4],
|
|
0x01: ['ORA', ora, readZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0x11: ['ORA', ora, readZeroPageIndirectY, modes.zeroPageIndirectY, 5],
|
|
|
|
// EOR
|
|
0x49: ['EOR', eor, readImmediate, modes.immediate, 2],
|
|
0x45: ['EOR', eor, readZeroPage, modes.zeroPage, 3],
|
|
0x55: ['EOR', eor, readZeroPageX, modes.zeroPageX, 4],
|
|
0x4D: ['EOR', eor, readAbsolute, modes.absolute, 4],
|
|
0x5D: ['EOR', eor, readAbsoluteX, modes.absoluteX, 4],
|
|
0x59: ['EOR', eor, readAbsoluteY, modes.absoluteY, 4],
|
|
0x41: ['EOR', eor, readZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0x51: ['EOR', eor, readZeroPageIndirectY, modes.zeroPageIndirectY, 5],
|
|
|
|
// CMP
|
|
0xc9: ['CMP', cmp, readImmediate, modes.immediate, 2],
|
|
0xc5: ['CMP', cmp, readZeroPage, modes.zeroPage, 3],
|
|
0xd5: ['CMP', cmp, readZeroPageX, modes.zeroPageX, 4],
|
|
0xcD: ['CMP', cmp, readAbsolute, modes.absolute, 4],
|
|
0xdD: ['CMP', cmp, readAbsoluteX, modes.absoluteX, 4],
|
|
0xd9: ['CMP', cmp, readAbsoluteY, modes.absoluteY, 4],
|
|
0xc1: ['CMP', cmp, readZeroPageXIndirect, modes.zeroPageXIndirect, 6],
|
|
0xd1: ['CMP', cmp, readZeroPageIndirectY, modes.zeroPageIndirectY, 5],
|
|
|
|
// CPX
|
|
0xE0: ['CPX', cpx, readImmediate, modes.immediate, 2],
|
|
0xE4: ['CPX', cpx, readZeroPage, modes.zeroPage, 3],
|
|
0xEC: ['CPX', cpx, readAbsolute, modes.absolute, 4],
|
|
|
|
// CPY
|
|
0xC0: ['CPY', cpy, readImmediate, modes.immediate, 2],
|
|
0xC4: ['CPY', cpy, readZeroPage, modes.zeroPage, 3],
|
|
0xCC: ['CPY', cpy, readAbsolute, modes.absolute, 4],
|
|
|
|
// BIT
|
|
0x24: ['BIT', bit, readZeroPage, modes.zeroPage, 3],
|
|
0x2C: ['BIT', bit, readAbsolute, modes.absolute, 4],
|
|
|
|
// BCC
|
|
0x90: ['BCC', brc, flags.C, modes.relative, 2],
|
|
|
|
// BCS
|
|
0xB0: ['BCS', brs, flags.C, modes.relative, 2],
|
|
|
|
// BEQ
|
|
0xF0: ['BEQ', brs, flags.Z, modes.relative, 2],
|
|
|
|
// BMI
|
|
0x30: ['BMI', brs, flags.N, modes.relative, 2],
|
|
|
|
// BNE
|
|
0xD0: ['BNE', brc, flags.Z, modes.relative, 2],
|
|
|
|
// BPL
|
|
0x10: ['BPL', brc, flags.N, modes.relative, 2],
|
|
|
|
// BVC
|
|
0x50: ['BVC', brc, flags.V, modes.relative, 2],
|
|
|
|
// BVS
|
|
0x70: ['BVS', brs, flags.V, modes.relative, 2],
|
|
|
|
// TAX
|
|
0xAA: ['TAX', tax, null, modes.implied, 2],
|
|
|
|
// TXA
|
|
0x8A: ['TXA', txa, null, modes.implied, 2],
|
|
|
|
// TAY
|
|
0xA8: ['TAY', tay, null, modes.implied, 2],
|
|
|
|
// TYA
|
|
0x98: ['TYA', tya, null, modes.implied, 2],
|
|
|
|
// TSX
|
|
0xBA: ['TSX', tsx, null, modes.implied, 2],
|
|
|
|
// TXS
|
|
0x9A: ['TXS', txs, null, modes.implied, 2],
|
|
|
|
// PHA
|
|
0x48: ['PHA', pha, null, modes.implied, 3],
|
|
|
|
// PLA
|
|
0x68: ['PLA', pla, null, modes.implied, 4],
|
|
|
|
// PHP
|
|
0x08: ['PHP', php, null, modes.implied, 3],
|
|
|
|
// PLP
|
|
0x28: ['PLP', plp, null, modes.implied, 4],
|
|
|
|
// JMP
|
|
0x4C: [
|
|
'JMP', jmp, readAddrAbsolute, modes.absolute, 3
|
|
],
|
|
0x6C: [
|
|
'JMP', jmp, readAddrAbsoluteIndirectBug, modes.absoluteIndirect, 5
|
|
],
|
|
// JSR
|
|
0x20: ['JSR', jsr, readAddrAbsolute, modes.absolute, 6],
|
|
|
|
// RTS
|
|
0x60: ['RTS', rts, null, modes.implied, 6],
|
|
|
|
// RTI
|
|
0x40: ['RTI', rti, null, modes.implied, 6],
|
|
|
|
// SEC
|
|
0x38: ['SEC', set, flags.C, modes.implied, 2],
|
|
|
|
// SED
|
|
0xF8: ['SED', set, flags.D, modes.implied, 2],
|
|
|
|
// SEI
|
|
0x78: ['SEI', set, flags.I, modes.implied, 2],
|
|
|
|
// CLC
|
|
0x18: ['CLC', clr, flags.C, modes.implied, 2],
|
|
|
|
// CLD
|
|
0xD8: ['CLD', clr, flags.D, modes.implied, 2],
|
|
|
|
// CLI
|
|
0x58: ['CLI', clr, flags.I, modes.implied, 2],
|
|
|
|
// CLV
|
|
0xB8: ['CLV', clr, flags.V, modes.implied, 2],
|
|
|
|
// NOP
|
|
0xea: ['NOP', nop, readImplied, modes.implied, 2],
|
|
|
|
// BRK
|
|
0x00: ['BRK', brk, readImmediate, modes.immediate, 7]
|
|
};
|
|
|
|
/* 65C02 Instructions */
|
|
|
|
var cops = {
|
|
// INC / DEC A
|
|
0x1A: ['INC', incA, null, modes.accumulator, 2],
|
|
0x3A: ['DEC', decA, null, modes.accumulator, 2],
|
|
|
|
// Indirect Zero Page for the masses
|
|
0x12: ['ORA', ora, readZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
0x32: ['AND', and, readZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
0x52: ['EOR', eor, readZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
0x72: ['ADC', adc, readZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
0x92: ['STA', sta, writeZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
0xB2: ['LDA', lda, readZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
0xD2: ['CMP', cmp, readZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
0xF2: ['SBC', sbc, readZeroPageIndirect, modes.zeroPageIndirect, 5],
|
|
|
|
// Better BIT
|
|
0x34: ['BIT', bit, readZeroPageX, modes.zeroPageX, 4],
|
|
0x3C: ['BIT', bit, readAbsoluteX, modes.absoluteX, 4],
|
|
0x89: ['BIT', bitI, readImmediate, modes.immediate, 2],
|
|
|
|
// JMP absolute indirect indexed
|
|
0x6C: [
|
|
'JMP', jmp, readAddrAbsoluteIndirect, modes.absoluteIndirect, 6
|
|
],
|
|
0x7C: [
|
|
'JMP', jmp, readAddrAbsoluteXIndirect, modes.absoluteXIndirect, 6
|
|
],
|
|
|
|
// BBR/BBS
|
|
0x0F: ['BBR0', bbr, 0, modes.zeroPage_relative, 5],
|
|
0x1F: ['BBR1', bbr, 1, modes.zeroPage_relative, 5],
|
|
0x2F: ['BBR2', bbr, 2, modes.zeroPage_relative, 5],
|
|
0x3F: ['BBR3', bbr, 3, modes.zeroPage_relative, 5],
|
|
0x4F: ['BBR4', bbr, 4, modes.zeroPage_relative, 5],
|
|
0x5F: ['BBR5', bbr, 5, modes.zeroPage_relative, 5],
|
|
0x6F: ['BBR6', bbr, 6, modes.zeroPage_relative, 5],
|
|
0x7F: ['BBR7', bbr, 7, modes.zeroPage_relative, 5],
|
|
|
|
0x8F: ['BBS0', bbs, 0, modes.zeroPage_relative, 5],
|
|
0x9F: ['BBS1', bbs, 1, modes.zeroPage_relative, 5],
|
|
0xAF: ['BBS2', bbs, 2, modes.zeroPage_relative, 5],
|
|
0xBF: ['BBS3', bbs, 3, modes.zeroPage_relative, 5],
|
|
0xCF: ['BBS4', bbs, 4, modes.zeroPage_relative, 5],
|
|
0xDF: ['BBS5', bbs, 5, modes.zeroPage_relative, 5],
|
|
0xEF: ['BBS6', bbs, 6, modes.zeroPage_relative, 5],
|
|
0xFF: ['BBS7', bbs, 7, modes.zeroPage_relative, 5],
|
|
|
|
// BRA
|
|
0x80: ['BRA', brc, 0, modes.relative, 2],
|
|
|
|
// NOP
|
|
0x02: ['NOP', nop, readImmediate, modes.immediate, 2],
|
|
0x22: ['NOP', nop, readImmediate, modes.immediate, 2],
|
|
0x42: ['NOP', nop, readImmediate, modes.immediate, 2],
|
|
0x44: ['NOP', nop, readImmediate, modes.immediate, 3],
|
|
0x54: ['NOP', nop, readImmediate, modes.immediate, 4],
|
|
0x62: ['NOP', nop, readImmediate, modes.immediate, 2],
|
|
0x82: ['NOP', nop, readImmediate, modes.immediate, 2],
|
|
0xC2: ['NOP', nop, readImmediate, modes.immediate, 2],
|
|
0xD4: ['NOP', nop, readImmediate, modes.immediate, 4],
|
|
0xE2: ['NOP', nop, readImmediate, modes.immediate, 2],
|
|
0xF4: ['NOP', nop, readImmediate, modes.immediate, 4],
|
|
0x5C: ['NOP', nop, readAbsolute, modes.absolute, 8],
|
|
0xDC: ['NOP', nop, readAbsolute, modes.absolute, 4],
|
|
0xFC: ['NOP', nop, readAbsolute, modes.absolute, 4],
|
|
|
|
// PHX
|
|
0xDA: ['PHX', phx, null, modes.implied, 3],
|
|
|
|
// PHY
|
|
0x5A: ['PHY', phy, null, modes.implied, 3],
|
|
|
|
// PLX
|
|
0xFA: ['PLX', plx, null, modes.implied, 4],
|
|
|
|
// PLY
|
|
0x7A: ['PLY', ply, null, modes.implied, 4],
|
|
|
|
// RMB/SMB
|
|
|
|
0x07: ['RMB0', rmb, 0, modes.zeroPage, 5],
|
|
0x17: ['RMB1', rmb, 1, modes.zeroPage, 5],
|
|
0x27: ['RMB2', rmb, 2, modes.zeroPage, 5],
|
|
0x37: ['RMB3', rmb, 3, modes.zeroPage, 5],
|
|
0x47: ['RMB4', rmb, 4, modes.zeroPage, 5],
|
|
0x57: ['RMB5', rmb, 5, modes.zeroPage, 5],
|
|
0x67: ['RMB6', rmb, 6, modes.zeroPage, 5],
|
|
0x77: ['RMB7', rmb, 7, modes.zeroPage, 5],
|
|
|
|
0x87: ['SMB0', smb, 0, modes.zeroPage, 5],
|
|
0x97: ['SMB1', smb, 1, modes.zeroPage, 5],
|
|
0xA7: ['SMB2', smb, 2, modes.zeroPage, 5],
|
|
0xB7: ['SMB3', smb, 3, modes.zeroPage, 5],
|
|
0xC7: ['SMB4', smb, 4, modes.zeroPage, 5],
|
|
0xD7: ['SMB5', smb, 5, modes.zeroPage, 5],
|
|
0xE7: ['SMB6', smb, 6, modes.zeroPage, 5],
|
|
0xF7: ['SMB7', smb, 7, modes.zeroPage, 5],
|
|
|
|
// STZ
|
|
0x64: ['STZ', stz, writeZeroPage, modes.zeroPage, 3],
|
|
0x74: ['STZ', stz, writeZeroPageX, modes.zeroPageX, 4],
|
|
0x9C: ['STZ', stz, writeAbsolute, modes.absolute, 4],
|
|
0x9E: ['STZ', stz, writeAbsoluteX, modes.absoluteX, 5],
|
|
|
|
// TRB
|
|
0x14: ['TRB', trb, readAddrZeroPage, modes.zeroPage, 5],
|
|
0x1C: ['TRB', trb, readAddrAbsolute, modes.absolute, 6],
|
|
|
|
// TSB
|
|
0x04: ['TSB', tsb, readAddrZeroPage, modes.zeroPage, 5],
|
|
0x0C: ['TSB', tsb, readAddrAbsolute, modes.absolute, 6]
|
|
};
|
|
|
|
if (is65C02) {
|
|
for (var key in cops) {
|
|
if (cops.hasOwnProperty(key)) {
|
|
ops[key] = cops[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
function unknown(b) {
|
|
var unk;
|
|
|
|
if (is65C02) {
|
|
unk = [
|
|
'NOP',
|
|
nop,
|
|
readImplied,
|
|
modes.implied,
|
|
2
|
|
];
|
|
} else {
|
|
unk = [
|
|
'???',
|
|
function() {
|
|
debug('Unknown OpCode: ' + toHex(b) +
|
|
' at ' + toHex(pc - 1, 4));
|
|
},
|
|
readImplied,
|
|
modes.implied,
|
|
1
|
|
];
|
|
}
|
|
ops[b] = unk;
|
|
return unk;
|
|
}
|
|
|
|
/* Certain browsers benefit from using arrays over maps */
|
|
var opary = [];
|
|
|
|
for (idx = 0; idx < 0x100; idx++) {
|
|
opary[idx] = ops[idx] || unknown(idx);
|
|
}
|
|
|
|
function dumpArgs(addr, m, symbols) {
|
|
var val;
|
|
var off;
|
|
function toHexOrSymbol(v, n) {
|
|
if (symbols && symbols[v]) {
|
|
return symbols[v];
|
|
} else {
|
|
return '$' + toHex(v, n);
|
|
}
|
|
}
|
|
var result = '';
|
|
switch (m) {
|
|
case modes.implied:
|
|
break;
|
|
case modes.immediate:
|
|
result = '#' + toHexOrSymbol(readByte(addr, true));
|
|
break;
|
|
case modes.absolute:
|
|
result = '' + toHexOrSymbol(readWord(addr, true), 4);
|
|
break;
|
|
case modes.zeroPage:
|
|
result = '' + toHexOrSymbol(readByte(addr, true));
|
|
break;
|
|
case modes.relative:
|
|
{
|
|
off = readByte(addr, true);
|
|
if (off > 127) {
|
|
off -= 256;
|
|
}
|
|
addr += off + 1;
|
|
result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
|
|
}
|
|
break;
|
|
case modes.absoluteX:
|
|
result = '' + toHexOrSymbol(readWord(addr, true), 4) + ',X';
|
|
break;
|
|
case modes.absoluteY:
|
|
result = '' + toHexOrSymbol(readWord(addr, true), 4) + ',Y';
|
|
break;
|
|
case modes.zeroPageX:
|
|
result = '' + toHexOrSymbol(readByte(addr, true)) + ',X';
|
|
break;
|
|
case modes.zeroPageY:
|
|
result = '' + toHexOrSymbol(readByte(addr, true)) + ',Y';
|
|
break;
|
|
case modes.absoluteIndirect:
|
|
result = '(' + toHexOrSymbol(readWord(addr, true), 4) + ')';
|
|
break;
|
|
case modes.zeroPageXIndirect:
|
|
result = '(' + toHexOrSymbol(readByte(addr, true)) + ',X)';
|
|
break;
|
|
case modes.zeroPageIndirectY:
|
|
result = '(' + toHexOrSymbol(readByte(addr, true)) + '),Y';
|
|
break;
|
|
case modes.accumulator:
|
|
result = 'A';
|
|
break;
|
|
case modes.zeroPageIndirect:
|
|
result = '(' + toHexOrSymbol(readByte(addr, true)) + ')';
|
|
break;
|
|
case modes.absoluteXIndirect:
|
|
result = '(' + toHexOrSymbol(readWord(addr, true), 4) + ',X)';
|
|
break;
|
|
case modes.zeroPage_relative:
|
|
val = readByte(addr, true);
|
|
off = readByte(addr + 1, true);
|
|
if (off > 127) {
|
|
off -= 256;
|
|
}
|
|
addr += off + 2;
|
|
result = '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return {
|
|
step: function cpu_step(cb) {
|
|
sync = true;
|
|
var op = opary[readBytePC()];
|
|
sync = false;
|
|
op[1](op[2]);
|
|
|
|
if (cb) {
|
|
cb(this);
|
|
}
|
|
},
|
|
|
|
stepDebug: function(n, cb) {
|
|
var op, idx;
|
|
|
|
for (idx = 0; idx < n; idx++) {
|
|
sync = true;
|
|
op = opary[readBytePC()];
|
|
sync = false;
|
|
op[1](op[2]);
|
|
|
|
if (cb) {
|
|
cb(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
stepCycles: function(c) {
|
|
var op, end = cycles + c;
|
|
|
|
while (cycles < end) {
|
|
sync = true;
|
|
op = opary[readBytePC()];
|
|
sync = false;
|
|
op[1](op[2]);
|
|
}
|
|
},
|
|
|
|
stepCyclesDebug: function(c, cb)
|
|
{
|
|
var op, end = cycles + c;
|
|
|
|
while (cycles < end) {
|
|
sync = true;
|
|
op = opary[readBytePC()];
|
|
sync = false;
|
|
op[1](op[2]);
|
|
|
|
if (cb) {
|
|
cb(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
addPageHandler: function(pho) {
|
|
for (var idx = pho.start(); idx <= pho.end(); idx++) {
|
|
if (pho.read)
|
|
readPages[idx] = pho;
|
|
if (pho.write)
|
|
writePages[idx] = pho;
|
|
}
|
|
if (pho.reset)
|
|
resetHandlers.push(pho);
|
|
},
|
|
|
|
reset: function cpu_reset()
|
|
{
|
|
// cycles = 0;
|
|
sr = 0x20;
|
|
sp = 0xff;
|
|
ar = 0;
|
|
yr = 0;
|
|
xr = 0;
|
|
pc = readWord(loc.RESET);
|
|
|
|
for (var idx = 0; idx < resetHandlers.length; idx++) {
|
|
resetHandlers[idx].reset();
|
|
}
|
|
},
|
|
|
|
/* IRQ - Interrupt Request */
|
|
irq: function cpu_irq()
|
|
{
|
|
if ((sr & flags.I) === 0) {
|
|
pushWord(pc);
|
|
pushByte(sr & ~flags.B);
|
|
if (is65C02) {
|
|
setFlag(flags.D, false);
|
|
}
|
|
setFlag(flags.I, true);
|
|
pc = readWord(loc.BRK);
|
|
}
|
|
},
|
|
|
|
/* NMI Non-maskable Interrupt */
|
|
nmi: function cpu_nmi()
|
|
{
|
|
pushWord(pc);
|
|
pushByte(sr & ~flags.B);
|
|
if (is65C02) {
|
|
setFlag(flags.D, false);
|
|
}
|
|
setFlag(flags.I, true);
|
|
pc = readWord(loc.NMI);
|
|
},
|
|
|
|
getPC: function () {
|
|
return pc;
|
|
},
|
|
|
|
setPC: function(_pc) {
|
|
pc = _pc;
|
|
},
|
|
|
|
dumpPC: function(_pc, symbols) {
|
|
if (_pc === undefined) {
|
|
_pc = pc;
|
|
}
|
|
var b = readByte(_pc, true),
|
|
op = ops[b],
|
|
size = sizes[op[3]],
|
|
result = toHex(_pc, 4) + '- ';
|
|
|
|
if (symbols) {
|
|
if (symbols[_pc]) {
|
|
result += symbols[_pc] +
|
|
' '.substring(symbols[_pc].length);
|
|
} else {
|
|
result += ' ';
|
|
}
|
|
}
|
|
|
|
for (var idx = 0; idx < 4; idx++) {
|
|
if (idx < size) {
|
|
result += toHex(readByte(_pc + idx, true)) + ' ';
|
|
} else {
|
|
result += ' ';
|
|
}
|
|
}
|
|
|
|
if (op === undefined)
|
|
result += '??? (' + toHex(b) + ')';
|
|
else
|
|
result += op[0] + ' ' + dumpArgs(_pc + 1, op[3], symbols);
|
|
|
|
return result;
|
|
},
|
|
|
|
dumpPage: function(start, end) {
|
|
var result = '';
|
|
if (start === undefined) {
|
|
start = pc >> 8;
|
|
}
|
|
if (end === undefined) {
|
|
end = start;
|
|
}
|
|
for (var page = start; page <= end; page++) {
|
|
var b, idx, jdx;
|
|
for (idx = 0; idx < 16; idx++) {
|
|
result += toHex(page) + toHex(idx << 4) + ': ';
|
|
for (jdx = 0; jdx < 16; jdx++) {
|
|
b = readByte(page * 256 + idx * 16 + jdx, true);
|
|
result += toHex(b) + ' ';
|
|
}
|
|
result += ' ';
|
|
for (jdx = 0; jdx < 16; jdx++) {
|
|
b = readByte(page * 256 + idx * 16 + jdx, true) & 0x7f;
|
|
if (b >= 0x20 && b < 0x7f) {
|
|
result += String.fromCharCode(b);
|
|
} else {
|
|
result += '.';
|
|
}
|
|
}
|
|
result += '\n';
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
list: function(_pc, symbols) {
|
|
if (_pc === undefined) {
|
|
_pc = pc;
|
|
}
|
|
var results = [];
|
|
for (var jdx = 0; jdx < 20; jdx++) {
|
|
var b = readByte(_pc), op = ops[b];
|
|
results.push(this.dumpPC(_pc, symbols));
|
|
_pc += sizes[op[3]];
|
|
}
|
|
return results;
|
|
},
|
|
|
|
sync: function() {
|
|
return sync;
|
|
},
|
|
|
|
cycles: function() {
|
|
return cycles;
|
|
},
|
|
|
|
registers: function() {
|
|
return [pc,ar,xr,yr,sr,sp];
|
|
},
|
|
|
|
getState: function() {
|
|
return {
|
|
a: ar,
|
|
x: xr,
|
|
y: yr,
|
|
s: sr,
|
|
pc: pc,
|
|
sp: sp,
|
|
cycles: cycles
|
|
};
|
|
},
|
|
|
|
setState: function(state) {
|
|
ar = state.a;
|
|
xr = state.x;
|
|
yr = state.y;
|
|
sr = state.s;
|
|
pc = state.pc;
|
|
sp = state.sp;
|
|
cycles = state.cycles;
|
|
},
|
|
|
|
dumpRegisters: function() {
|
|
return toHex(pc, 4) +
|
|
'- A=' + toHex(ar) +
|
|
' X=' + toHex(xr) +
|
|
' Y=' + toHex(yr) +
|
|
' P=' + toHex(sr) +
|
|
' S=' + toHex(sp) +
|
|
' ' +
|
|
((sr & flags.N) ? 'N' : '-') +
|
|
((sr & flags.V) ? 'V' : '-') +
|
|
'-' +
|
|
((sr & flags.B) ? 'B' : '-') +
|
|
((sr & flags.D) ? 'D' : '-') +
|
|
((sr & flags.I) ? 'I' : '-') +
|
|
((sr & flags.Z) ? 'Z' : '-') +
|
|
((sr & flags.C) ? 'C' : '-');
|
|
},
|
|
|
|
read: function(page, off) {
|
|
return readPages[page].read(page, off, false);
|
|
},
|
|
|
|
write: function(page, off, val) {
|
|
writePages[page].write(page, off, val);
|
|
}
|
|
};
|
|
}
|