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