Merge pull request #12 from whscullin/whscullin/cpu-cycle-accuracy

CPU cycle accuracy
This commit is contained in:
Will Scullin 2019-09-18 19:56:44 -07:00 committed by GitHub
commit cc5c558a00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 2379 additions and 75 deletions

View File

@ -41,7 +41,8 @@
}, { }, {
"files": [ "test/*"], "files": [ "test/*"],
"env": { "env": {
"jest": true "jest": true,
"node": true
} }
}, { }, {
"files": [ "js/entry2.js", "js/entry2e.js"], "files": [ "js/entry2.js", "js/entry2e.js"],

View File

@ -94,7 +94,6 @@ export default function CPU6502(options)
var readPages = []; var readPages = [];
var writePages = []; var writePages = [];
var resetHandlers = []; var resetHandlers = [];
var inCallback = false;
var cycles = 0; var cycles = 0;
var sync = false; var sync = false;
@ -164,10 +163,12 @@ export default function CPU6502(options)
} }
function increment(a) { function increment(a) {
cycles += 1;
return testNZ((a + 0x01) & 0xff); return testNZ((a + 0x01) & 0xff);
} }
function decrement(a) { function decrement(a) {
cycles += 1;
return testNZ((a + 0xff) & 0xff); return testNZ((a + 0xff) & 0xff);
} }
@ -176,14 +177,26 @@ export default function CPU6502(options)
page = addr >> 8, page = addr >> 8,
off = addr & 0xff; off = addr & 0xff;
return readPages[page].read(page, off, dbg); var result = readPages[page].read(page, off, dbg);
if (!dbg) {
cycles++;
}
return result;
} }
function readByte(addr, dbg) { function readByte(addr, dbg) {
var page = addr >> 8, var page = addr >> 8,
off = addr & 0xff; off = addr & 0xff;
return readPages[page].read(page, off, dbg); var result = readPages[page].read(page, off, dbg);
if (!dbg) {
cycles += 1;
}
return result;
} }
function writeByte(addr, val) { function writeByte(addr, val) {
@ -191,6 +204,8 @@ export default function CPU6502(options)
off = addr & 0xff; off = addr & 0xff;
writePages[page].write(page, off, val); writePages[page].write(page, off, val);
cycles += 1;
} }
function readWord(addr, dbg) { function readWord(addr, dbg) {
@ -210,24 +225,51 @@ export default function CPU6502(options)
return (msb << 8) | lsb; return (msb << 8) | lsb;
} }
function pushByte(val) { function rawPushByte(val) {
writeByte(loc.STACK | sp, val); writeByte(loc.STACK | sp, val);
sp = (sp + 0xff) & 0xff; sp = (sp + 0xff) & 0xff;
} }
function pushWord(val) { function rawPushWord(val) {
pushByte(val >> 8); rawPushByte(val >> 8);
pushByte(val & 0xff); rawPushByte(val & 0xff);
} }
function pullByte() { function pushByte(val) {
writeByte(loc.STACK | sp, val);
sp = (sp + 0xff) & 0xff;
cycles += 1;
}
function pushWord(val) {
rawPushWord(val);
cycles += 1;
}
function rawPullByte() {
sp = (sp + 0x01) & 0xff; sp = (sp + 0x01) & 0xff;
return readByte(loc.STACK | sp); return readByte(loc.STACK | sp);
} }
function rawPullWord() {
var lsb = rawPullByte(loc.STACK | sp);
var msb = rawPullByte(loc.STACK | sp);
return (msb << 8) | lsb;
}
function pullByte() {
sp = (sp + 0x01) & 0xff;
var result = readByte(loc.STACK | sp);
cycles +=2;
return result;
}
function pullWord() { function pullWord() {
var lsb = pullByte(), var lsb = rawPullByte(loc.STACK | sp);
msb = pullByte(); var msb = rawPullByte(loc.STACK | sp);
cycles += 2;
return (msb << 8) | lsb; return (msb << 8) | lsb;
} }
@ -269,7 +311,8 @@ export default function CPU6502(options)
addr = (addr + xr) & 0xffff; addr = (addr + xr) & 0xffff;
page = addr >> 8; page = addr >> 8;
if (page != oldPage) { if (page != oldPage) {
cycles++; var off = addr & 0xff;
readByte(oldPage << 8 | off);
} }
return readByte(addr); return readByte(addr);
} }
@ -280,24 +323,31 @@ export default function CPU6502(options)
addr = (addr + yr) & 0xffff; addr = (addr + yr) & 0xffff;
page = addr >> 8; page = addr >> 8;
if (page != oldPage) { if (page != oldPage) {
cycles++; var off = addr & 0xff;
readByte(oldPage << 8 | off);
} }
return readByte(addr); return readByte(addr);
} }
// $00,X // $00,X
function readZeroPageX() { function readZeroPageX() {
return readByte((readBytePC() + xr) & 0xff); var result = readByte((readBytePC() + xr) & 0xff);
cycles++;
return result;
} }
// $00,Y // $00,Y
function readZeroPageY() { function readZeroPageY() {
return readByte((readBytePC() + yr) & 0xff); var addr = (readBytePC() + yr) & 0xff;
cycles++;
return readByte(addr);
} }
// ($00,X) // ($00,X)
function readZeroPageXIndirect() { function readZeroPageXIndirect() {
return readByte(readZPWord((readBytePC() + xr) & 0xff)); var addr = readZPWord((readBytePC() + xr) & 0xff);
cycles++;
return readByte(addr);
} }
// ($00),Y // ($00),Y
@ -306,7 +356,8 @@ export default function CPU6502(options)
addr = (addr + yr) & 0xffff; addr = (addr + yr) & 0xffff;
page = addr >> 8; page = addr >> 8;
if (page != oldPage) { if (page != oldPage) {
cycles++; var off = addr & 0xff;
readByte(oldPage << 8 | off);
} }
return readByte(addr); return readByte(addr);
} }
@ -332,32 +383,50 @@ export default function CPU6502(options)
// $0000,X // $0000,X
function writeAbsoluteX(val) { function writeAbsoluteX(val) {
writeByte((readWordPC() + xr) & 0xffff, val); var addr = readWordPC(), oldPage = addr >> 8;
addr = (addr + xr) & 0xffff;
var off = addr & 0xff;
readByte(oldPage << 8 | off);
writeByte(addr, val);
} }
// $0000,Y // $0000,Y
function writeAbsoluteY(val) { function writeAbsoluteY(val) {
writeByte((readWordPC() + yr) & 0xffff, val); var addr = readWordPC(), oldPage = addr >> 8;
addr = (addr + yr) & 0xffff;
var off = addr & 0xff;
readByte(oldPage << 8 | off);
writeByte(addr, val);
} }
// $00,X // $00,X
function writeZeroPageX(val) { function writeZeroPageX(val) {
writeByte((readBytePC() + xr) & 0xff, val); var address = (readBytePC() + xr) & 0xff;
cycles++;
writeByte(address, val);
} }
// $00,Y // $00,Y
function writeZeroPageY(val) { function writeZeroPageY(val) {
writeByte((readBytePC() + yr) & 0xff, val); var address = (readBytePC() + yr) & 0xff;
cycles++;
writeByte(address, val);
} }
// ($00,X) // ($00,X)
function writeZeroPageXIndirect(val) { function writeZeroPageXIndirect(val) {
writeByte(readZPWord((readBytePC() + xr) & 0xff), val); var address = readZPWord((readBytePC() + xr) & 0xff);
cycles++;
writeByte(address, val);
} }
// ($00),Y // ($00),Y
function writeZeroPageIndirectY(val) { function writeZeroPageIndirectY(val) {
writeByte((readZPWord(readBytePC()) + yr) & 0xffff, val); var addr = readZPWord(readBytePC()), oldPage = addr >> 8;
addr = (addr + yr) & 0xffff;
var off = addr & 0xff;
readByte(oldPage << 8 | off);
writeByte(addr, val);
} }
// ($00) (65C02) // ($00) (65C02)
@ -372,7 +441,9 @@ export default function CPU6502(options)
// $00,X // $00,X
function readAddrZeroPageX() { function readAddrZeroPageX() {
return (readBytePC() + xr) & 0xff; var result = (readBytePC() + xr) & 0xff;
cycles++;
return result;
} }
// $0000 (65C02) // $0000 (65C02)
@ -387,28 +458,34 @@ export default function CPU6502(options)
// ($0000) (65C02) // ($0000) (65C02)
function readAddrAbsoluteIndirect() { function readAddrAbsoluteIndirect() {
return readWord(readWordPC()); var address = readWordPC();
cycles++;
return readWord(address);
} }
// $0000,X // $0000,X
function readAddrAbsoluteX() { function readAddrAbsoluteX(opts) {
var addr = readWordPC(); var addr = readWordPC();
if (!is65C02) { if (!is65C02 || (opts && opts.inc)) {
readByte(addr); readByte(addr);
} else {
cycles++;
} }
return (addr + xr) & 0xffff; return (addr + xr) & 0xffff;
} }
// $(0000,X) // $(0000,X)
function readAddrAbsoluteXIndirect() { function readAddrAbsoluteXIndirect() {
return readWord((readWordPC() + xr) & 0xffff); var address = (readWordPC() + xr) & 0xffff;
cycles++;
return readWord(address);
} }
/* Break */ /* Break */
function brk(readFn) { function brk(readFn) {
readFn(); readFn();
pushWord(pc); rawPushWord(pc);
php(); rawPushByte(sr | flags.B);
if (is65C02) { if (is65C02) {
setFlag(flags.D, false); setFlag(flags.D, false);
} }
@ -467,7 +544,7 @@ export default function CPU6502(options)
} }
function inc(readAddrFn) { function inc(readAddrFn) {
var addr = readAddrFn(); var addr = readAddrFn({inc: true});
writeByte(addr, increment(readByte(addr))); writeByte(addr, increment(readByte(addr)));
} }
@ -487,7 +564,7 @@ export default function CPU6502(options)
} }
function dec(readAddrFn) { function dec(readAddrFn) {
var addr = readAddrFn(); var addr = readAddrFn({inc: true});
writeByte(addr, decrement(readByte(addr))); writeByte(addr, decrement(readByte(addr)));
} }
@ -503,6 +580,7 @@ export default function CPU6502(options)
function shiftLeft(val) { function shiftLeft(val) {
setFlag(flags.C, val & 0x80); setFlag(flags.C, val & 0x80);
cycles++;
return testNZ((val << 1) & 0xff); return testNZ((val << 1) & 0xff);
} }
@ -518,6 +596,7 @@ export default function CPU6502(options)
function shiftRight(val) { function shiftRight(val) {
setFlag(flags.C, val & 0x01); setFlag(flags.C, val & 0x01);
cycles++;
return testNZ(val >> 1); return testNZ(val >> 1);
} }
@ -534,6 +613,7 @@ export default function CPU6502(options)
function rotateLeft(val) { function rotateLeft(val) {
var c = (sr & flags.C); var c = (sr & flags.C);
setFlag(flags.C, val & 0x80); setFlag(flags.C, val & 0x80);
cycles++;
return testNZ(((val << 1) | (c ? 0x01 : 0x00)) & 0xff); return testNZ(((val << 1) | (c ? 0x01 : 0x00)) & 0xff);
} }
@ -550,6 +630,7 @@ export default function CPU6502(options)
function rotateRight(a) { function rotateRight(a) {
var c = (sr & flags.C); var c = (sr & flags.C);
setFlag(flags.C, a & 0x01); setFlag(flags.C, a & 0x01);
cycles++;
return testNZ((a >> 1) | (c ? 0x80 : 0x00)); return testNZ((a >> 1) | (c ? 0x80 : 0x00));
} }
@ -585,6 +666,7 @@ export default function CPU6502(options)
var addr = readBytePC(); var addr = readBytePC();
var val = readByte(addr); var val = readByte(addr);
val &= bit; val &= bit;
cycles++;
writeByte(addr, val); writeByte(addr, val);
} }
@ -595,6 +677,7 @@ export default function CPU6502(options)
var addr = readBytePC(); var addr = readBytePC();
var val = readByte(addr); var val = readByte(addr);
val |= bit; val |= bit;
cycles++;
writeByte(addr, val); writeByte(addr, val);
} }
@ -603,6 +686,7 @@ export default function CPU6502(options)
var addr = readAddrFn(), var addr = readAddrFn(),
val = readByte(addr); val = readByte(addr);
testZ(val & ar); testZ(val & ar);
cycles++;
writeByte(addr, val & ~ar); writeByte(addr, val & ~ar);
} }
@ -611,6 +695,7 @@ export default function CPU6502(options)
var addr = readAddrFn(), var addr = readAddrFn(),
val = readByte(addr); val = readByte(addr);
testZ(val & ar); testZ(val & ar);
cycles++;
writeByte(addr, val | ar); writeByte(addr, val | ar);
} }
@ -674,6 +759,7 @@ export default function CPU6502(options)
function bbr(b) { function bbr(b) {
var val = readZeroPage(); var val = readZeroPage();
var off = readBytePC(); // changes pc var off = readBytePC(); // changes pc
cycles++;
if (((1 << b) & val) === 0) { if (((1 << b) & val) === 0) {
pc += off > 127 ? off - 256 : off; pc += off > 127 ? off - 256 : off;
} }
@ -682,23 +768,24 @@ export default function CPU6502(options)
function bbs(b) { function bbs(b) {
var val = readZeroPage(); // ZP var val = readZeroPage(); // ZP
var off = readBytePC(); // changes pc var off = readBytePC(); // changes pc
cycles++;
if (((1 << b) & val) !== 0) { if (((1 << b) & val) !== 0) {
pc += off > 127 ? off - 256 : off; pc += off > 127 ? off - 256 : off;
} }
} }
/* Transfers and stack */ /* Transfers and stack */
function tax() { testNZ(xr = ar); } function tax() { testNZ(xr = ar); cycles += 1; }
function txa() { testNZ(ar = xr); } function txa() { testNZ(ar = xr); cycles += 1; }
function tay() { testNZ(yr = ar); } function tay() { testNZ(yr = ar); cycles += 1; }
function tya() { testNZ(ar = yr); } function tya() { testNZ(ar = yr); cycles += 1; }
function tsx() { testNZ(xr = sp); } function tsx() { testNZ(xr = sp); cycles += 1; }
function txs() { sp = xr; } function txs() { sp = xr; cycles += 1; }
function pha() { pushByte(ar); } function pha() { pushByte(ar); }
@ -731,26 +818,31 @@ export default function CPU6502(options)
/* Return from Subroutine */ /* Return from Subroutine */
function rts() { function rts() {
pc = (pullWord() + 1) & 0xffff; pc = (pullWord() + 1) & 0xffff;
cycles += 1;
} }
/* Return from Subroutine */ /* Return from Subroutine */
function rti() { function rti() {
sr = pullByte() & ~flags.B; sr = rawPullByte() & ~flags.B;
pc = pullWord(); pc = rawPullWord();
cycles += 2;
} }
/* Set and Clear */ /* Set and Clear */
function set(flag) { function set(flag) {
sr |= flag; sr |= flag;
cycles += 1;
} }
function clr(flag) { function clr(flag) {
sr &= ~flag; sr &= ~flag;
cycles += 1;
} }
/* No-Op */ /* No-Op */
function nop(readAddrFn) { function nop(readAddrFn) {
readAddrFn(); readAddrFn();
cycles += 1;
} }
var ops = { var ops = {
@ -871,8 +963,8 @@ export default function CPU6502(options)
// AND // AND
0x29: ['AND', and, readImmediate, modes.immediate, 2], 0x29: ['AND', and, readImmediate, modes.immediate, 2],
0x25: ['AND', and, readZeroPage, modes.zeroPage, 2], 0x25: ['AND', and, readZeroPage, modes.zeroPage, 3],
0x35: ['AND', and, readZeroPageX, modes.zeroPageX, 3], 0x35: ['AND', and, readZeroPageX, modes.zeroPageX, 4],
0x2D: ['AND', and, readAbsolute, modes.absolute, 4], 0x2D: ['AND', and, readAbsolute, modes.absolute, 4],
0x3D: ['AND', and, readAbsoluteX, modes.absoluteX, 4], 0x3D: ['AND', and, readAbsoluteX, modes.absoluteX, 4],
0x39: ['AND', and, readAbsoluteY, modes.absoluteY, 4], 0x39: ['AND', and, readAbsoluteY, modes.absoluteY, 4],
@ -881,8 +973,8 @@ export default function CPU6502(options)
// ORA // ORA
0x09: ['ORA', ora, readImmediate, modes.immediate, 2], 0x09: ['ORA', ora, readImmediate, modes.immediate, 2],
0x05: ['ORA', ora, readZeroPage, modes.zeroPage, 2], 0x05: ['ORA', ora, readZeroPage, modes.zeroPage, 3],
0x15: ['ORA', ora, readZeroPageX, modes.zeroPageX, 3], 0x15: ['ORA', ora, readZeroPageX, modes.zeroPageX, 4],
0x0D: ['ORA', ora, readAbsolute, modes.absolute, 4], 0x0D: ['ORA', ora, readAbsolute, modes.absolute, 4],
0x1D: ['ORA', ora, readAbsoluteX, modes.absoluteX, 4], 0x1D: ['ORA', ora, readAbsoluteX, modes.absoluteX, 4],
0x19: ['ORA', ora, readAbsoluteY, modes.absoluteY, 4], 0x19: ['ORA', ora, readAbsoluteY, modes.absoluteY, 4],
@ -1139,9 +1231,6 @@ export default function CPU6502(options)
if (is65C02) { if (is65C02) {
for (var key in cops) { for (var key in cops) {
if (cops.hasOwnProperty(key)) { if (cops.hasOwnProperty(key)) {
if (key in ops) {
debug('overriding opcode ' + toHex(key));
}
ops[key] = cops[key]; ops[key] = cops[key];
} }
} }
@ -1162,10 +1251,8 @@ export default function CPU6502(options)
unk = [ unk = [
'???', '???',
function() { function() {
/*
debug('Unknown OpCode: ' + toHex(b) + debug('Unknown OpCode: ' + toHex(b) +
' at ' + toHex(pc - 1, 4)); ' at ' + toHex(pc - 1, 4));
*/
}, },
readImplied, readImplied,
modes.implied, modes.implied,
@ -1266,18 +1353,14 @@ export default function CPU6502(options)
sync = true; sync = true;
var op = opary[readBytePC()]; var op = opary[readBytePC()];
sync = false; sync = false;
op[1](op[2]); op[1](op[2]);
cycles += op[4];
if (cb) { if (cb) {
inCallback = true;
cb(this); cb(this);
inCallback = false;
} }
}, },
stepN: function(n) { stepDebug: function(n, cb) {
var op, idx; var op, idx;
for (idx = 0; idx < n; idx++) { for (idx = 0; idx < n; idx++) {
@ -1285,7 +1368,10 @@ export default function CPU6502(options)
op = opary[readBytePC()]; op = opary[readBytePC()];
sync = false; sync = false;
op[1](op[2]); op[1](op[2]);
cycles += op[4];
if (cb) {
cb(this);
}
} }
}, },
@ -1297,7 +1383,6 @@ export default function CPU6502(options)
op = opary[readBytePC()]; op = opary[readBytePC()];
sync = false; sync = false;
op[1](op[2]); op[1](op[2]);
cycles += op[4];
} }
}, },
@ -1305,21 +1390,14 @@ export default function CPU6502(options)
{ {
var op, end = cycles + c; var op, end = cycles + c;
if (inCallback) {
return;
}
while (cycles < end) { while (cycles < end) {
sync = true; sync = true;
op = opary[readBytePC()]; op = opary[readBytePC()];
sync = false; sync = false;
op[1](op[2]); op[1](op[2]);
cycles += op[4];
if (cb) { if (cb) {
inCallback = true;
cb(this); cb(this);
inCallback = false;
} }
} }
}, },
@ -1350,7 +1428,7 @@ export default function CPU6502(options)
} }
}, },
/* IRQ - Interupt Request */ /* IRQ - Interrupt Request */
irq: function cpu_irq() irq: function cpu_irq()
{ {
if ((sr & flags.I) === 0) { if ((sr & flags.I) === 0) {

View File

@ -227,7 +227,6 @@ function doLoadLocalDisk(drive, file) {
var name = parts.join('.'); var name = parts.join('.');
if (disk2.setBinary(drive, name, ext, this.result)) { if (disk2.setBinary(drive, name, ext, this.result)) {
drivelights.label(drive, name); drivelights.label(drive, name);
MicroModal.close('load-modal');
initGamepad(); initGamepad();
} }
}; };

View File

@ -3,17 +3,17 @@ import CPU6502 from '../js/cpu6502';
import Test6502 from '../js/roms/6502test'; import Test6502 from '../js/roms/6502test';
import Test65C02 from '../js/roms/65C02test'; import Test65C02 from '../js/roms/65C02test';
var cpu;
var lastPC = 0;
var done = false;
function traceCB() {
var pc = cpu.getPC();
done = lastPC == pc;
lastPC = pc;
}
describe('CPU', function () { describe('CPU', function () {
var cpu;
var lastPC = 0;
var done = false;
function traceCB() {
var pc = cpu.getPC();
done = lastPC == pc;
lastPC = pc;
}
describe('6502', function () { describe('6502', function () {
it('completes the test ROM', function () { it('completes the test ROM', function () {
cpu = new CPU6502(); cpu = new CPU6502();

2226
test/cpu6502.spec.js Normal file

File diff suppressed because it is too large Load Diff