apple1js/js/cpu6502.ts

3418 lines
73 KiB
TypeScript

import { Memory, MemberOf, MemoryPages, byte, word } from './types';
import { toHex } from './util';
export const FLAVOR_6502 = '6502';
export const FLAVOR_ROCKWELL_65C02 = 'rockwell65c02';
export const FLAVOR_WDC_65C02 = 'wdc65c02';
export const FLAVORS_65C02 = [FLAVOR_ROCKWELL_65C02, FLAVOR_WDC_65C02];
export const FLAVORS = [FLAVOR_6502, ...FLAVORS_65C02];
export type Flavor = MemberOf<typeof FLAVORS>;
export interface CpuOptions {
flavor?: Flavor;
}
export interface CpuState {
/** Accumulator */
a: byte;
/** X index */
x: byte;
/** Y index */
y: byte;
/** Status register */
s: byte;
/** Program counter */
pc: word;
/** Stack pointer */
sp: byte;
/** Elapsed cycles */
cycles: number;
}
export type Mode =
| 'accumulator' // A (Accumulator)
| 'implied' // Implied
| 'immediate' // # Immediate
| 'absolute' // a Absolute
| 'zeroPage' // zp Zero Page
| 'relative' // r Relative
| 'absoluteX' // a,X Absolute, X
| 'absoluteY' // a,Y Absolute, Y
| 'zeroPageX' // zp,X Zero Page, X
| 'zeroPageY' // zp,Y Zero Page, Y
| 'absoluteIndirect' // (a) Indirect
| 'zeroPageXIndirect' // (zp,X) Zero Page Indexed Indirect
| 'zeroPageIndirectY' // (zp),Y Zero Page Indexed with Y
| 'zeroPageIndirect' // (zp),
| 'absoluteXIndirect' // (a, X),
| 'zeroPage_relative'; // zp, Relative
export type Modes = Record<Mode, number>;
/** Addressing mode name to instruction size mapping. */
export const sizes: Modes = {
accumulator: 1,
implied: 1,
immediate: 2,
absolute: 3,
zeroPage: 2,
relative: 2,
absoluteX: 3,
absoluteY: 3,
zeroPageX: 2,
zeroPageY: 2,
absoluteIndirect: 3,
zeroPageXIndirect: 2,
zeroPageIndirectY: 2,
/* 65c02 */
zeroPageIndirect: 2,
absoluteXIndirect: 3,
zeroPage_relative: 3,
};
/** Status register flag numbers. */
export type flag = 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01;
/**
*
*/
export type DebugInfo = {
/** Program counter */
pc: word;
/** Accumulator */
ar: byte;
/** X index */
xr: byte;
/** Y index */
yr: byte;
/** Status register */
sr: byte;
/** Stack pointer */
sp: byte;
/** Current command */
cmd: byte[];
};
/** Flags to status byte mask. */
export const flags: { [key: string]: flag } = {
N: 0x80, // Negative
V: 0x40, // oVerflow
X: 0x20, // Unused, always 1
B: 0x10, // Break
D: 0x08, // Decimal
I: 0x04, // Interrupt
Z: 0x02, // Zero
C: 0x01, // Carry
};
/** CPU-referenced memory locations. */
const loc = {
STACK: 0x100,
NMI: 0xfffa,
RESET: 0xfffc,
BRK: 0xfffe,
};
interface ResettablePageHandler extends MemoryPages {
reset(): void;
}
function isResettablePageHandler(
pageHandler: MemoryPages | ResettablePageHandler
): pageHandler is ResettablePageHandler {
return (pageHandler as ResettablePageHandler).reset !== undefined;
}
const BLANK_PAGE: Memory = {
read: function () {
return 0;
},
write: function () {
/* not writable */
},
};
interface Opts {
inc?: boolean;
}
type ReadFn = () => byte;
type WriteFn = (val: byte) => void;
type ReadAddrFn = (opts?: Opts) => word;
type ImpliedFn = () => void;
interface Instruction<T = unknown> {
name: string;
mode: Mode;
op: (fn: T) => void;
modeFn: T;
}
type StrictInstruction =
| Instruction<ReadFn>
| Instruction<WriteFn>
| Instruction<ReadAddrFn>
| Instruction<ImpliedFn>
| Instruction<flag>
| Instruction<flag | 0>
| Instruction<byte>;
type Instructions = Record<byte, StrictInstruction>;
type callback = (cpu: CPU6502) => boolean | void;
export default class CPU6502 {
/** flavor */
private readonly flavor: Flavor;
/** 65C02 emulation mode flag */
private readonly is65C02: boolean;
/**
* Registers
*/
/** Program counter */
private pc: word = 0;
/** Status register */
private sr: byte = flags.X;
/** Accumulator */
private ar: byte = 0;
/** X index */
private xr: byte = 0;
/** Y index */
private yr: byte = 0;
/** Stack pointer */
private sp: byte = 0xff;
/** Current instruction */
private op: Instruction;
/** Last accessed memory address */
private addr: word = 0;
/** Filled array of memory handlers by address page */
private memPages: Memory[] = new Array<Memory>(0x100);
/** Callbacks invoked on reset signal */
private resetHandlers: ResettablePageHandler[] = [];
/** Elapsed cycles */
private cycles = 0;
/** Command being fetched signal */
private sync = false;
/** Processor is in WAI mode */
private wait = false;
/** Processor is in STP mode */
private stop = false;
/** Filled array of CPU operations */
private readonly opary: Instruction[];
constructor({ flavor }: CpuOptions = {}) {
this.flavor = flavor ?? FLAVOR_6502;
this.is65C02 = !!flavor && FLAVORS_65C02.includes(flavor);
this.memPages.fill(BLANK_PAGE);
this.memPages.fill(BLANK_PAGE);
// Create this CPU's instruction table
let ops = { ...this.OPS_6502 };
switch (this.flavor) {
case FLAVOR_WDC_65C02:
ops = {
...ops,
...this.OPS_65C02,
...this.OPS_WDC_65C02,
};
break;
case FLAVOR_ROCKWELL_65C02:
ops = {
...ops,
...this.OPS_65C02,
...this.OPS_ROCKWELL_65C02,
};
break;
default:
ops = {
...ops,
...this.OPS_NMOS_6502,
};
}
// Certain browsers benefit from using arrays over maps
this.opary = new Array<Instruction>(0x100);
for (let idx = 0; idx < 0x100; idx++) {
this.opary[idx] = ops[idx] || this.unknown(idx);
}
}
/**
* Set or clears `f` in the status register. `f` must be a byte with a
* single bit set.
*/
private setFlag(f: flag, on: boolean) {
this.sr = on ? this.sr | f : this.sr & ~f;
}
/** Updates the status register's zero flag and negative flag. */
private testNZ(val: byte) {
this.sr = val === 0 ? this.sr | flags.Z : this.sr & ~flags.Z;
this.sr = val & 0x80 ? this.sr | flags.N : this.sr & ~flags.N;
return val;
}
/** Updates the status register's zero flag. */
private testZ(val: byte) {
this.sr = val === 0 ? this.sr | flags.Z : this.sr & ~flags.Z;
return val;
}
/**
* Returns `a + b`, unless `sub` is true, in which case it performs
* `a - b`. The status register is updated according to the result.
*/
private add(a: byte, b: byte, sub: boolean): byte {
const a7 = a >> 7;
const b7 = b >> 7;
const ci = this.sr & flags.C;
let c;
let co;
let v;
let n;
let z;
const updateFlags = (c: byte) => {
const bin = c & 0xff;
n = bin >> 7;
co = c >> 8;
z = !((a + b + ci) & 0xff);
v = a7 ^ b7 ^ n ^ co;
};
const updateBCDFlags = (c: byte) => {
if (this.is65C02) {
const bin = c & 0xff;
n = bin >> 7;
z = !bin;
if (this.op.mode === 'immediate') {
if (this.flavor === FLAVOR_WDC_65C02) {
this.readByte(sub ? 0xb8 : 0x7f);
} else {
// rockwell65c02
this.readByte(sub ? 0xb1 : 0x59);
}
} else {
this.readByte(this.addr);
}
}
if (!sub) {
co = c >> 8;
}
};
c = (a & 0x0f) + (b & 0x0f) + ci;
if ((this.sr & flags.D) !== 0) {
// BCD
if (sub) {
if (c < 0x10) {
c = (c - 0x06) & 0x0f;
}
c += (a & 0xf0) + (b & 0xf0);
updateFlags(c);
if (c < 0x100) {
c += 0xa0;
}
} else {
if (c > 0x9) {
c = 0x10 + ((c + 0x6) & 0xf);
}
c += (a & 0xf0) + (b & 0xf0);
updateFlags(c);
if (c >= 0xa0) {
c += 0x60;
}
}
updateBCDFlags(c);
} else {
c += (a & 0xf0) + (b & 0xf0);
updateFlags(c);
}
c = c & 0xff;
this.setFlag(flags.N, !!n);
this.setFlag(flags.V, !!v);
this.setFlag(flags.Z, !!z);
this.setFlag(flags.C, !!co);
return c;
}
/** Increments `a` and returns the value, setting the status register. */
private increment(a: byte) {
return this.testNZ((a + 0x01) & 0xff);
}
private decrement(a: byte) {
return this.testNZ((a + 0xff) & 0xff);
}
private readBytePC(): byte {
const result = this.readByte(this.pc);
this.pc = (this.pc + 1) & 0xffff;
return result;
}
private readByte(addr: word): byte {
this.addr = addr;
const page = addr >> 8,
off = addr & 0xff;
const result = this.memPages[page].read(page, off);
this.cycles++;
return result;
}
private writeByte(addr: word, val: byte) {
this.addr = addr;
const page = addr >> 8,
off = addr & 0xff;
this.memPages[page].write(page, off, val);
this.cycles++;
}
private readWord(addr: word): word {
return this.readByte(addr) | (this.readByte(addr + 1) << 8);
}
private readWordPC(): word {
return this.readBytePC() | (this.readBytePC() << 8);
}
private readZPWord(addr: byte): word {
const lsb = this.readByte(addr & 0xff);
const msb = this.readByte((addr + 1) & 0xff);
return (msb << 8) | lsb;
}
private pushByte(val: byte) {
this.writeByte(loc.STACK | this.sp, val);
this.sp = (this.sp + 0xff) & 0xff;
}
private pushWord(val: word) {
this.pushByte(val >> 8);
this.pushByte(val & 0xff);
}
private pullByte(): byte {
this.sp = (this.sp + 0x01) & 0xff;
return this.readByte(loc.STACK | this.sp);
}
private pullWordRaw(): word {
const lsb = this.pullByte();
const msb = this.pullByte();
return (msb << 8) | lsb;
}
// Helpers that replicate false reads and writes during work cycles that
// vary between CPU versions
private workCycle(addr: word, val: byte) {
if (this.is65C02) {
this.readByte(addr);
} else {
this.writeByte(addr, val);
}
}
private workCycleIndexedWrite(pc: word, addr: word, addrIdx: word): void {
const oldPage = addr & 0xff00;
if (this.is65C02) {
this.readByte(pc);
} else {
const off = addrIdx & 0xff;
this.readByte(oldPage | off);
}
}
private workCycleIndexedRead(pc: word, addr: word, addrIdx: word): void {
const oldPage = addr & 0xff00;
const newPage = addrIdx & 0xff00;
if (newPage !== oldPage) {
if (this.is65C02) {
this.readByte(pc);
} else {
const off = addrIdx & 0xff;
this.readByte(oldPage | off);
}
}
}
/*
* Implied function
*/
implied = () => {
this.readByte(this.pc);
};
/*
* Read functions
*/
// #$00
readImmediate = (): byte => {
return this.readBytePC();
};
// $0000
readAbsolute = (): byte => {
return this.readByte(this.readWordPC());
};
// $00
readZeroPage = (): byte => {
return this.readByte(this.readBytePC());
};
// $0000,X
readAbsoluteX = (): byte => {
const addr = this.readWordPC();
const pc = this.addr;
const addrIdx = (addr + this.xr) & 0xffff;
this.workCycleIndexedRead(pc, addr, addrIdx);
return this.readByte(addrIdx);
};
// $0000,Y
readAbsoluteY = (): byte => {
const addr = this.readWordPC();
const pc = this.addr;
const addrIdx = (addr + this.yr) & 0xffff;
this.workCycleIndexedRead(pc, addr, addrIdx);
return this.readByte(addrIdx);
};
// $00,X
readZeroPageX = (): byte => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
return this.readByte((zpAddr + this.xr) & 0xff);
};
// $00,Y
readZeroPageY = (): byte => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
return this.readByte((zpAddr + this.yr) & 0xff);
};
// ($00,X)
readZeroPageXIndirect = (): byte => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
const addr = this.readZPWord((zpAddr + this.xr) & 0xff);
return this.readByte(addr);
};
// ($00),Y
readZeroPageIndirectY = (): byte => {
const zpAddr = this.readBytePC();
const pc = this.addr;
const addr = this.readZPWord(zpAddr);
const addrIdx = (addr + this.yr) & 0xffff;
this.workCycleIndexedRead(pc, addr, addrIdx);
return this.readByte(addrIdx);
};
// ($00) (65C02)
readZeroPageIndirect = (): byte => {
return this.readByte(this.readZPWord(this.readBytePC()));
};
/*
* Write Functions
*/
// $0000
writeAbsolute = (val: byte) => {
this.writeByte(this.readWordPC(), val);
};
// $00
writeZeroPage = (val: byte) => {
this.writeByte(this.readBytePC(), val);
};
// $0000,X
writeAbsoluteX = (val: byte) => {
const addr = this.readWordPC();
const pc = this.addr;
const addrIdx = (addr + this.xr) & 0xffff;
this.workCycleIndexedWrite(pc, addr, addrIdx);
this.writeByte(addrIdx, val);
};
// $0000,Y
writeAbsoluteY = (val: byte) => {
const addr = this.readWordPC();
const pc = this.addr;
const addrIdx = (addr + this.yr) & 0xffff;
this.workCycleIndexedWrite(pc, addr, addrIdx);
this.writeByte(addrIdx, val);
};
// $00,X
writeZeroPageX = (val: byte) => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
this.writeByte((zpAddr + this.xr) & 0xff, val);
};
// $00,Y
writeZeroPageY = (val: byte) => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
this.writeByte((zpAddr + this.yr) & 0xff, val);
};
// ($00,X)
writeZeroPageXIndirect = (val: byte) => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
const addr = this.readZPWord((zpAddr + this.xr) & 0xff);
this.writeByte(addr, val);
};
// ($00),Y
writeZeroPageIndirectY = (val: byte) => {
const zpAddr = this.readBytePC();
const pc = this.addr;
const addr = this.readZPWord(zpAddr);
const addrIdx = (addr + this.yr) & 0xffff;
this.workCycleIndexedWrite(pc, addr, addrIdx);
this.writeByte(addrIdx, val);
};
// ($00) (65C02)
writeZeroPageIndirect = (val: byte) => {
this.writeByte(this.readZPWord(this.readBytePC()), val);
};
// $00
readAddrZeroPage = () => {
return this.readBytePC();
};
// $00,X
readAddrZeroPageX = () => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
return (zpAddr + this.xr) & 0xff;
};
// $0000 (65C02)
readAddrAbsolute = (): word => {
return this.readWordPC();
};
// ($0000) (6502)
readAddrAbsoluteIndirectBug = (): word => {
const addr = this.readWordPC();
const page = addr & 0xff00;
const off = addr & 0x00ff;
const lsb = this.readByte(addr);
const msb = this.readByte(page | ((off + 0x01) & 0xff));
return (msb << 8) | lsb;
};
// ($0000) (65C02)
readAddrAbsoluteIndirect = (): word => {
const addr = this.readWord(this.readWordPC());
this.readByte(this.addr);
return addr;
};
// $0000,X
readAddrAbsoluteX = (opts?: Opts): word => {
let addr = this.readWordPC();
const page = addr & 0xff00;
addr = (addr + this.xr) & 0xffff;
if (this.is65C02) {
if (opts?.inc) {
this.readByte(this.addr);
} else {
const newPage = addr & 0xff00;
if (page !== newPage) {
this.readByte(this.addr);
}
}
} else {
const off = addr & 0x00ff;
this.readByte(page | off);
}
return addr;
};
// $0000,Y (NMOS 6502)
readAddrAbsoluteY = (): word => {
let addr = this.readWordPC();
const page = addr & 0xff00;
addr = (addr + this.yr) & 0xffff;
const off = addr & 0x00ff;
this.readByte(page | off);
return addr;
};
// ($00,X) (NMOS 6502)
readAddrZeroPageXIndirect = () => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
return this.readZPWord((zpAddr + this.xr) & 0xff);
};
// ($00),Y (NMOS 6502)
readAddrZeroPageIndirectY = () => {
const zpAddr = this.readBytePC();
const addr = this.readZPWord(zpAddr);
const addrIdx = (addr + this.yr) & 0xffff;
const oldPage = addr & 0xff00;
const off = addrIdx & 0xff;
this.readByte(oldPage | off);
return addrIdx;
};
// $(0000,X) (65C02)
readAddrAbsoluteXIndirect = (): word => {
const lsb = this.readBytePC();
const pc = this.addr;
const msb = this.readBytePC();
const addr = (((msb << 8) | lsb) + this.xr) & 0xffff;
this.readByte(pc);
return this.readWord(addr);
};
// 5C, DC, FC NOP (65C02)
readNop = (): void => {
this.readWordPC();
this.readByte(this.addr);
};
// NOP (65C02)
readNopImplied = (): void => {
// Op is 1 cycle
};
/* Break */
brk = (readFn: ReadFn) => {
readFn();
this.pushWord(this.pc);
this.pushByte(this.sr | flags.B);
if (this.is65C02) {
this.setFlag(flags.D, false);
}
this.setFlag(flags.I, true);
this.pc = this.readWord(loc.BRK);
};
/* Stop (65C02) */
stp = () => {
this.stop = true;
this.readByte(this.pc);
this.readByte(this.pc);
};
/* Wait (65C02) */
wai = () => {
this.wait = true;
this.readByte(this.pc);
this.readByte(this.pc);
};
/* Load Accumulator */
lda = (readFn: ReadFn) => {
this.ar = this.testNZ(readFn());
};
/* Load X Register */
ldx = (readFn: ReadFn) => {
this.xr = this.testNZ(readFn());
};
/* Load Y Register */
ldy = (readFn: ReadFn) => {
this.yr = this.testNZ(readFn());
};
/* Store Accumulator */
sta = (writeFn: WriteFn) => {
writeFn(this.ar);
};
/* Store X Register */
stx = (writeFn: WriteFn) => {
writeFn(this.xr);
};
/* Store Y Register */
sty = (writeFn: WriteFn) => {
writeFn(this.yr);
};
/* Store Zero */
stz = (writeFn: WriteFn) => {
writeFn(0);
};
/* Add with Carry */
adc = (readFn: ReadFn) => {
this.ar = this.add(this.ar, readFn(), /* sub= */ false);
};
/* Subtract with Carry */
sbc = (readFn: ReadFn) => {
this.ar = this.add(this.ar, readFn() ^ 0xff, /* sub= */ true);
};
/* Increment Memory */
incA = () => {
this.readByte(this.pc);
this.ar = this.increment(this.ar);
};
inc = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn({ inc: true });
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.increment(oldVal);
this.writeByte(addr, val);
};
/* Increment X */
inx = () => {
this.readByte(this.pc);
this.xr = this.increment(this.xr);
};
/* Increment Y */
iny = () => {
this.readByte(this.pc);
this.yr = this.increment(this.yr);
};
/* Decrement Memory */
decA = () => {
this.readByte(this.pc);
this.ar = this.decrement(this.ar);
};
dec = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn({ inc: true });
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.decrement(oldVal);
this.writeByte(addr, val);
};
/* Decrement X */
dex = () => {
this.readByte(this.pc);
this.xr = this.decrement(this.xr);
};
/* Decrement Y */
dey = () => {
this.readByte(this.pc);
this.yr = this.decrement(this.yr);
};
shiftLeft = (val: byte) => {
this.setFlag(flags.C, !!(val & 0x80));
return this.testNZ((val << 1) & 0xff);
};
/* Arithmetic Shift Left */
aslA = () => {
this.readByte(this.pc);
this.ar = this.shiftLeft(this.ar);
};
asl = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.shiftLeft(oldVal);
this.writeByte(addr, val);
};
shiftRight = (val: byte) => {
this.setFlag(flags.C, !!(val & 0x01));
return this.testNZ(val >> 1);
};
/* Logical Shift Right */
lsrA = () => {
this.readByte(this.pc);
this.ar = this.shiftRight(this.ar);
};
lsr = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.shiftRight(oldVal);
this.writeByte(addr, val);
};
rotateLeft = (val: byte) => {
const c = this.sr & flags.C;
this.setFlag(flags.C, !!(val & 0x80));
return this.testNZ(((val << 1) | (c ? 0x01 : 0x00)) & 0xff);
};
/* Rotate Left */
rolA = () => {
this.readByte(this.pc);
this.ar = this.rotateLeft(this.ar);
};
rol = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.rotateLeft(oldVal);
this.writeByte(addr, val);
};
private rotateRight(a: byte) {
const c = this.sr & flags.C;
this.setFlag(flags.C, !!(a & 0x01));
return this.testNZ((a >> 1) | (c ? 0x80 : 0x00));
}
/* Rotate Right */
rorA = () => {
this.readByte(this.pc);
this.ar = this.rotateRight(this.ar);
};
ror = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.rotateRight(oldVal);
this.writeByte(addr, val);
};
/* Logical And Accumulator */
and = (readFn: ReadFn) => {
this.ar = this.testNZ(this.ar & readFn());
};
/* Logical Or Accumulator */
ora = (readFn: ReadFn) => {
this.ar = this.testNZ(this.ar | readFn());
};
/* Logical Exclusive Or Accumulator */
eor = (readFn: ReadFn) => {
this.ar = this.testNZ(this.ar ^ readFn());
};
/* Reset Bit */
rmb = (b: byte) => {
const bit = (0x1 << b) ^ 0xff;
const addr = this.readBytePC();
let val = this.readByte(addr);
this.readByte(addr);
val &= bit;
this.writeByte(addr, val);
};
/* Set Bit */
smb = (b: byte) => {
const bit = 0x1 << b;
const addr = this.readBytePC();
let val = this.readByte(addr);
this.readByte(addr);
val |= bit;
this.writeByte(addr, val);
};
/* Test and Reset Bits */
trb = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const val = this.readByte(addr);
this.testZ(val & this.ar);
this.readByte(addr);
this.writeByte(addr, val & ~this.ar);
};
/* Test and Set Bits */
tsb = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const val = this.readByte(addr);
this.testZ(val & this.ar);
this.readByte(addr);
this.writeByte(addr, val | this.ar);
};
/* Bit */
bit = (readFn: ReadFn) => {
const val = readFn();
this.setFlag(flags.Z, (val & this.ar) === 0);
this.setFlag(flags.N, !!(val & 0x80));
this.setFlag(flags.V, !!(val & 0x40));
};
/* Bit Immediate*/
bitI = (readFn: ReadFn) => {
const val = readFn();
this.setFlag(flags.Z, (val & this.ar) === 0);
};
private compare(a: byte, b: byte) {
b = b ^ 0xff;
const c = a + b + 1;
this.setFlag(flags.C, c > 0xff);
this.testNZ(c & 0xff);
}
cmp = (readFn: ReadFn) => {
this.compare(this.ar, readFn());
};
cpx = (readFn: ReadFn) => {
this.compare(this.xr, readFn());
};
cpy = (readFn: ReadFn) => {
this.compare(this.yr, readFn());
};
/* Branches */
brs = (f: flag) => {
const off = this.readBytePC(); // changes pc
if ((f & this.sr) !== 0) {
this.readByte(this.pc);
const oldPage = this.pc & 0xff00;
this.pc += off > 127 ? off - 256 : off;
this.pc &= 0xffff;
const newPage = this.pc & 0xff00;
const newOff = this.pc & 0xff;
if (newPage !== oldPage) this.readByte(oldPage | newOff);
}
};
brc = (f: flag | 0) => {
const off = this.readBytePC(); // changes pc
if ((f & this.sr) === 0) {
this.readByte(this.pc);
const oldPage = this.pc & 0xff00;
this.pc += off > 127 ? off - 256 : off;
this.pc &= 0xffff;
const newPage = this.pc & 0xff00;
const newOff = this.pc & 0xff;
if (newPage !== oldPage) this.readByte(oldPage | newOff);
}
};
/* WDC 65C02 branches */
bbr = (b: byte) => {
const zpAddr = this.readBytePC();
const val = this.readByte(zpAddr);
this.writeByte(zpAddr, val);
const off = this.readBytePC(); // changes pc
const oldPc = this.pc;
const oldPage = oldPc & 0xff00;
let newPC = this.pc + (off > 127 ? off - 256 : off);
newPC &= 0xffff;
const newOff = newPC & 0xff;
this.readByte(oldPage | newOff);
if (((1 << b) & val) === 0) {
this.pc = newPC;
}
};
bbs = (b: byte) => {
const zpAddr = this.readBytePC();
const val = this.readByte(zpAddr);
this.writeByte(zpAddr, val);
const off = this.readBytePC(); // changes pc
const oldPc = this.pc;
const oldPage = oldPc & 0xff00;
let newPC = this.pc + (off > 127 ? off - 256 : off);
newPC &= 0xffff;
const newOff = newPC & 0xff;
this.readByte(oldPage | newOff);
if (((1 << b) & val) !== 0) {
this.pc = newPC;
}
};
/* Transfers and stack */
tax = () => {
this.readByte(this.pc);
this.testNZ((this.xr = this.ar));
};
txa = () => {
this.readByte(this.pc);
this.testNZ((this.ar = this.xr));
};
tay = () => {
this.readByte(this.pc);
this.testNZ((this.yr = this.ar));
};
tya = () => {
this.readByte(this.pc);
this.testNZ((this.ar = this.yr));
};
tsx = () => {
this.readByte(this.pc);
this.testNZ((this.xr = this.sp));
};
txs = () => {
this.readByte(this.pc);
this.sp = this.xr;
};
pha = () => {
this.readByte(this.pc);
this.pushByte(this.ar);
};
pla = () => {
this.readByte(this.pc);
this.readByte(0x0100 | this.sp);
this.testNZ((this.ar = this.pullByte()));
};
phx = () => {
this.readByte(this.pc);
this.pushByte(this.xr);
};
plx = () => {
this.readByte(this.pc);
this.readByte(0x0100 | this.sp);
this.testNZ((this.xr = this.pullByte()));
};
phy = () => {
this.readByte(this.pc);
this.pushByte(this.yr);
};
ply = () => {
this.readByte(this.pc);
this.readByte(0x0100 | this.sp);
this.testNZ((this.yr = this.pullByte()));
};
php = () => {
this.readByte(this.pc);
this.pushByte(this.sr | flags.B);
};
plp = () => {
this.readByte(this.pc);
this.readByte(0x0100 | this.sp);
this.sr = (this.pullByte() & ~flags.B) | flags.X;
};
/* Jump */
jmp = (readAddrFn: ReadAddrFn) => {
this.pc = readAddrFn();
};
/* Jump Subroutine */
jsr = () => {
const lsb = this.readBytePC();
this.readByte(0x0100 | this.sp);
this.pushWord(this.pc);
const msb = this.readBytePC();
this.pc = ((msb << 8) | lsb) & 0xffff;
};
/* Return from Subroutine */
rts = () => {
this.readByte(this.pc);
this.readByte(0x0100 | this.sp);
const addr = this.pullWordRaw();
this.readByte(addr);
this.pc = (addr + 1) & 0xffff;
};
/* Return from Interrupt */
rti = () => {
this.readByte(this.pc);
this.readByte(0x0100 | this.sp);
this.sr = (this.pullByte() & ~flags.B) | flags.X;
this.pc = this.pullWordRaw();
};
/* Set and Clear */
set = (flag: flag) => {
this.readByte(this.pc);
this.sr |= flag;
};
clr = (flag: flag) => {
this.readByte(this.pc);
this.sr &= ~flag;
};
/* No-Op */
nop = (readFn: ImpliedFn | ReadFn) => {
readFn();
};
/* NMOS 6502 Illegal opcodes */
/* ASO = ASL + ORA */
aso = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.shiftLeft(oldVal);
this.writeByte(addr, val);
this.ar |= val;
this.testNZ(this.ar);
};
/* RLA = ROL + AND */
rla = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.rotateLeft(oldVal);
this.writeByte(addr, val);
this.ar &= val;
this.testNZ(this.ar);
};
/* LSE = LSR + EOR */
lse = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.shiftRight(oldVal);
this.writeByte(addr, val);
this.ar ^= val;
this.testNZ(this.ar);
};
/* RRA = ROR + ADC */
rra = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.rotateRight(oldVal);
this.writeByte(addr, val);
this.ar = this.add(this.ar, val, false);
};
/* AXS = Store A & X */
axs = (writeFn: WriteFn) => {
writeFn(this.ar & this.xr);
};
/* LAX = Load A & X */
lax = (readFn: ReadFn) => {
const val = readFn();
this.ar = val;
this.xr = val;
this.testNZ(val);
};
/* DCM = DEC + CMP */
dcm = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn({ inc: true });
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.decrement(oldVal);
this.writeByte(addr, val);
this.compare(this.ar, val);
};
/* INS = INC + SBC */
ins = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn({ inc: true });
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.increment(oldVal);
this.writeByte(addr, val);
this.ar = this.add(this.ar, val ^ 0xff, true);
};
/* ALR = AND + LSR */
alr = (readFn: ReadFn) => {
const val = readFn() & this.ar;
this.ar = this.shiftRight(val);
};
/* ARR = AND + ROR */
arr = (readFn: ReadFn) => {
const val = readFn() & this.ar;
const ah = val >> 4;
const al = val & 0xf;
const b7 = val >> 7;
const b6 = (val >> 6) & 0x1;
this.ar = this.rotateRight(val);
let c = !!b7;
const v = !!(b7 ^ b6);
if (this.sr & flags.D) {
if (al + (al & 0x1) > 0x5) {
this.ar = (this.ar & 0xf0) | ((this.ar + 0x6) & 0xf);
}
if (ah + (ah & 0x1) > 5) {
c = true;
this.ar = (this.ar + 0x60) & 0xff;
}
}
this.setFlag(flags.V, v);
this.setFlag(flags.C, c);
};
/* XAA = TAX + AND */
xaa = (readFn: ReadFn) => {
const val = readFn();
this.ar = (this.xr & 0xee) | (this.xr & this.ar & 0x11);
this.ar = this.testNZ(this.ar & val);
};
/** OAL = ORA + AND */
oal = (readFn: ReadFn) => {
this.ar |= 0xee;
const val = this.testNZ(this.ar & readFn());
this.ar = val;
this.xr = val;
};
/* SAX = A & X + SBC */
sax = (readFn: ReadFn) => {
const a = this.xr & this.ar;
let b = readFn();
b = b ^ 0xff;
const c = a + b + 1;
this.setFlag(flags.C, c > 0xff);
this.xr = this.testNZ(c & 0xff);
};
/* TAS = X & Y -> S */
tas = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
let val = this.xr & this.ar;
this.sp = val;
const msb = addr >> 8;
val = val & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* SAY = Y & AH + 1 */
say = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const msb = addr >> 8;
const val = this.yr & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* XAS = X & AH + 1 */
xas = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const msb = addr >> 8;
const val = this.xr & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* AXA = X & AH + 1 */
axa = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
let val = this.xr & this.ar;
const msb = addr >> 8;
val = val & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* ANC = AND with carry */
anc = (readFn: ReadFn) => {
this.ar = this.testNZ(this.ar & readFn());
const c = !!(this.ar & 0x80);
this.setFlag(flags.C, c);
};
/* LAS = RD & SP -> A, X, S */
las = (readFn: ReadFn) => {
const val = this.sp & readFn();
this.sp = val;
this.xr = val;
this.ar = this.testNZ(val);
};
/* SKB/SKW */
skp = (readFn: ReadFn) => {
readFn();
};
/* HLT */
hlt = (_impliedFn: ImpliedFn) => {
this.readByte(this.pc);
this.readByte(this.pc);
// PC shouldn't have advanced
this.pc = --this.pc & 0xffff;
this.stop = true;
};
private unknown(b: byte) {
let unk: StrictInstruction;
if (this.is65C02) {
// Default behavior is a 1 cycle NOP
unk = {
name: 'NOP',
op: this.nop,
modeFn: this.readNopImplied,
mode: 'implied',
};
} else {
// All 6502 Instructions should be defined
throw new Error(`Missing ${toHex(b)}`);
}
return unk;
}
public step(cb?: callback) {
this.sync = true;
this.op = this.opary[this.readBytePC()];
this.sync = false;
this.op.op(this.op.modeFn);
cb?.(this);
}
public stepN(n: number, cb?: callback) {
for (let idx = 0; idx < n; idx++) {
this.sync = true;
this.op = this.opary[this.readBytePC()];
this.sync = false;
this.op.op(this.op.modeFn);
if (cb?.(this)) {
return;
}
}
}
public stepCycles(c: number) {
const end = this.cycles + c;
while (this.cycles < end) {
this.sync = true;
this.op = this.opary[this.readBytePC()];
this.sync = false;
this.op.op(this.op.modeFn);
}
}
public stepCyclesDebug(c: number, cb?: callback): void {
const end = this.cycles + c;
while (this.cycles < end) {
this.sync = true;
this.op = this.opary[this.readBytePC()];
this.sync = false;
this.op.op(this.op.modeFn);
if (cb?.(this)) {
return;
}
}
}
public addPageHandler(pho: MemoryPages | ResettablePageHandler) {
for (let idx = pho.start(); idx <= pho.end(); idx++) {
this.memPages[idx] = pho;
}
if (isResettablePageHandler(pho)) this.resetHandlers.push(pho);
}
public reset() {
// cycles = 0;
this.sr = flags.X;
this.sp = 0xff;
this.ar = 0;
this.yr = 0;
this.xr = 0;
this.pc = this.readWord(loc.RESET);
this.wait = false;
this.stop = false;
for (let idx = 0; idx < this.resetHandlers.length; idx++) {
this.resetHandlers[idx].reset();
}
}
/* IRQ - Interrupt Request */
public irq() {
if ((this.sr & flags.I) === 0) {
this.pushWord(this.pc);
this.pushByte(this.sr & ~flags.B);
if (this.is65C02) {
this.setFlag(flags.D, false);
}
this.setFlag(flags.I, true);
this.pc = this.readWord(loc.BRK);
this.wait = false;
}
}
/* NMI Non-maskable Interrupt */
public nmi() {
this.pushWord(this.pc);
this.pushByte(this.sr & ~flags.B);
if (this.is65C02) {
this.setFlag(flags.D, false);
}
this.setFlag(flags.I, true);
this.pc = this.readWord(loc.NMI);
this.wait = false;
}
public getPC() {
return this.pc;
}
public setPC(pc: word) {
this.pc = pc;
}
public getDebugInfo(): DebugInfo {
const b = this.read(this.pc);
const op = this.opary[b];
const size = sizes[op.mode];
const cmd = new Array(size);
cmd[0] = b;
for (let idx = 1; idx < size; idx++) {
cmd[idx] = this.read(this.pc + idx);
}
return {
pc: this.pc,
ar: this.ar,
xr: this.xr,
yr: this.yr,
sr: this.sr,
sp: this.sp,
cmd,
};
}
public getSync() {
return this.sync;
}
public getStop() {
return this.stop;
}
public getWait() {
return this.wait;
}
public getCycles() {
return this.cycles;
}
public getOpInfo(opcode: byte) {
return this.opary[opcode];
}
public getState(): CpuState {
return {
a: this.ar,
x: this.xr,
y: this.yr,
s: this.sr,
pc: this.pc,
sp: this.sp,
cycles: this.cycles,
};
}
public setState(state: CpuState) {
this.ar = state.a;
this.xr = state.x;
this.yr = state.y;
this.sr = state.s;
this.pc = state.pc;
this.sp = state.sp;
this.cycles = state.cycles;
}
public read(addr: word): byte;
public read(page: byte, off: byte): byte;
public read(a: number, b?: number): byte {
let page, off;
if (b !== undefined) {
page = a & 0xff;
off = b & 0xff;
} else {
page = (a >> 8) & 0xff;
off = a & 0xff;
}
return this.memPages[page].read(page, off);
}
public write(addr: word, val: byte): void;
public write(page: byte, off: byte, val: byte): void;
public write(a: number, b: number, c?: byte): void {
let page, off, val;
if (c !== undefined) {
page = a & 0xff;
off = b & 0xff;
val = c & 0xff;
} else {
page = (a >> 8) & 0xff;
off = a & 0xff;
val = b & 0xff;
}
this.memPages[page].write(page, off, val);
}
OPS_6502: Instructions = {
// LDA
0xa9: {
name: 'LDA',
op: this.lda,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xa5: {
name: 'LDA',
op: this.lda,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xb5: {
name: 'LDA',
op: this.lda,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0xad: {
name: 'LDA',
op: this.lda,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0xbd: {
name: 'LDA',
op: this.lda,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0xb9: {
name: 'LDA',
op: this.lda,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0xa1: {
name: 'LDA',
op: this.lda,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0xb1: {
name: 'LDA',
op: this.lda,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// LDX
0xa2: {
name: 'LDX',
op: this.ldx,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xa6: {
name: 'LDX',
op: this.ldx,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xb6: {
name: 'LDX',
op: this.ldx,
modeFn: this.readZeroPageY,
mode: 'zeroPageY',
},
0xae: {
name: 'LDX',
op: this.ldx,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0xbe: {
name: 'LDX',
op: this.ldx,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
// LDY
0xa0: {
name: 'LDY',
op: this.ldy,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xa4: {
name: 'LDY',
op: this.ldy,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xb4: {
name: 'LDY',
op: this.ldy,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0xac: {
name: 'LDY',
op: this.ldy,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0xbc: {
name: 'LDY',
op: this.ldy,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
// STA
0x85: {
name: 'STA',
op: this.sta,
modeFn: this.writeZeroPage,
mode: 'zeroPage',
},
0x95: {
name: 'STA',
op: this.sta,
modeFn: this.writeZeroPageX,
mode: 'zeroPageX',
},
0x8d: {
name: 'STA',
op: this.sta,
modeFn: this.writeAbsolute,
mode: 'absolute',
},
0x9d: {
name: 'STA',
op: this.sta,
modeFn: this.writeAbsoluteX,
mode: 'absoluteX',
},
0x99: {
name: 'STA',
op: this.sta,
modeFn: this.writeAbsoluteY,
mode: 'absoluteY',
},
0x81: {
name: 'STA',
op: this.sta,
modeFn: this.writeZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x91: {
name: 'STA',
op: this.sta,
modeFn: this.writeZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// STX
0x86: {
name: 'STX',
op: this.stx,
modeFn: this.writeZeroPage,
mode: 'zeroPage',
},
0x96: {
name: 'STX',
op: this.stx,
modeFn: this.writeZeroPageY,
mode: 'zeroPageY',
},
0x8e: {
name: 'STX',
op: this.stx,
modeFn: this.writeAbsolute,
mode: 'absolute',
},
// STY
0x84: {
name: 'STY',
op: this.sty,
modeFn: this.writeZeroPage,
mode: 'zeroPage',
},
0x94: {
name: 'STY',
op: this.sty,
modeFn: this.writeZeroPageX,
mode: 'zeroPageX',
},
0x8c: {
name: 'STY',
op: this.sty,
modeFn: this.writeAbsolute,
mode: 'absolute',
},
// ADC
0x69: {
name: 'ADC',
op: this.adc,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x65: {
name: 'ADC',
op: this.adc,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x75: {
name: 'ADC',
op: this.adc,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x6d: {
name: 'ADC',
op: this.adc,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0x7d: {
name: 'ADC',
op: this.adc,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0x79: {
name: 'ADC',
op: this.adc,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0x61: {
name: 'ADC',
op: this.adc,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x71: {
name: 'ADC',
op: this.adc,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// SBC
0xe9: {
name: 'SBC',
op: this.sbc,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xe5: {
name: 'SBC',
op: this.sbc,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xf5: {
name: 'SBC',
op: this.sbc,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0xed: {
name: 'SBC',
op: this.sbc,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0xfd: {
name: 'SBC',
op: this.sbc,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0xf9: {
name: 'SBC',
op: this.sbc,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0xe1: {
name: 'SBC',
op: this.sbc,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0xf1: {
name: 'SBC',
op: this.sbc,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// INC
0xe6: {
name: 'INC',
op: this.inc,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0xf6: {
name: 'INC',
op: this.inc,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0xee: {
name: 'INC',
op: this.inc,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0xfe: {
name: 'INC',
op: this.inc,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// INX
0xe8: { name: 'INX', op: this.inx, modeFn: this.implied, mode: 'implied' },
// INY
0xc8: { name: 'INY', op: this.iny, modeFn: this.implied, mode: 'implied' },
// DEC
0xc6: {
name: 'DEC',
op: this.dec,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0xd6: {
name: 'DEC',
op: this.dec,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0xce: {
name: 'DEC',
op: this.dec,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0xde: {
name: 'DEC',
op: this.dec,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// DEX
0xca: { name: 'DEX', op: this.dex, modeFn: this.implied, mode: 'implied' },
// DEY
0x88: { name: 'DEY', op: this.dey, modeFn: this.implied, mode: 'implied' },
// ASL
0x0a: {
name: 'ASL',
op: this.aslA,
modeFn: this.implied,
mode: 'accumulator',
},
0x06: {
name: 'ASL',
op: this.asl,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x16: {
name: 'ASL',
op: this.asl,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x0e: {
name: 'ASL',
op: this.asl,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x1e: {
name: 'ASL',
op: this.asl,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// LSR
0x4a: {
name: 'LSR',
op: this.lsrA,
modeFn: this.implied,
mode: 'accumulator',
},
0x46: {
name: 'LSR',
op: this.lsr,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x56: {
name: 'LSR',
op: this.lsr,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x4e: {
name: 'LSR',
op: this.lsr,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x5e: {
name: 'LSR',
op: this.lsr,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// ROL
0x2a: {
name: 'ROL',
op: this.rolA,
modeFn: this.implied,
mode: 'accumulator',
},
0x26: {
name: 'ROL',
op: this.rol,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x36: {
name: 'ROL',
op: this.rol,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x2e: {
name: 'ROL',
op: this.rol,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x3e: {
name: 'ROL',
op: this.rol,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// ROR
0x6a: {
name: 'ROR',
op: this.rorA,
modeFn: this.implied,
mode: 'accumulator',
},
0x66: {
name: 'ROR',
op: this.ror,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x76: {
name: 'ROR',
op: this.ror,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x6e: {
name: 'ROR',
op: this.ror,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x7e: {
name: 'ROR',
op: this.ror,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// AND
0x29: {
name: 'AND',
op: this.and,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x25: {
name: 'AND',
op: this.and,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x35: {
name: 'AND',
op: this.and,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x2d: {
name: 'AND',
op: this.and,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0x3d: {
name: 'AND',
op: this.and,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0x39: {
name: 'AND',
op: this.and,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0x21: {
name: 'AND',
op: this.and,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x31: {
name: 'AND',
op: this.and,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// ORA
0x09: {
name: 'ORA',
op: this.ora,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x05: {
name: 'ORA',
op: this.ora,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x15: {
name: 'ORA',
op: this.ora,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x0d: {
name: 'ORA',
op: this.ora,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0x1d: {
name: 'ORA',
op: this.ora,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0x19: {
name: 'ORA',
op: this.ora,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0x01: {
name: 'ORA',
op: this.ora,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x11: {
name: 'ORA',
op: this.ora,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// EOR
0x49: {
name: 'EOR',
op: this.eor,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x45: {
name: 'EOR',
op: this.eor,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x55: {
name: 'EOR',
op: this.eor,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x4d: {
name: 'EOR',
op: this.eor,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0x5d: {
name: 'EOR',
op: this.eor,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0x59: {
name: 'EOR',
op: this.eor,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0x41: {
name: 'EOR',
op: this.eor,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x51: {
name: 'EOR',
op: this.eor,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// CMP
0xc9: {
name: 'CMP',
op: this.cmp,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xc5: {
name: 'CMP',
op: this.cmp,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xd5: {
name: 'CMP',
op: this.cmp,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0xcd: {
name: 'CMP',
op: this.cmp,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0xdd: {
name: 'CMP',
op: this.cmp,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0xd9: {
name: 'CMP',
op: this.cmp,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0xc1: {
name: 'CMP',
op: this.cmp,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0xd1: {
name: 'CMP',
op: this.cmp,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// CPX
0xe0: {
name: 'CPX',
op: this.cpx,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xe4: {
name: 'CPX',
op: this.cpx,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xec: {
name: 'CPX',
op: this.cpx,
modeFn: this.readAbsolute,
mode: 'absolute',
},
// CPY
0xc0: {
name: 'CPY',
op: this.cpy,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xc4: {
name: 'CPY',
op: this.cpy,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xcc: {
name: 'CPY',
op: this.cpy,
modeFn: this.readAbsolute,
mode: 'absolute',
},
// BIT
0x24: {
name: 'BIT',
op: this.bit,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x2c: {
name: 'BIT',
op: this.bit,
modeFn: this.readAbsolute,
mode: 'absolute',
},
// BCC
0x90: { name: 'BCC', op: this.brc, modeFn: flags.C, mode: 'relative' },
// BCS
0xb0: { name: 'BCS', op: this.brs, modeFn: flags.C, mode: 'relative' },
// BEQ
0xf0: { name: 'BEQ', op: this.brs, modeFn: flags.Z, mode: 'relative' },
// BMI
0x30: { name: 'BMI', op: this.brs, modeFn: flags.N, mode: 'relative' },
// BNE
0xd0: { name: 'BNE', op: this.brc, modeFn: flags.Z, mode: 'relative' },
// BPL
0x10: { name: 'BPL', op: this.brc, modeFn: flags.N, mode: 'relative' },
// BVC
0x50: { name: 'BVC', op: this.brc, modeFn: flags.V, mode: 'relative' },
// BVS
0x70: { name: 'BVS', op: this.brs, modeFn: flags.V, mode: 'relative' },
// TAX
0xaa: { name: 'TAX', op: this.tax, modeFn: this.implied, mode: 'implied' },
// TXA
0x8a: { name: 'TXA', op: this.txa, modeFn: this.implied, mode: 'implied' },
// TAY
0xa8: { name: 'TAY', op: this.tay, modeFn: this.implied, mode: 'implied' },
// TYA
0x98: { name: 'TYA', op: this.tya, modeFn: this.implied, mode: 'implied' },
// TSX
0xba: { name: 'TSX', op: this.tsx, modeFn: this.implied, mode: 'implied' },
// TXS
0x9a: { name: 'TXS', op: this.txs, modeFn: this.implied, mode: 'implied' },
// PHA
0x48: { name: 'PHA', op: this.pha, modeFn: this.implied, mode: 'implied' },
// PLA
0x68: { name: 'PLA', op: this.pla, modeFn: this.implied, mode: 'implied' },
// PHP
0x08: { name: 'PHP', op: this.php, modeFn: this.implied, mode: 'implied' },
// PLP
0x28: { name: 'PLP', op: this.plp, modeFn: this.implied, mode: 'implied' },
// JMP
0x4c: {
name: 'JMP',
op: this.jmp,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x6c: {
name: 'JMP',
op: this.jmp,
modeFn: this.readAddrAbsoluteIndirectBug,
mode: 'absoluteIndirect',
},
// JSR
0x20: {
name: 'JSR',
op: this.jsr,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
// RTS
0x60: { name: 'RTS', op: this.rts, modeFn: this.implied, mode: 'implied' },
// RTI
0x40: { name: 'RTI', op: this.rti, modeFn: this.implied, mode: 'implied' },
// SEC
0x38: { name: 'SEC', op: this.set, modeFn: flags.C, mode: 'implied' },
// SED
0xf8: { name: 'SED', op: this.set, modeFn: flags.D, mode: 'implied' },
// SEI
0x78: { name: 'SEI', op: this.set, modeFn: flags.I, mode: 'implied' },
// CLC
0x18: { name: 'CLC', op: this.clr, modeFn: flags.C, mode: 'implied' },
// CLD
0xd8: { name: 'CLD', op: this.clr, modeFn: flags.D, mode: 'implied' },
// CLI
0x58: { name: 'CLI', op: this.clr, modeFn: flags.I, mode: 'implied' },
// CLV
0xb8: { name: 'CLV', op: this.clr, modeFn: flags.V, mode: 'implied' },
// NOP
0xea: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
// BRK
0x00: {
name: 'BRK',
op: this.brk,
modeFn: this.readImmediate,
mode: 'immediate',
},
};
/* 65C02 Instructions */
OPS_65C02: Instructions = {
// INC / DEC A
0x1a: {
name: 'INC',
op: this.incA,
modeFn: this.implied,
mode: 'accumulator',
},
0x3a: {
name: 'DEC',
op: this.decA,
modeFn: this.implied,
mode: 'accumulator',
},
// Indirect Zero Page for the masses
0x12: {
name: 'ORA',
op: this.ora,
modeFn: this.readZeroPageIndirect,
mode: 'zeroPageIndirect',
},
0x32: {
name: 'AND',
op: this.and,
modeFn: this.readZeroPageIndirect,
mode: 'zeroPageIndirect',
},
0x52: {
name: 'EOR',
op: this.eor,
modeFn: this.readZeroPageIndirect,
mode: 'zeroPageIndirect',
},
0x72: {
name: 'ADC',
op: this.adc,
modeFn: this.readZeroPageIndirect,
mode: 'zeroPageIndirect',
},
0x92: {
name: 'STA',
op: this.sta,
modeFn: this.writeZeroPageIndirect,
mode: 'zeroPageIndirect',
},
0xb2: {
name: 'LDA',
op: this.lda,
modeFn: this.readZeroPageIndirect,
mode: 'zeroPageIndirect',
},
0xd2: {
name: 'CMP',
op: this.cmp,
modeFn: this.readZeroPageIndirect,
mode: 'zeroPageIndirect',
},
0xf2: {
name: 'SBC',
op: this.sbc,
modeFn: this.readZeroPageIndirect,
mode: 'zeroPageIndirect',
},
// Better BIT
0x34: {
name: 'BIT',
op: this.bit,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x3c: {
name: 'BIT',
op: this.bit,
modeFn: this.readAbsoluteX,
mode: 'absoluteX',
},
0x89: {
name: 'BIT',
op: this.bitI,
modeFn: this.readImmediate,
mode: 'immediate',
},
// JMP absolute indirect indexed
0x6c: {
name: 'JMP',
op: this.jmp,
modeFn: this.readAddrAbsoluteIndirect,
mode: 'absoluteIndirect',
},
0x7c: {
name: 'JMP',
op: this.jmp,
modeFn: this.readAddrAbsoluteXIndirect,
mode: 'absoluteXIndirect',
},
// BBR/BBS
0x0f: { name: 'BBR0', op: this.bbr, modeFn: 0, mode: 'zeroPage_relative' },
0x1f: { name: 'BBR1', op: this.bbr, modeFn: 1, mode: 'zeroPage_relative' },
0x2f: { name: 'BBR2', op: this.bbr, modeFn: 2, mode: 'zeroPage_relative' },
0x3f: { name: 'BBR3', op: this.bbr, modeFn: 3, mode: 'zeroPage_relative' },
0x4f: { name: 'BBR4', op: this.bbr, modeFn: 4, mode: 'zeroPage_relative' },
0x5f: { name: 'BBR5', op: this.bbr, modeFn: 5, mode: 'zeroPage_relative' },
0x6f: { name: 'BBR6', op: this.bbr, modeFn: 6, mode: 'zeroPage_relative' },
0x7f: { name: 'BBR7', op: this.bbr, modeFn: 7, mode: 'zeroPage_relative' },
0x8f: { name: 'BBS0', op: this.bbs, modeFn: 0, mode: 'zeroPage_relative' },
0x9f: { name: 'BBS1', op: this.bbs, modeFn: 1, mode: 'zeroPage_relative' },
0xaf: { name: 'BBS2', op: this.bbs, modeFn: 2, mode: 'zeroPage_relative' },
0xbf: { name: 'BBS3', op: this.bbs, modeFn: 3, mode: 'zeroPage_relative' },
0xcf: { name: 'BBS4', op: this.bbs, modeFn: 4, mode: 'zeroPage_relative' },
0xdf: { name: 'BBS5', op: this.bbs, modeFn: 5, mode: 'zeroPage_relative' },
0xef: { name: 'BBS6', op: this.bbs, modeFn: 6, mode: 'zeroPage_relative' },
0xff: { name: 'BBS7', op: this.bbs, modeFn: 7, mode: 'zeroPage_relative' },
// BRA
0x80: { name: 'BRA', op: this.brc, modeFn: 0, mode: 'relative' },
// NOP
0x02: {
name: 'NOP',
op: this.nop,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x22: {
name: 'NOP',
op: this.nop,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x42: {
name: 'NOP',
op: this.nop,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x44: {
name: 'NOP',
op: this.nop,
modeFn: this.readZeroPage,
mode: 'immediate',
},
0x54: {
name: 'NOP',
op: this.nop,
modeFn: this.readZeroPageX,
mode: 'immediate',
},
0x62: {
name: 'NOP',
op: this.nop,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x82: {
name: 'NOP',
op: this.nop,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xc2: {
name: 'NOP',
op: this.nop,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xd4: {
name: 'NOP',
op: this.nop,
modeFn: this.readZeroPageX,
mode: 'immediate',
},
0xe2: {
name: 'NOP',
op: this.nop,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xf4: {
name: 'NOP',
op: this.nop,
modeFn: this.readZeroPageX,
mode: 'immediate',
},
0x5c: { name: 'NOP', op: this.nop, modeFn: this.readNop, mode: 'absolute' },
0xdc: { name: 'NOP', op: this.nop, modeFn: this.readNop, mode: 'absolute' },
0xfc: { name: 'NOP', op: this.nop, modeFn: this.readNop, mode: 'absolute' },
// PHX
0xda: { name: 'PHX', op: this.phx, modeFn: this.implied, mode: 'implied' },
// PHY
0x5a: { name: 'PHY', op: this.phy, modeFn: this.implied, mode: 'implied' },
// PLX
0xfa: { name: 'PLX', op: this.plx, modeFn: this.implied, mode: 'implied' },
// PLY
0x7a: { name: 'PLY', op: this.ply, modeFn: this.implied, mode: 'implied' },
// RMB/SMB
0x07: { name: 'RMB0', op: this.rmb, modeFn: 0, mode: 'zeroPage' },
0x17: { name: 'RMB1', op: this.rmb, modeFn: 1, mode: 'zeroPage' },
0x27: { name: 'RMB2', op: this.rmb, modeFn: 2, mode: 'zeroPage' },
0x37: { name: 'RMB3', op: this.rmb, modeFn: 3, mode: 'zeroPage' },
0x47: { name: 'RMB4', op: this.rmb, modeFn: 4, mode: 'zeroPage' },
0x57: { name: 'RMB5', op: this.rmb, modeFn: 5, mode: 'zeroPage' },
0x67: { name: 'RMB6', op: this.rmb, modeFn: 6, mode: 'zeroPage' },
0x77: { name: 'RMB7', op: this.rmb, modeFn: 7, mode: 'zeroPage' },
0x87: { name: 'SMB0', op: this.smb, modeFn: 0, mode: 'zeroPage' },
0x97: { name: 'SMB1', op: this.smb, modeFn: 1, mode: 'zeroPage' },
0xa7: { name: 'SMB2', op: this.smb, modeFn: 2, mode: 'zeroPage' },
0xb7: { name: 'SMB3', op: this.smb, modeFn: 3, mode: 'zeroPage' },
0xc7: { name: 'SMB4', op: this.smb, modeFn: 4, mode: 'zeroPage' },
0xd7: { name: 'SMB5', op: this.smb, modeFn: 5, mode: 'zeroPage' },
0xe7: { name: 'SMB6', op: this.smb, modeFn: 6, mode: 'zeroPage' },
0xf7: { name: 'SMB7', op: this.smb, modeFn: 7, mode: 'zeroPage' },
// STZ
0x64: {
name: 'STZ',
op: this.stz,
modeFn: this.writeZeroPage,
mode: 'zeroPage',
},
0x74: {
name: 'STZ',
op: this.stz,
modeFn: this.writeZeroPageX,
mode: 'zeroPageX',
},
0x9c: {
name: 'STZ',
op: this.stz,
modeFn: this.writeAbsolute,
mode: 'absolute',
},
0x9e: {
name: 'STZ',
op: this.stz,
modeFn: this.writeAbsoluteX,
mode: 'absoluteX',
},
// TRB
0x14: {
name: 'TRB',
op: this.trb,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x1c: {
name: 'TRB',
op: this.trb,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
// TSB
0x04: {
name: 'TSB',
op: this.tsb,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x0c: {
name: 'TSB',
op: this.tsb,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
};
OPS_NMOS_6502: Instructions = {
// ASO
0x0f: {
name: 'ASO',
op: this.aso,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x1f: {
name: 'ASO',
op: this.aso,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0x1b: {
name: 'ASO',
op: this.aso,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
0x07: {
name: 'ASO',
op: this.aso,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x17: {
name: 'ASO',
op: this.aso,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x03: {
name: 'ASO',
op: this.aso,
modeFn: this.readAddrZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x13: {
name: 'ASO',
op: this.aso,
modeFn: this.readAddrZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// RLA
0x2f: {
name: 'RLA',
op: this.rla,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x3f: {
name: 'RLA',
op: this.rla,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0x3b: {
name: 'RLA',
op: this.rla,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
0x27: {
name: 'RLA',
op: this.rla,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x37: {
name: 'RLA',
op: this.rla,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x23: {
name: 'RLA',
op: this.rla,
modeFn: this.readAddrZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x33: {
name: 'RLA',
op: this.rla,
modeFn: this.readAddrZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// LSE
0x4f: {
name: 'LSE',
op: this.lse,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x5f: {
name: 'LSE',
op: this.lse,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0x5b: {
name: 'LSE',
op: this.lse,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
0x47: {
name: 'LSE',
op: this.lse,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x57: {
name: 'LSE',
op: this.lse,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x43: {
name: 'LSE',
op: this.lse,
modeFn: this.readAddrZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x53: {
name: 'LSE',
op: this.lse,
modeFn: this.readAddrZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// RRA
0x6f: {
name: 'RRA',
op: this.rra,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x7f: {
name: 'RRA',
op: this.rra,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0x7b: {
name: 'RRA',
op: this.rra,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
0x67: {
name: 'RRA',
op: this.rra,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0x77: {
name: 'RRA',
op: this.rra,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0x63: {
name: 'RRA',
op: this.rra,
modeFn: this.readAddrZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0x73: {
name: 'RRA',
op: this.rra,
modeFn: this.readAddrZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// AXS
0x8f: {
name: 'AXS',
op: this.axs,
modeFn: this.writeAbsolute,
mode: 'absolute',
},
0x87: {
name: 'AXS',
op: this.axs,
modeFn: this.writeZeroPage,
mode: 'zeroPage',
},
0x97: {
name: 'AXS',
op: this.axs,
modeFn: this.writeZeroPageY,
mode: 'zeroPageY',
},
0x83: {
name: 'AXS',
op: this.axs,
modeFn: this.writeZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
// LAX
0xaf: {
name: 'LAX',
op: this.lax,
modeFn: this.readAbsolute,
mode: 'absolute',
},
0xbf: {
name: 'LAX',
op: this.lax,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
0xa7: {
name: 'LAX',
op: this.lax,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0xb7: {
name: 'LAX',
op: this.lax,
modeFn: this.readZeroPageY,
mode: 'zeroPageY',
},
0xa3: {
name: 'LAX',
op: this.lax,
modeFn: this.readZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0xb3: {
name: 'LAX',
op: this.lax,
modeFn: this.readZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// DCM
0xcf: {
name: 'DCM',
op: this.dcm,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0xdf: {
name: 'DCM',
op: this.dcm,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0xdb: {
name: 'DCM',
op: this.dcm,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
0xc7: {
name: 'DCM',
op: this.dcm,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0xd7: {
name: 'DCM',
op: this.dcm,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0xc3: {
name: 'DCM',
op: this.dcm,
modeFn: this.readAddrZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0xd3: {
name: 'DCM',
op: this.dcm,
modeFn: this.readAddrZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// INS
0xef: {
name: 'INS',
op: this.ins,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0xff: {
name: 'INS',
op: this.ins,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0xfb: {
name: 'INS',
op: this.ins,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
0xe7: {
name: 'INS',
op: this.ins,
modeFn: this.readAddrZeroPage,
mode: 'zeroPage',
},
0xf7: {
name: 'INS',
op: this.ins,
modeFn: this.readAddrZeroPageX,
mode: 'zeroPageX',
},
0xe3: {
name: 'INS',
op: this.ins,
modeFn: this.readAddrZeroPageXIndirect,
mode: 'zeroPageXIndirect',
},
0xf3: {
name: 'INS',
op: this.ins,
modeFn: this.readAddrZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// ALR
0x4b: {
name: 'ALR',
op: this.alr,
modeFn: this.readImmediate,
mode: 'immediate',
},
// ARR
0x6b: {
name: 'ARR',
op: this.arr,
modeFn: this.readImmediate,
mode: 'immediate',
},
// XAA
0x8b: {
name: 'XAA',
op: this.xaa,
modeFn: this.readImmediate,
mode: 'immediate',
},
// OAL
0xab: {
name: 'OAL',
op: this.oal,
modeFn: this.readImmediate,
mode: 'immediate',
},
// SAX
0xcb: {
name: 'SAX',
op: this.sax,
modeFn: this.readImmediate,
mode: 'immediate',
},
// NOP
0x1a: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
0x3a: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
0x5a: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
0x7a: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
0xda: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
0xfa: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
// SKB
0x80: {
name: 'SKB',
op: this.skp,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x82: {
name: 'SKB',
op: this.skp,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x89: {
name: 'SKB',
op: this.skp,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xc2: {
name: 'SKB',
op: this.skp,
modeFn: this.readImmediate,
mode: 'immediate',
},
0xe2: {
name: 'SKB',
op: this.skp,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x04: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x14: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x34: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x44: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x54: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0x64: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPage,
mode: 'zeroPage',
},
0x74: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0xd4: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
0xf4: {
name: 'SKB',
op: this.skp,
modeFn: this.readZeroPageX,
mode: 'zeroPageX',
},
// SKW
0x0c: {
name: 'SKW',
op: this.skp,
modeFn: this.readAddrAbsolute,
mode: 'absolute',
},
0x1c: {
name: 'SKW',
op: this.skp,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0x3c: {
name: 'SKW',
op: this.skp,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0x5c: {
name: 'SKW',
op: this.skp,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0x7c: {
name: 'SKW',
op: this.skp,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0xdc: {
name: 'SKW',
op: this.skp,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
0xfc: {
name: 'SKW',
op: this.skp,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// HLT
0x02: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x12: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x22: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x32: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x42: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x52: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x62: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x72: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0x92: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0xb2: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0xd2: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
0xf2: {
name: 'HLT',
op: this.hlt,
modeFn: this.readNopImplied,
mode: 'implied',
},
// TAS
0x9b: {
name: 'TAS',
op: this.tas,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
// SAY
0x9c: {
name: 'SAY',
op: this.say,
modeFn: this.readAddrAbsoluteX,
mode: 'absoluteX',
},
// XAS
0x9e: {
name: 'XAS',
op: this.xas,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
// AXA
0x9f: {
name: 'AXA',
op: this.axa,
modeFn: this.readAddrAbsoluteY,
mode: 'absoluteY',
},
0x93: {
name: 'AXA',
op: this.axa,
modeFn: this.readAddrZeroPageIndirectY,
mode: 'zeroPageIndirectY',
},
// ANC
0x2b: {
name: 'ANC',
op: this.anc,
modeFn: this.readImmediate,
mode: 'immediate',
},
0x0b: {
name: 'ANC',
op: this.anc,
modeFn: this.readImmediate,
mode: 'immediate',
},
// LAS
0xbb: {
name: 'LAS',
op: this.las,
modeFn: this.readAbsoluteY,
mode: 'absoluteY',
},
// SBC
0xeb: {
name: 'SBC',
op: this.sbc,
modeFn: this.readImmediate,
mode: 'immediate',
},
};
OPS_ROCKWELL_65C02: Instructions = {
0xcb: { name: 'NOP', op: this.nop, modeFn: this.implied, mode: 'implied' },
0xdb: {
name: 'NOP',
op: this.nop,
modeFn: this.readZeroPageX,
mode: 'immediate',
},
};
/* WDC 65C02 Instructions */
OPS_WDC_65C02: Instructions = {
0xcb: { name: 'WAI', op: this.wai, modeFn: this.implied, mode: 'implied' },
0xdb: { name: 'STP', op: this.stp, modeFn: this.implied, mode: 'implied' },
};
}