mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
No Opcode left behind (#92)
* Better 65C02 support * NMOS illegal opcodes
This commit is contained in:
parent
70ec626dd0
commit
c24c01539d
10
js/apple2.ts
10
js/apple2.ts
|
@ -17,7 +17,11 @@ import {
|
||||||
} from './gl';
|
} from './gl';
|
||||||
import ROM from './roms/rom';
|
import ROM from './roms/rom';
|
||||||
import { Apple2IOState } from './apple2io';
|
import { Apple2IOState } from './apple2io';
|
||||||
import CPU6502, { CpuState } from './cpu6502';
|
import CPU6502, {
|
||||||
|
CpuState,
|
||||||
|
FLAVOR_6502,
|
||||||
|
FLAVOR_ROCKWELL_65C02,
|
||||||
|
} from './cpu6502';
|
||||||
import MMU, { MMUState } from './mmu';
|
import MMU, { MMUState } from './mmu';
|
||||||
import RAM, { RAMState } from './ram';
|
import RAM, { RAMState } from './ram';
|
||||||
|
|
||||||
|
@ -92,7 +96,9 @@ export class Apple2 implements Restorable<State>, DebuggerContainer {
|
||||||
const HiresPage = options.gl ? HiresPageGL : HiresPage2D;
|
const HiresPage = options.gl ? HiresPageGL : HiresPage2D;
|
||||||
const VideoModes = options.gl ? VideoModesGL : VideoModes2D;
|
const VideoModes = options.gl ? VideoModesGL : VideoModes2D;
|
||||||
|
|
||||||
this.cpu = new CPU6502({ '65C02': options.enhanced });
|
this.cpu = new CPU6502({
|
||||||
|
flavor: options.enhanced ? FLAVOR_ROCKWELL_65C02 : FLAVOR_6502
|
||||||
|
});
|
||||||
this.vm = new VideoModes(options.canvas, options.e);
|
this.vm = new VideoModes(options.canvas, options.e);
|
||||||
|
|
||||||
const [{ default: Apple2ROM }, { default: characterRom }] = await Promise.all([
|
const [{ default: Apple2ROM }, { default: characterRom }] = await Promise.all([
|
||||||
|
|
509
js/cpu6502.ts
509
js/cpu6502.ts
|
@ -1,8 +1,24 @@
|
||||||
import { Memory, MemoryPages, byte, word } from './types';
|
import { Memory, MemberOf, MemoryPages, byte, word } from './types';
|
||||||
import { debug, toHex } from './util';
|
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 {
|
export interface CpuOptions {
|
||||||
'65C02'?: boolean;
|
flavor?: Flavor
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CpuState {
|
export interface CpuState {
|
||||||
|
@ -152,6 +168,8 @@ type Instructions = Record<byte, StrictInstruction>
|
||||||
type callback = (cpu: CPU6502) => boolean | void;
|
type callback = (cpu: CPU6502) => boolean | void;
|
||||||
|
|
||||||
export default class CPU6502 {
|
export default class CPU6502 {
|
||||||
|
/** flavor */
|
||||||
|
private readonly flavor: Flavor;
|
||||||
/** 65C02 emulation mode flag */
|
/** 65C02 emulation mode flag */
|
||||||
private readonly is65C02: boolean;
|
private readonly is65C02: boolean;
|
||||||
|
|
||||||
|
@ -186,11 +204,17 @@ export default class CPU6502 {
|
||||||
/** Command being fetched signal */
|
/** Command being fetched signal */
|
||||||
private sync = false;
|
private sync = false;
|
||||||
|
|
||||||
|
/** Processor is in WAI mode */
|
||||||
|
private wait = false;
|
||||||
|
/** Processor is in STP mode */
|
||||||
|
private stop = false;
|
||||||
|
|
||||||
/** Filled array of CPU operations */
|
/** Filled array of CPU operations */
|
||||||
private readonly opary: Instruction[];
|
private readonly opary: Instruction[];
|
||||||
|
|
||||||
constructor(options: CpuOptions = {}) {
|
constructor({ flavor }: CpuOptions = {}) {
|
||||||
this.is65C02 = options['65C02'] ? true : false;
|
this.flavor = flavor ?? FLAVOR_6502;
|
||||||
|
this.is65C02 = !!flavor && FLAVORS_65C02.includes(flavor);
|
||||||
|
|
||||||
this.memPages.fill(BLANK_PAGE);
|
this.memPages.fill(BLANK_PAGE);
|
||||||
this.memPages.fill(BLANK_PAGE);
|
this.memPages.fill(BLANK_PAGE);
|
||||||
|
@ -198,17 +222,35 @@ export default class CPU6502 {
|
||||||
// Create this CPU's instruction table
|
// Create this CPU's instruction table
|
||||||
|
|
||||||
let ops = { ...this.OPS_6502 };
|
let ops = { ...this.OPS_6502 };
|
||||||
if (this.is65C02) {
|
|
||||||
ops = { ...ops, ...this.OPS_65C02 };
|
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
|
// Certain browsers benefit from using arrays over maps
|
||||||
const opary: Instruction[] = new Array(0x100);
|
this.opary = new Array(0x100);
|
||||||
|
|
||||||
for (let idx = 0; idx < 0x100; idx++) {
|
for (let idx = 0; idx < 0x100; idx++) {
|
||||||
opary[idx] = ops[idx] || this.unknown(idx);
|
this.opary[idx] = ops[idx] || this.unknown(idx);
|
||||||
}
|
}
|
||||||
this.opary = opary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -262,7 +304,11 @@ export default class CPU6502 {
|
||||||
n = bin >> 7;
|
n = bin >> 7;
|
||||||
z = !bin;
|
z = !bin;
|
||||||
if (this.op.mode === 'immediate') {
|
if (this.op.mode === 'immediate') {
|
||||||
this.readByte(sub ? 0xB8 : 0x7F);
|
if (this.flavor === FLAVOR_WDC_65C02) {
|
||||||
|
this.readByte(sub ? 0xB8 : 0x7F);
|
||||||
|
} else { // rockwell65c02
|
||||||
|
this.readByte(sub ? 0xB1 : 0x59);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.readByte(this.addr);
|
this.readByte(this.addr);
|
||||||
}
|
}
|
||||||
|
@ -625,6 +671,34 @@ export default class CPU6502 {
|
||||||
return addr;
|
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)
|
// $(0000,X) (65C02)
|
||||||
readAddrAbsoluteXIndirect = (): word => {
|
readAddrAbsoluteXIndirect = (): word => {
|
||||||
const lsb = this.readBytePC();
|
const lsb = this.readBytePC();
|
||||||
|
@ -635,12 +709,17 @@ export default class CPU6502 {
|
||||||
return this.readWord(addr);
|
return this.readWord(addr);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 5C, DC, FC NOP
|
// 5C, DC, FC NOP (65C02)
|
||||||
readNop = (): void => {
|
readNop = (): void => {
|
||||||
this.readWordPC();
|
this.readWordPC();
|
||||||
this.readByte(this.addr);
|
this.readByte(this.addr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// NOP (65C02)
|
||||||
|
readNopImplied = (): void => {
|
||||||
|
// Op is 1 cycle
|
||||||
|
};
|
||||||
|
|
||||||
/* Break */
|
/* Break */
|
||||||
brk = (readFn: ReadFn) => {
|
brk = (readFn: ReadFn) => {
|
||||||
readFn();
|
readFn();
|
||||||
|
@ -653,6 +732,20 @@ export default class CPU6502 {
|
||||||
this.pc = this.readWord(loc.BRK);
|
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 */
|
/* Load Accumulator */
|
||||||
lda = (readFn: ReadFn) => {
|
lda = (readFn: ReadFn) => {
|
||||||
this.ar = this.testNZ(readFn());
|
this.ar = this.testNZ(readFn());
|
||||||
|
@ -1055,32 +1148,219 @@ export default class CPU6502 {
|
||||||
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) {
|
private unknown(b: byte) {
|
||||||
let unk: StrictInstruction;
|
let unk: StrictInstruction;
|
||||||
|
|
||||||
if (this.is65C02) {
|
if (this.is65C02) {
|
||||||
|
// Default behavior is a 1 cycle NOP
|
||||||
unk = {
|
unk = {
|
||||||
name: 'NOP',
|
name: 'NOP',
|
||||||
op: this.nop,
|
op: this.nop,
|
||||||
modeFn: this.implied,
|
modeFn: this.readNopImplied,
|
||||||
mode: 'implied',
|
mode: 'implied',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const cpu = this;
|
// All 6502 Instructions should be defined
|
||||||
unk = {
|
throw new Error(`Missing ${toHex(b)}`);
|
||||||
name: '???',
|
|
||||||
op: function (_impliedFn: ImpliedFn) {
|
|
||||||
debug('Unknown OpCode: ' + toHex(b) +
|
|
||||||
' at ' + toHex(cpu.pc - 1, 4));
|
|
||||||
},
|
|
||||||
modeFn: this.implied,
|
|
||||||
mode: 'implied'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return unk;
|
return unk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public step(cb?: callback) {
|
public step(cb?: callback) {
|
||||||
this.sync = true;
|
this.sync = true;
|
||||||
this.op = this.opary[this.readBytePC()];
|
this.op = this.opary[this.readBytePC()];
|
||||||
|
@ -1145,6 +1425,8 @@ export default class CPU6502 {
|
||||||
this.yr = 0;
|
this.yr = 0;
|
||||||
this.xr = 0;
|
this.xr = 0;
|
||||||
this.pc = this.readWord(loc.RESET);
|
this.pc = this.readWord(loc.RESET);
|
||||||
|
this.wait = false;
|
||||||
|
this.stop = false;
|
||||||
|
|
||||||
for (let idx = 0; idx < this.resetHandlers.length; idx++) {
|
for (let idx = 0; idx < this.resetHandlers.length; idx++) {
|
||||||
this.resetHandlers[idx].reset();
|
this.resetHandlers[idx].reset();
|
||||||
|
@ -1161,6 +1443,7 @@ export default class CPU6502 {
|
||||||
}
|
}
|
||||||
this.setFlag(flags.I, true);
|
this.setFlag(flags.I, true);
|
||||||
this.pc = this.readWord(loc.BRK);
|
this.pc = this.readWord(loc.BRK);
|
||||||
|
this.wait = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1173,6 +1456,7 @@ export default class CPU6502 {
|
||||||
}
|
}
|
||||||
this.setFlag(flags.I, true);
|
this.setFlag(flags.I, true);
|
||||||
this.pc = this.readWord(loc.NMI);
|
this.pc = this.readWord(loc.NMI);
|
||||||
|
this.wait = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPC() {
|
public getPC() {
|
||||||
|
@ -1207,6 +1491,14 @@ export default class CPU6502 {
|
||||||
return this.sync;
|
return this.sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getStop() {
|
||||||
|
return this.stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWait() {
|
||||||
|
return this.wait;
|
||||||
|
}
|
||||||
|
|
||||||
public getCycles() {
|
public getCycles() {
|
||||||
return this.cycles;
|
return this.cycles;
|
||||||
}
|
}
|
||||||
|
@ -1652,4 +1944,171 @@ export default class CPU6502 {
|
||||||
0x04: { name: 'TSB', op: this.tsb, modeFn: this.readAddrZeroPage, mode: 'zeroPage' },
|
0x04: { name: 'TSB', op: this.tsb, modeFn: this.readAddrZeroPage, mode: 'zeroPage' },
|
||||||
0x0C: { name: 'TSB', op: this.tsb, modeFn: this.readAddrAbsolute, mode: 'absolute' }
|
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' }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
import CPU6502 from 'js/cpu6502';
|
import CPU6502, { FLAVOR_ROCKWELL_65C02, FLAVOR_WDC_65C02 } from 'js/cpu6502';
|
||||||
import { toHex } from 'js/util';
|
import { toHex } from 'js/util';
|
||||||
import type { byte, word } from 'js/types';
|
import type { byte, word } from 'js/types';
|
||||||
|
|
||||||
|
@ -158,28 +158,56 @@ const maxTests = 16;
|
||||||
|
|
||||||
if (testPath) {
|
if (testPath) {
|
||||||
const testPath6502 = `${testPath}/6502/v1/`;
|
const testPath6502 = `${testPath}/6502/v1/`;
|
||||||
const testPath65C02 = `${testPath}/wdc65c02/v1/`;
|
const testPathWDC65C02 = `${testPath}/wdc65c02/v1/`;
|
||||||
|
const testPathRW65C02 = `${testPath}/rockwell65c02/v1/`;
|
||||||
|
|
||||||
const opAry6502: OpTest[] = [];
|
const opAry6502: OpTest[] = [];
|
||||||
const opAry65C02: OpTest[] = [];
|
const opAryRW65C02: OpTest[] = [];
|
||||||
|
const opAryWDC65C02: OpTest[] = [];
|
||||||
|
|
||||||
const buildOpArrays = () => {
|
const buildOpArrays = () => {
|
||||||
const cpu = new CPU6502();
|
const cpu = new CPU6502();
|
||||||
|
|
||||||
// Grab the implemented op codes
|
|
||||||
// TODO: Decide which undocumented opcodes are worthwhile.
|
|
||||||
for (const op in cpu.OPS_6502) {
|
for (const op in cpu.OPS_6502) {
|
||||||
const { name, mode } = cpu.OPS_6502[op];
|
const { name, mode } = cpu.OPS_6502[op];
|
||||||
const test = { op: toHex(+op), name, mode };
|
const test = { op: toHex(+op), name, mode };
|
||||||
opAry6502.push(test);
|
opAry6502.push(test);
|
||||||
opAry65C02.push(test);
|
opAryRW65C02.push(test);
|
||||||
|
opAryWDC65C02.push(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const op in cpu.OPS_NMOS_6502) {
|
||||||
|
const { name, mode } = cpu.OPS_NMOS_6502[op];
|
||||||
|
const test = { op: toHex(+op), name, mode };
|
||||||
|
opAry6502.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const op in cpu.OPS_65C02) {
|
for (const op in cpu.OPS_65C02) {
|
||||||
const { name, mode } = cpu.OPS_65C02[op];
|
const { name, mode } = cpu.OPS_65C02[op];
|
||||||
const test = { op: toHex(+op), name, mode };
|
const test = { op: toHex(+op), name, mode };
|
||||||
opAry65C02.push(test);
|
opAryRW65C02.push(test);
|
||||||
|
opAryWDC65C02.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WDC 65C02 NOPs
|
||||||
|
[
|
||||||
|
'03', '0b', '13', '1b', '23', '2b', '33', '3b',
|
||||||
|
'43', '4b', '53', '5b', '63', '6b', '73', '7b',
|
||||||
|
'83', '8b', '93', '9b', 'a3', 'ab', 'b3', 'bb',
|
||||||
|
'c3', 'd3', 'e3', 'eb', 'f3', 'fb'
|
||||||
|
].forEach((op) =>
|
||||||
|
opAryWDC65C02.push({ op, name: 'nop', mode: 'implied'})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rockwell 65C02 NOPs
|
||||||
|
[
|
||||||
|
'03', '0b', '13', '1b', '23', '2b', '33', '3b',
|
||||||
|
'43', '4b', '53', '5b', '63', '6b', '73', '7b',
|
||||||
|
'83', '8b', '93', '9b', 'a3', 'ab', 'b3', 'bb',
|
||||||
|
'c3', 'cb', 'd3', 'db', 'e3', 'eb', 'f3', 'fb'
|
||||||
|
].forEach((op) =>
|
||||||
|
opAryRW65C02.push({ op, name: 'nop', mode: 'implied'})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
buildOpArrays();
|
buildOpArrays();
|
||||||
|
@ -188,7 +216,7 @@ if (testPath) {
|
||||||
let cpu: CPU6502;
|
let cpu: CPU6502;
|
||||||
let memory: TestMemory;
|
let memory: TestMemory;
|
||||||
|
|
||||||
describe('6502', function() {
|
describe('NMOS 6502', function() {
|
||||||
beforeAll(function() {
|
beforeAll(function() {
|
||||||
cpu = new CPU6502();
|
cpu = new CPU6502();
|
||||||
memory = new TestMemory(256);
|
memory = new TestMemory(256);
|
||||||
|
@ -209,15 +237,36 @@ if (testPath) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('WDC 65C02', function() {
|
describe('Rockwell 65C02', function() {
|
||||||
beforeAll(function() {
|
beforeAll(function() {
|
||||||
cpu = new CPU6502({ '65C02': true });
|
cpu = new CPU6502({ flavor: FLAVOR_ROCKWELL_65C02 });
|
||||||
memory = new TestMemory(256);
|
memory = new TestMemory(256);
|
||||||
cpu.addPageHandler(memory);
|
cpu.addPageHandler(memory);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.each(opAry65C02)('Test op $op $name $mode', ({op}) => {
|
describe.each(opAryRW65C02)('Test op $op $name $mode', ({op}) => {
|
||||||
const data = fs.readFileSync(`${testPath65C02}${op}.json`, 'utf-8');
|
const data = fs.readFileSync(`${testPathRW65C02}${op}.json`, 'utf-8');
|
||||||
|
const tests = JSON.parse(data) as Test[];
|
||||||
|
|
||||||
|
it.each(tests.slice(0, maxTests))('Test $name', (test) => {
|
||||||
|
initState(cpu, test.initial);
|
||||||
|
memory.logStart();
|
||||||
|
cpu.step();
|
||||||
|
memory.logStop();
|
||||||
|
expectState(cpu, memory, test);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('WDC 65C02', function() {
|
||||||
|
beforeAll(function() {
|
||||||
|
cpu = new CPU6502({ flavor: FLAVOR_WDC_65C02 });
|
||||||
|
memory = new TestMemory(256);
|
||||||
|
cpu.addPageHandler(memory);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(opAryWDC65C02)('Test op $op $name $mode', ({op}) => {
|
||||||
|
const data = fs.readFileSync(`${testPathWDC65C02}${op}.json`, 'utf-8');
|
||||||
const tests = JSON.parse(data) as Test[];
|
const tests = JSON.parse(data) as Test[];
|
||||||
|
|
||||||
it.each(tests.slice(0, maxTests))('Test $name', (test) => {
|
it.each(tests.slice(0, maxTests))('Test $name', (test) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import CPU6502 from '../js/cpu6502';
|
import CPU6502, { FLAVOR_ROCKWELL_65C02 } from '../js/cpu6502';
|
||||||
// From https://github.com/Klaus2m5/6502_65C02_functional_tests
|
// From https://github.com/Klaus2m5/6502_65C02_functional_tests
|
||||||
import Test6502 from './roms/6502test';
|
import Test6502 from './roms/6502test';
|
||||||
import Test65C02 from './roms/65C02test';
|
import Test65C02 from './roms/65C02test';
|
||||||
|
@ -33,7 +33,7 @@ describe('CPU', function () {
|
||||||
|
|
||||||
describe('65C02', function () {
|
describe('65C02', function () {
|
||||||
it('completes the test ROM', function () {
|
it('completes the test ROM', function () {
|
||||||
cpu = new CPU6502({'65C02': true});
|
cpu = new CPU6502({ flavor: FLAVOR_ROCKWELL_65C02 });
|
||||||
const test = new Test65C02();
|
const test = new Test65C02();
|
||||||
cpu.addPageHandler(test);
|
cpu.addPageHandler(test);
|
||||||
cpu.setPC(0x400);
|
cpu.setPC(0x400);
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import CPU6502, { CpuState, flags } from '../js/cpu6502';
|
import CPU6502, {
|
||||||
|
CpuState,
|
||||||
|
FLAVOR_ROCKWELL_65C02,
|
||||||
|
flags
|
||||||
|
} from '../js/cpu6502';
|
||||||
import { TestMemory } from './util/memory';
|
import { TestMemory } from './util/memory';
|
||||||
import { bios, Program } from './util/bios';
|
import { bios, Program } from './util/bios';
|
||||||
import { toReadableState } from './util/cpu';
|
import { toReadableState } from './util/cpu';
|
||||||
|
@ -277,14 +281,6 @@ describe('CPU6502', function() {
|
||||||
pc: 0x1234
|
pc: 0x1234
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log unimplemented opcodes', () => {
|
|
||||||
jest.spyOn(console, 'log').mockImplementation();
|
|
||||||
testCode([0xFF], 1, {}, {
|
|
||||||
cycles: 1
|
|
||||||
});
|
|
||||||
expect(console.log).toHaveBeenLastCalledWith('Unknown OpCode: FF at 0400');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#registers', function() {
|
describe('#registers', function() {
|
||||||
|
@ -1542,7 +1538,7 @@ describe('CPU6502', function() {
|
||||||
|
|
||||||
describe('65c02', function() {
|
describe('65c02', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
cpu = new CPU6502({'65C02': true});
|
cpu = new CPU6502({ flavor: FLAVOR_ROCKWELL_65C02 });
|
||||||
memory = new TestMemory(4);
|
memory = new TestMemory(4);
|
||||||
|
|
||||||
cpu.addPageHandler(memory);
|
cpu.addPageHandler(memory);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user