NMOS illegal opcodes

This commit is contained in:
Will Scullin 2021-12-18 20:17:27 -08:00
parent 7489458011
commit 644d3c4107
No known key found for this signature in database
GPG Key ID: 26DCD1042C6638CD
3 changed files with 409 additions and 38 deletions

View File

@ -11,7 +11,7 @@
*/
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';
@ -235,20 +235,26 @@ export default class CPU6502 {
let ops = { ...this.OPS_6502 };
if (this.flavor === FLAVOR_WDC_65C02) {
ops = {
...ops,
...this.OPS_65C02,
...this.OPS_WDC_65C02,
};
}
if (this.flavor === FLAVOR_ROCKWELL_65C02) {
ops = {
...ops,
...this.OPS_65C02,
...this.OPS_ROCKWELL_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
@ -677,6 +683,34 @@ export default class CPU6502 {
return addr;
};
// $0000,Y (NMOS 6502)
readAddrAbsoluteY = (): word => {
let addr = this.readWordPC();
const page = addr & 0xff00;
addr = (addr + this.yr) & 0xffff;
const off = addr & 0x00ff;
this.readByte(page | off);
return addr;
};
// ($00,X) (NMOS 6502)
readAddrZeroPageXIndirect = () => {
const zpAddr = this.readBytePC();
this.readByte(zpAddr);
return this.readZPWord((zpAddr + this.xr) & 0xff);
};
// ($00),Y (NMOS 6502)
readAddrZeroPageIndirectY = () => {
const zpAddr = this.readBytePC();
const addr = this.readZPWord(zpAddr);
const addrIdx = (addr + this.yr) & 0xffff;
const oldPage = addr & 0xff00;
const off = addrIdx & 0xff;
this.readByte(oldPage | off);
return addrIdx;
};
// $(0000,X) (65C02)
readAddrAbsoluteXIndirect = (): word => {
const lsb = this.readBytePC();
@ -1126,9 +1160,204 @@ export default class CPU6502 {
readFn();
};
/* NMOS 6502 Illegal opcodes */
/* ASO = ASL + ORA */
aso = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.shiftLeft(oldVal);
this.writeByte(addr, val);
this.ar |= val;
this.testNZ(this.ar);
};
/* RLA = ROL + AND */
rla = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.rotateLeft(oldVal);
this.writeByte(addr, val);
this.ar &= val;
this.testNZ(this.ar);
};
/* LSE = LSR + EOR */
lse = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.shiftRight(oldVal);
this.writeByte(addr, val);
this.ar ^= val;
this.testNZ(this.ar);
};
/* RRA = ROR + ADC */
rra = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.rotateRight(oldVal);
this.writeByte(addr, val);
this.ar = this.add(this.ar, val, false);
};
/* AXS = Store A & X */
axs = (writeFn: WriteFn) => {
writeFn(this.ar & this.xr);
};
/* LAX = Load A & X */
lax = (readFn: ReadFn) => {
const val = readFn();
this.ar = val;
this.xr = val;
this.testNZ(val);
};
/* DCM = DEC + CMP */
dcm = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn({ inc: true});
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.decrement(oldVal);
this.writeByte(addr, val);
this.compare(this.ar, val);
};
/* INS = INC + SBC */
ins = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn({ inc: true});
const oldVal = this.readByte(addr);
this.workCycle(addr, oldVal);
const val = this.increment(oldVal);
this.writeByte(addr, val);
this.ar = this.add(this.ar, val ^ 0xff, true);
};
/* ALR = AND + LSR */
alr = (readFn: ReadFn) => {
const val = readFn() & this.ar;
this.ar = this.shiftRight(val);
};
/* ARR = AND + ROR */
arr = (readFn: ReadFn) => {
const val = readFn() & this.ar;
const ah = val >> 4;
const al = val & 0xf;
const b7 = val >> 7;
const b6 = (val >> 6) & 0x1;
this.ar = this.rotateRight(val);
let c = !!b7;
const v = !!(b7 ^ b6);
if (this.sr & flags.D) {
if (al + (al & 0x1) > 0x5) {
this.ar = (this.ar & 0xf0) | ((this.ar + 0x6) & 0xf);
}
if (ah + (ah & 0x1) > 5) {
c = true;
this.ar =((this.ar + 0x60) & 0xff);
}
}
this.setFlag(flags.V, v);
this.setFlag(flags.C, c);
};
/* XAA = TAX + AND */
xaa = (readFn: ReadFn) => {
const val = readFn();
this.ar = (this.xr & 0xEE) | (this.xr & this.ar & 0x11);
this.ar = this.testNZ(this.ar & val);
};
/** OAL = ORA + AND */
oal = (readFn: ReadFn) => {
this.ar |= 0xEE;
const val = this.testNZ(this.ar & readFn());
this.ar = val;
this.xr = val;
};
/* SAX = A & X + SBC */
sax = (readFn: ReadFn) => {
const a = this.xr & this.ar;
let b = readFn();
b = (b ^ 0xff);
const c = a + b + 1;
this.setFlag(flags.C, c > 0xff);
this.xr = this.testNZ(c & 0xff);
};
/* TAS = X & Y -> S */
tas = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
let val = this.xr & this.ar;
this.sp = val;
const msb = addr >> 8;
val = val & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* SAY = Y & AH + 1 */
say = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const msb = addr >> 8;
const val = this.yr & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* XAS = X & AH + 1 */
xas = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
const msb = addr >> 8;
const val = this.xr & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* AXA = X & AH + 1 */
axa = (readAddrFn: ReadAddrFn) => {
const addr = readAddrFn();
let val = this.xr & this.ar;
const msb = addr >> 8;
val = val & ((msb + 1) & 0xff);
this.writeByte(addr, val);
};
/* ANC = AND with carry */
anc = (readFn: ReadFn) => {
this.ar = this.testNZ(this.ar & readFn());
const c = !!(this.ar & 0x80);
this.setFlag(flags.C, c);
};
/* LAS = RD & SP -> A, X, S */
las = (readFn: ReadFn) => {
const val = this.sp & readFn();
this.sp = val;
this.xr = val;
this.ar = this.testNZ(val);
};
/* SKB/SKW */
skp = (readFn: ReadFn) => {
readFn();
};
/* HLT */
hlt = (_impliedFn: ImpliedFn) => {
this.readByte(this.pc);
this.readByte(this.pc);
// PC shouldn't have advanced
this.pc = (--this.pc) & 0xffff;
this.stop = true;
};
private unknown(b: byte) {
let unk: StrictInstruction;
if (this.is65C02) {
// Default behavior is a 1 cycle NOP
unk = {
@ -1138,21 +1367,12 @@ export default class CPU6502 {
mode: 'implied',
};
} else {
const cpu = this;
unk = {
name: '???',
op: function (_impliedFn: ImpliedFn) {
debug('Unknown OpCode: ' + toHex(b) +
' at ' + toHex(cpu.pc - 1, 4));
},
modeFn: this.implied,
mode: 'implied'
};
// All 6502 Instructions should be defined
throw new Error(`Missing ${toHex(b)}`);
}
return unk;
}
public step(cb?: callback) {
this.sync = true;
this.op = this.opary[this.readBytePC()];
@ -1737,6 +1957,161 @@ export default class CPU6502 {
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' },

View File

@ -168,8 +168,6 @@ if (testPath) {
const buildOpArrays = () => {
const cpu = new CPU6502();
// Grab the implemented op codes
// TODO: Decide which undocumented opcodes are worthwhile.
for (const op in cpu.OPS_6502) {
const { name, mode } = cpu.OPS_6502[op];
const test = { op: toHex(+op), name, mode };
@ -178,6 +176,12 @@ if (testPath) {
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) {
const { name, mode } = cpu.OPS_65C02[op];
const test = { op: toHex(+op), name, mode };
@ -212,7 +216,7 @@ if (testPath) {
let cpu: CPU6502;
let memory: TestMemory;
describe('6502', function() {
describe('NMOS 6502', function() {
beforeAll(function() {
cpu = new CPU6502();
memory = new TestMemory(256);

View File

@ -281,14 +281,6 @@ describe('CPU6502', function() {
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() {