apple2js/js/cpu6502.ts
Ian Flanigan b80436d99c
More typescript conversion (#46)
* Convert js/ram to a class

* Convert js/mmu to Typescript

* Convert js/apple2io to Typescript

* Convert js/canvas to Typescript

* Use new types in js/mmu

* Rename js/symbols.js to js/symbols.ts

* Remove the difference between readPages and writePages

As @whscullin said in PR #38, there's no need to have both readable
and writable pages since all implementations are currently both. This
change combines them into `Page`. Likewise, `PageHandler` now extends
`Page`.

`Apple2IO` now implements `PageHandler`. This caught a bug where `end`
had been renamed `endend` by mistake.

There are a few other formatting changes as well.

* Convert js/apple2 to Typescript

* Convert js/prefs to Typescript

* Convert all of the ROMs in js/roms to Typescript

Now all of the ROMs are classes that extend the ROM class. There is
some rudamentary checking to make sure that the length of the ROM
matches the declared start and end pages. (This caught what looks to
be an error in roms/apple2e, but it's hard for me to tell.)

The typing also caught an error where the character ROM was being
used for the main ROM for the apple2j version.

* Convert js/roms/cards/* to Typescript

* Convert js/formats/format_utils to Typescript

This change also seems to fix a bug with `.po` image files that
weren't being read correctly.
2020-11-24 08:48:14 -08:00

1701 lines
56 KiB
TypeScript

/*
* 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 { byte, word } from './types';
import { debug, toHex } from './util';
type symbols = { [key: number]: string };
export interface CpuOptions {
'65C02'?: boolean;
}
export interface CpuState {
a: byte,
x: byte,
y: byte,
s: byte,
pc: word,
sp: byte,
cycles: number
}
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
type Modes = Record<Mode, number>;
/** Addressing mode name to instruction size mapping. */
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. */
type flag = 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01;
/** Flags to status byte mask. */
const flags: { [key: string]: flag } = {
N: 0x80, // Negative
V: 0x40, // oVerflow
B: 0x10, // Break
D: 0x08, // Decimal
I: 0x04, // Interrupt
Z: 0x02, // Zero
C: 0x01 // Carry
};
/** CPU-referenced memory locations. */
const loc = {
STACK: 0x100,
NMI: 0xFFFA,
RESET: 0xFFFC,
BRK: 0xFFFE
};
export interface Page {
read(page: byte, offset: byte): byte;
write(page: byte, offset: byte, value: byte): void;
}
export interface PageHandler extends Page {
start(): byte;
end(): byte;
}
interface ResettablePageHandler extends PageHandler {
reset(): void;
}
function isResettablePageHandler(pageHandler: PageHandler | ResettablePageHandler): pageHandler is ResettablePageHandler {
return (pageHandler as ResettablePageHandler).reset !== undefined;
}
const BLANK_PAGE: Page = {
read: function () { return 0; },
write: function () { }
};
interface Opts {
rwm?: boolean;
}
type ReadFn = () => byte;
type WriteFn = (val: byte) => void;
type ReadAddrFn = (opts?: Opts) => word;
type ImpliedFn = () => void
interface Instruction<T = any> {
name: string
mode: Mode
op: (fn: T) => void
modeFn: T
}
type StrictInstruction =
Instruction<ReadFn> |
Instruction<WriteFn> |
Instruction<ReadAddrFn> |
Instruction<ImpliedFn> |
Instruction<flag> |
Instruction<byte>
type Instructions = Record<byte, StrictInstruction>
type callback = (cpu: CPU6502) => void;
export default class CPU6502 {
private readonly is65C02;
/* Registers */
private pc = 0; // Program Counter
private sr = 0x20; // Process Status Register
private ar = 0; // Accumulator
private xr = 0; // X Register
private yr = 0; // Y Register
private sp = 0xff; // Stack Pointer
private memPages: Page[] = new Array(0x100);
private resetHandlers: ResettablePageHandler[] = [];
private cycles = 0;
private sync = false;
private readonly opary: Instruction[];
constructor(options: CpuOptions = {}) {
this.is65C02 = options['65C02'] ? true : false;
this.memPages.fill(BLANK_PAGE);
this.memPages.fill(BLANK_PAGE);
// Create this CPU's instruction table
let ops = { ...this.OPS_6502 };
if (this.is65C02) {
ops = { ...ops, ...this.OPS_65C02 };
}
// Certain browsers benefit from using arrays over maps
const opary: Instruction[] = new Array(0x100);
for (let idx = 0; idx < 0x100; idx++) {
opary[idx] = ops[idx] || this.unknown(idx);
}
this.opary = opary;
}
/**
* Set or clears `f` in the status register. `f` must be a byte with a
* single bit set.
*/
private setFlag(f: byte, on: boolean) {
this.sr = on ? (this.sr | f) : (this.sr & ~f);
}
/** Updates the status register's zero flag and negative flag. */
private testNZ(val: byte) {
this.sr = val === 0 ? (this.sr | flags.Z) : (this.sr & ~flags.Z);
this.sr = (val & 0x80) ? (this.sr | flags.N) : (this.sr & ~flags.N);
return val;
}
/** Updates the status register's zero flag. */
private testZ(val: byte) {
this.sr = val === 0 ? (this.sr | flags.Z) : (this.sr & ~flags.Z);
return val;
}
/**
* Returns `a + b`, unless `sub` is true, in which case it performs
* `a - b`. The status register is updated according to the result.
*/
private add(a: byte, b: byte, sub: boolean) {
// KEGS
let c, v;
if ((this.sr & flags.D) !== 0) {
// BCD
c = (a & 0x0f) + (b & 0x0f) + (this.sr & flags.C);
if (sub) {
if (c < 0x10)
c = (c - 0x06) & 0x0f;
c += (a & 0xf0) + (b & 0xf0);
v = (c >> 1) ^ c;
if (c < 0x100)
c = (c + 0xa0) & 0xff;
} else {
if (c > 0x09)
c = (c - 0x0a) | 0x10; // carry to MSN
c += (a & 0xf0) + (b & 0xf0);
v = (c >> 1) ^ c;
if (c > 0x99)
c += 0x60;
}
} else {
c = a + b + (this.sr & flags.C);
v = (c ^ a) & 0x80;
}
if (((a ^ b) & 0x80) !== 0) {
v = 0;
}
this.setFlag(flags.C, c > 0xff);
this.setFlag(flags.V, !!v);
return this.testNZ(c & 0xff);
}
/** Increments `a` and returns the value, setting the status register. */
private increment(a: byte) {
return this.testNZ((a + 0x01) & 0xff);
}
private decrement(a: byte) {
return this.testNZ((a + 0xff) & 0xff);
}
private readBytePC(): byte {
const addr = this.pc,
page = addr >> 8,
off = addr & 0xff;
const result = this.memPages[page].read(page, off);
this.pc = (this.pc + 1) & 0xffff;
this.cycles++;
return result;
}
private readByte(addr: word): byte {
const page = addr >> 8,
off = addr & 0xff;
const result = this.memPages[page].read(page, off);
this.cycles++;
return result;
}
private readByteDebug(addr: word) {
const page = addr >> 8,
off = addr & 0xff;
return this.memPages[page].read(page, off);
}
private writeByte(addr: word, val: byte) {
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 readWordDebug(addr: word): word {
return this.readByteDebug(addr) | (this.readByteDebug(addr + 1) << 8);
}
private readWordPC(): word {
return this.readBytePC() | (this.readBytePC() << 8);
}
private readZPWord(addr: byte): word {
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;
}
/*
* Implied function
*/
implied() {
}
/*
* 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 => {
let addr = this.readWordPC();
const oldPage = addr >> 8;
addr = (addr + this.xr) & 0xffff;
const newPage = addr >> 8;
if (newPage != oldPage) {
const off = addr & 0xff;
this.readByte(oldPage << 8 | off);
}
return this.readByte(addr);
}
// $0000,Y
readAbsoluteY = (): byte => {
let addr = this.readWordPC();
const oldPage = addr >> 8;
addr = (addr + this.yr) & 0xffff;
const newPage = addr >> 8;
if (newPage != oldPage) {
const off = addr & 0xff;
this.readByte(oldPage << 8 | off);
}
return this.readByte(addr);
}
// $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 => {
let addr = this.readZPWord(this.readBytePC());
const oldPage = addr >> 8;
addr = (addr + this.yr) & 0xffff;
const newPage = addr >> 8;
if (newPage != oldPage) {
const off = addr & 0xff;
this.readByte(oldPage << 8 | off);
}
return this.readByte(addr);
}
// ($00) (65C02)
readZeroPageIndirect = (): byte => {
return this.readByte(this.readZPWord(this.readBytePC()));
}
/*
* Write Functions
*/
// $0000
writeAbsolute = (val: byte) => {
this.writeByte(this.readWordPC(), val);
}
// $00
writeZeroPage = (val: byte) => {
this.writeByte(this.readBytePC(), val);
}
// $0000,X
writeAbsoluteX = (val: byte) => {
let addr = this.readWordPC();
const oldPage = addr >> 8;
addr = (addr + this.xr) & 0xffff;
const off = addr & 0xff;
this.readByte(oldPage << 8 | off);
this.writeByte(addr, val);
}
// $0000,Y
writeAbsoluteY = (val: byte) => {
let addr = this.readWordPC();
const oldPage = addr >> 8;
addr = (addr + this.yr) & 0xffff;
const off = addr & 0xff;
this.readByte(oldPage << 8 | off);
this.writeByte(addr, 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) => {
let addr = this.readZPWord(this.readBytePC());
const oldPage = addr >> 8;
addr = (addr + this.yr) & 0xffff;
const off = addr & 0xff;
this.readByte(oldPage << 8 | off);
this.writeByte(addr, val);
}
// ($00) (65C02)
writeZeroPageIndirect = (val: byte) => {
this.writeByte(this.readZPWord(this.readBytePC()), val);
}
// $00
readAddrZeroPage = () => {
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 lsb = this.readBytePC();
const msb = this.readBytePC();
this.readByte(this.pc);
return this.readWord(msb << 8 | lsb);
}
// $0000,X
readAddrAbsoluteX = (opts: Opts = {}): word => {
const addr = this.readWordPC();
if (!this.is65C02 || opts.rwm) {
this.readByte(addr);
} else {
this.readByte(this.pc);
}
return (addr + this.xr) & 0xffff;
}
// $(0000,X) (65C02)
readAddrAbsoluteXIndirect = (): word => {
const address = this.readWordPC();
this.readByte(this.pc);
return this.readWord((address + this.xr) & 0xffff);
}
/* Break */
brk = (readFn: ReadFn) => {
readFn();
this.pushWord(this.pc);
this.pushByte(this.sr | flags.B);
if (this.is65C02) {
this.setFlag(flags.D, false);
}
this.setFlag(flags.I, true);
this.pc = this.readWord(loc.BRK);
}
/* Load Accumulator */
lda = (readFn: ReadFn) => {
this.ar = this.testNZ(readFn());
}
/* Load X Register */
ldx = (readFn: ReadFn) => {
this.xr = this.testNZ(readFn());
}
/* Load Y Register */
ldy = (readFn: ReadFn) => {
this.yr = this.testNZ(readFn());
}
/* Store Accumulator */
sta = (writeFn: WriteFn) => {
writeFn(this.ar);
}
/* Store X Register */
stx = (writeFn: WriteFn) => {
writeFn(this.xr);
}
/* Store Y Register */
sty = (writeFn: WriteFn) => {
writeFn(this.yr);
}
/* Store Zero */
stz = (writeFn: WriteFn) => {
writeFn(0);
}
/* Add with Carry */
adc = (readFn: ReadFn) => {
this.ar = this.add(this.ar, readFn(), /* sub= */ false);
}
/* Subtract with Carry */
sbc = (readFn: ReadFn) => {
this.ar = this.add(this.ar, readFn() ^ 0xff, /* sub= */ true);
}
/* Increment Memory */
incA = () => {
this.readByte(this.pc);
this.ar = this.increment(this.ar);
}
inc = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn({ rwm: true });
const oldVal = this.readByte(addr);
this.writeByte(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({ rwm: true });
const oldVal = this.readByte(addr);
this.writeByte(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({ rwm: true });
const oldVal = this.readByte(addr);
this.writeByte(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({ rwm: true });
const oldVal = this.readByte(addr);
this.writeByte(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({ rwm: true });
const oldVal = this.readByte(addr);
this.writeByte(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({ rwm: true });
const oldVal = this.readByte(addr);
this.writeByte(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 >> 8;
this.pc += off > 127 ? off - 256 : off;
const newPage = this.pc >> 8;
const newOff = this.pc & 0xff;
if (newPage != oldPage) this.readByte(oldPage << 8 | newOff);
}
}
brc = (f: flag) => {
const off = this.readBytePC(); // changes pc
if ((f & this.sr) === 0) {
this.readByte(this.pc);
const oldPage = this.pc >> 8;
this.pc += off > 127 ? off - 256 : off;
const newPage = this.pc >> 8;
const newOff = this.pc & 0xff;
if (newPage != oldPage) this.readByte(oldPage << 8 | newOff);
}
}
/* WDC 65C02 branches */
bbr = (b: byte) => {
const zpAddr = this.readBytePC();
const val = this.readByte(zpAddr);
this.readByte(zpAddr);
const off = this.readBytePC(); // changes pc
if (((1 << b) & val) === 0) {
const oldPc = this.pc;
const oldPage = oldPc >> 8;
this.readByte(oldPc);
this.pc += off > 127 ? off - 256 : off;
const newPage = this.pc >> 8;
if (oldPage != newPage) {
this.readByte(oldPc);
}
}
}
bbs = (b: byte) => {
const zpAddr = this.readBytePC();
const val = this.readByte(zpAddr);
this.readByte(zpAddr);
const off = this.readBytePC(); // changes pc
if (((1 << b) & val) !== 0) {
const oldPc = this.pc;
const oldPage = oldPc >> 8;
this.readByte(oldPc);
this.pc += off > 127 ? off - 256 : off;
const newPage = this.pc >> 8;
if (oldPage != newPage) {
this.readByte(oldPc);
}
}
}
/* Transfers and stack */
tax = () => { this.readByte(this.pc); this.testNZ(this.xr = this.ar); }
txa = () => { this.readByte(this.pc); this.testNZ(this.ar = this.xr); }
tay = () => { this.readByte(this.pc); this.testNZ(this.yr = this.ar); }
tya = () => { this.readByte(this.pc); this.testNZ(this.ar = this.yr); }
tsx = () => { this.readByte(this.pc); this.testNZ(this.xr = this.sp); }
txs = () => { this.readByte(this.pc); this.sp = this.xr; }
pha = () => { this.readByte(this.pc); this.pushByte(this.ar); }
pla = () => { this.readByte(this.pc); this.readByte(0x0100 | this.sp); this.testNZ(this.ar = this.pullByte()); }
phx = () => { this.readByte(this.pc); this.pushByte(this.xr); }
plx = () => { this.readByte(this.pc); this.readByte(0x0100 | this.sp); this.testNZ(this.xr = this.pullByte()); }
phy = () => { this.readByte(this.pc); this.pushByte(this.yr); }
ply = () => { this.readByte(this.pc); this.readByte(0x0100 | this.sp); this.testNZ(this.yr = this.pullByte()); }
php = () => { this.readByte(this.pc); this.pushByte(this.sr | flags.B); }
plp = () => { this.readByte(this.pc); this.readByte(0x0100 | this.sp); this.sr = (this.pullByte() & ~flags.B) | 0x20; }
/* Jump */
jmp = (readAddrFn: ReadAddrFn) => {
this.pc = readAddrFn();
}
/* Jump Subroutine */
jsr = () => {
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;
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 = (impliedFn: ImpliedFn) => {
this.readByte(this.pc);
impliedFn();
}
private unknown(b: byte) {
let unk: StrictInstruction;
if (this.is65C02) {
unk = {
name: 'NOP',
op: this.nop,
modeFn: this.implied,
mode: 'implied',
};
} else {
const cpu = this;
unk = {
name: '???',
op: function (_impliedFn: ImpliedFn) {
debug('Unknown OpCode: ' + toHex(b) +
' at ' + toHex(cpu.pc - 1, 4));
},
modeFn: this.implied,
mode: 'implied'
};
}
return unk;
}
private dumpArgs(addr: word, m: Mode, symbols: symbols) {
function toHexOrSymbol(v: word, n?: number) {
if (symbols && symbols[v]) {
return symbols[v];
} else {
return '$' + toHex(v, n);
}
}
let off, val;
let result = '';
switch (m) {
case 'implied':
break;
case 'immediate':
result = '#' + toHexOrSymbol(this.readByteDebug(addr));
break;
case 'absolute':
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4);
break;
case 'zeroPage':
result = '' + toHexOrSymbol(this.readByteDebug(addr));
break;
case 'relative':
{
let off = this.readByteDebug(addr);
if (off > 127) {
off -= 256;
}
addr += off + 1;
result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
}
break;
case 'absoluteX':
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X';
break;
case 'absoluteY':
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',Y';
break;
case 'zeroPageX':
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',X';
break;
case 'zeroPageY':
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',Y';
break;
case 'absoluteIndirect':
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ')';
break;
case 'zeroPageXIndirect':
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ',X)';
break;
case 'zeroPageIndirectY':
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + '),Y';
break;
case 'accumulator':
result = 'A';
break;
case 'zeroPageIndirect':
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ')';
break;
case 'absoluteXIndirect':
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X)';
break;
case 'zeroPage_relative':
val = this.readByteDebug(addr);
off = this.readByteDebug(addr + 1);
if (off > 127) {
off -= 256;
}
addr += off + 2;
result = '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
break;
default:
break;
}
return result;
}
public step(cb: callback) {
this.sync = true;
const op = this.opary[this.readBytePC()];
this.sync = false;
op.op(op.modeFn);
if (cb) {
cb(this);
}
}
public stepDebug(n: number, cb: callback) {
for (let idx = 0; idx < n; idx++) {
this.sync = true;
const op = this.opary[this.readBytePC()];
this.sync = false;
op.op(op.modeFn);
if (cb) {
cb(this);
}
}
}
public stepCycles(c: number) {
const end = this.cycles + c;
while (this.cycles < end) {
this.sync = true;
const op = this.opary[this.readBytePC()];
this.sync = false;
op.op(op.modeFn);
}
}
public stepCyclesDebug(c: number, cb: callback): void {
const end = this.cycles + c;
while (this.cycles < end) {
this.sync = true;
const op = this.opary[this.readBytePC()];
this.sync = false;
op.op(op.modeFn);
if (cb) {
cb(this);
}
}
}
public addPageHandler(pho: (PageHandler | ResettablePageHandler) & Page) {
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 = 0x20;
this.sp = 0xff;
this.ar = 0;
this.yr = 0;
this.xr = 0;
this.pc = this.readWord(loc.RESET);
for (let idx = 0; idx < this.resetHandlers.length; idx++) {
this.resetHandlers[idx].reset();
}
}
/* IRQ - Interrupt Request */
public irq() {
if ((this.sr & flags.I) === 0) {
this.pushWord(this.pc);
this.pushByte(this.sr & ~flags.B);
if (this.is65C02) {
this.setFlag(flags.D, false);
}
this.setFlag(flags.I, true);
this.pc = this.readWord(loc.BRK);
}
}
/* NMI Non-maskable Interrupt */
public nmi() {
this.pushWord(this.pc);
this.pushByte(this.sr & ~flags.B);
if (this.is65C02) {
this.setFlag(flags.D, false);
}
this.setFlag(flags.I, true);
this.pc = this.readWord(loc.NMI);
}
public getPC() {
return this.pc;
}
public setPC(pc: word) {
this.pc = pc;
}
public dumpPC(pc: word | undefined, symbols: symbols) {
if (pc === undefined) {
pc = this.pc;
}
const b = this.readByte(pc),
op = this.opary[b],
size = sizes[op.mode];
let result = toHex(pc, 4) + '- ';
if (symbols) {
if (symbols[pc]) {
result += symbols[pc] +
' '.substring(symbols[pc].length);
} else {
result += ' ';
}
}
for (let idx = 0; idx < 4; idx++) {
if (idx < size) {
result += toHex(this.readByte(pc + idx)) + ' ';
} else {
result += ' ';
}
}
if (op === undefined)
result += '??? (' + toHex(b) + ')';
else
result += op.name + ' ' + this.dumpArgs(pc + 1, op.mode, symbols);
return result;
}
public dumpPage(start?: word, end?: word) {
let result = '';
if (start === undefined) {
start = this.pc >> 8;
}
if (end === undefined) {
end = start;
}
for (let page = start; page <= end; page++) {
for (let idx = 0; idx < 16; idx++) {
result += toHex(page) + toHex(idx << 4) + ': ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.readByteDebug(page * 256 + idx * 16 + jdx);
result += toHex(b) + ' ';
}
result += ' ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.readByte(page * 256 + idx * 16 + jdx) & 0x7f;
if (b >= 0x20 && b < 0x7f) {
result += String.fromCharCode(b);
} else {
result += '.';
}
}
result += '\n';
}
}
return result;
}
public list(_pc: word, symbols: symbols) {
if (_pc === undefined) {
_pc = this.pc;
}
const results = [];
for (let jdx = 0; jdx < 20; jdx++) {
const b = this.readByte(_pc), op = this.opary[b];
results.push(this.dumpPC(_pc, symbols));
_pc += sizes[op.mode];
}
return results;
}
public getSync() {
return this.sync;
}
public getCycles() {
return this.cycles;
}
public registers() {
return [this.pc, this.ar, this.xr, this.yr, this.sr, this.sp];
}
public getState(): CpuState {
return {
a: this.ar,
x: this.xr,
y: this.yr,
s: this.sr,
pc: this.pc,
sp: this.sp,
cycles: this.cycles
};
}
public setState(state: CpuState) {
this.ar = state.a;
this.xr = state.x;
this.yr = state.y;
this.sr = state.s;
this.pc = state.pc;
this.sp = state.sp;
this.cycles = state.cycles;
}
public dumpRegisters() {
return toHex(this.pc, 4) +
'- A=' + toHex(this.ar) +
' X=' + toHex(this.xr) +
' Y=' + toHex(this.yr) +
' P=' + toHex(this.sr) +
' S=' + toHex(this.sp) +
' ' +
((this.sr & flags.N) ? 'N' : '-') +
((this.sr & flags.V) ? 'V' : '-') +
'-' +
((this.sr & flags.B) ? 'B' : '-') +
((this.sr & flags.D) ? 'D' : '-') +
((this.sr & flags.I) ? 'I' : '-') +
((this.sr & flags.Z) ? 'Z' : '-') +
((this.sr & flags.C) ? 'C' : '-');
}
public read(page: byte, off: byte): byte {
return this.memPages[page].read(page, off);
}
public write(page: byte, off: byte, val: byte) {
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.readImmediate, mode: 'immediate' },
0x54: { name: 'NOP', op: this.nop, modeFn: this.readImmediate, 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.readImmediate, mode: 'immediate' },
0xE2: { name: 'NOP', op: this.nop, modeFn: this.readImmediate, mode: 'immediate' },
0xF4: { name: 'NOP', op: this.nop, modeFn: this.readImmediate, mode: 'immediate' },
0x5C: { name: 'NOP', op: this.nop, modeFn: this.readAbsolute, mode: 'absolute' },
0xDC: { name: 'NOP', op: this.nop, modeFn: this.readAbsolute, mode: 'absolute' },
0xFC: { name: 'NOP', op: this.nop, modeFn: this.readAbsolute, 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' }
}
}