mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
04ae0327c2
This adds both the recommended TypeScript checks, plus the recommended TypeScript checks that require type checking. This latter addition means that eslint essentially has to compile all of the TypeScript in the project, causing it to be slower. This isn't much of a problem in VS Code because there's a lot of caching being done, but it's clearly slower when run on the commandline. All of the errors are either fixed or suppressed. Some errors are suppressed because fixing them would be too laborious for the little value gained. The eslint config is also slightly refactored to separate the strictly TypeScript checks from the JavaScript checks.
2115 lines
72 KiB
TypeScript
2115 lines
72 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;
|
|
off = b;
|
|
} else {
|
|
page = a >> 8;
|
|
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;
|
|
off = b;
|
|
val = c;
|
|
} else {
|
|
page = a >> 8;
|
|
off = a & 0xff;
|
|
val = b;
|
|
}
|
|
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' }
|
|
};
|
|
}
|