diff --git a/6502.js b/6502.js index d5a8e680..3f97795b 100644 --- a/6502.js +++ b/6502.js @@ -1,6 +1,5 @@ "use strict"; import * as utils from "./utils.js"; -import * as opcodes from "./6502.opcodes.js"; import * as via from "./via.js"; import { Acia } from "./acia.js"; import { Serial } from "./serial.js"; @@ -107,7 +106,7 @@ class Base6502 { this.a = this.x = this.y = this.s = 0; this.p = new Flags(); this.pc = 0; - this.opcodes = model.nmos ? opcodes.Cpu6502(this) : opcodes.Cpu65c12(this); + this.opcodes = model.opcodesFactory(this); this.disassembler = this.opcodes.disassembler; this.forceTracing = false; this.runner = this.opcodes.runInstruction; diff --git a/6502.opcodes.js b/6502.opcodes.js index 92cd3847..3d25cb4a 100644 --- a/6502.opcodes.js +++ b/6502.opcodes.js @@ -469,8 +469,52 @@ function getOp(op, arg) { read: true, write: true, }; + case "RMB0": + case "RMB1": + case "RMB2": + case "RMB3": + case "RMB4": + case "RMB5": + case "RMB6": + case "RMB7": + return { + op: [`REG = REG & (${~(1 << (op[3] - "0"))});`], + read: true, + write: true, + }; + case "SMB0": + case "SMB1": + case "SMB2": + case "SMB3": + case "SMB4": + case "SMB5": + case "SMB6": + case "SMB7": + return { + op: [`REG = REG | ${1 << (op[3] - "0")};`], + read: true, + write: true, + }; + case "BBR0": + case "BBR1": + case "BBR2": + case "BBR3": + case "BBR4": + case "BBR5": + case "BBR6": + case "BBR7": + return { op: `cpu.branch(!(REG & ${1 << (op[3] - "0")}));`, read: true }; + case "BBS0": + case "BBS1": + case "BBS2": + case "BBS3": + case "BBS4": + case "BBS5": + case "BBS6": + case "BBS7": + return { op: `cpu.branch(REG & ${1 << (op[3] - "0")});`, read: true }; } - return null; + throw new Error(`Unrecognised operation '${op}'`); } const opcodes6502 = { @@ -902,6 +946,42 @@ const opcodes65c12 = { 0xfe: "INC abs,x", }; +const opcodes65c02 = { + ...opcodes65c12, + 0x07: "RMB0 zp", + 0x17: "RMB1 zp", + 0x27: "RMB2 zp", + 0x37: "RMB3 zp", + 0x47: "RMB4 zp", + 0x57: "RMB5 zp", + 0x67: "RMB6 zp", + 0x77: "RMB7 zp", + 0x87: "SMB0 zp", + 0x97: "SMB1 zp", + 0xa7: "SMB2 zp", + 0xb7: "SMB3 zp", + 0xc7: "SMB4 zp", + 0xd7: "SMB5 zp", + 0xe7: "SMB6 zp", + 0xf7: "SMB7 zp", + 0x0f: "BBR0 zp,branch", + 0x1f: "BBR1 zp,branch", + 0x2f: "BBR2 zp,branch", + 0x3f: "BBR3 zp,branch", + 0x4f: "BBR4 zp,branch", + 0x5f: "BBR5 zp,branch", + 0x6f: "BBR6 zp,branch", + 0x7f: "BBR7 zp,branch", + 0x8f: "BBS0 zp,branch", + 0x9f: "BBS1 zp,branch", + 0xaf: "BBS2 zp,branch", + 0xbf: "BBS3 zp,branch", + 0xcf: "BBS4 zp,branch", + 0xdf: "BBS5 zp,branch", + 0xef: "BBS6 zp,branch", + 0xff: "BBS7 zp,branch", +}; + class Disassemble6502 { constructor(cpu, opcodes) { this.cpu = cpu; @@ -986,7 +1066,7 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { switch (arg) { case undefined: // Many of these ops need a little special casing. - if (op.read || op.write) throw "Unsupported " + opcodeString; + if (op.read || op.write) throw new Error(`Unsupported ${opcodeString}`); ig.append(op.preop); ig.tick(Math.max(2, 1 + (op.extra || 0))); ig.append(op.op); @@ -995,6 +1075,19 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { case "branch": return [op.op]; // special cased here, would be nice to pull out of cpu + case "zp,branch": + ig.tick(2); + ig.append("const addr = cpu.getb() | 0;"); + if (op.read) { + ig.zpReadOp("addr", "REG"); + if (op.write) { + ig.tick(1); // Spurious write + } + } + ig.append(op.op); + if (op.write) ig.zpWriteOp("addr", "REG"); + return ig.render(); + case "zp": case "zpx": // Seems to be enough to keep tests happy, but needs investigation. case "zp,x": @@ -1082,7 +1175,7 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { case "imm": if (op.write) { - throw "This isn't possible"; + throw new Error("This isn't possible"); } if (op.read) { // NOP imm @@ -1188,7 +1281,7 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { return ig.render(); default: - throw "Unknown arg type " + arg; + throw new Error(`Unknown arg type ${arg}`); } } @@ -1309,3 +1402,7 @@ export function Cpu6502(cpu) { export function Cpu65c12(cpu) { return makeCpuFunctions(cpu, opcodes65c12, true); } + +export function Cpu65c02(cpu) { + return makeCpuFunctions(cpu, opcodes65c02, true); +} diff --git a/fake6502.js b/fake6502.js index 599d2871..b980d5b4 100644 --- a/fake6502.js +++ b/fake6502.js @@ -1,9 +1,9 @@ -// Fakes out a 6502 +// Fakes out various 6502s for testing purposes. "use strict"; import { FakeVideo } from "./video.js"; import { FakeSoundChip } from "./soundchip.js"; -import { TEST_6502, TEST_65C12 } from "./models.js"; +import { findModel, TEST_6502, TEST_65C02, TEST_65C12 } from "./models.js"; import { FakeDdNoise } from "./ddnoise.js"; import { Cpu6502 } from "./6502.js"; import { Cmos } from "./cmos.js"; @@ -19,9 +19,14 @@ export function fake6502(model, opts) { opts = opts || {}; const video = opts.video || fakeVideo; model = model || TEST_6502; + if (opts.tube) model.tube = findModel("Tube65c02"); return new Cpu6502(model, dbgr, video, soundChip, new FakeDdNoise(), new FakeMusic5000(), new Cmos()); } +export function fake65C02() { + return fake6502(TEST_65C02); +} + export function fake65C12() { return fake6502(TEST_65C12); } diff --git a/models.js b/models.js index 94c80aa8..360f824d 100644 --- a/models.js +++ b/models.js @@ -2,13 +2,20 @@ import { NoiseAwareWdFdc } from "./wd-fdc.js"; import { NoiseAwareIntelFdc } from "./intel-fdc.js"; +import * as opcodes from "./6502.opcodes.js"; + +const CpuModel = Object.freeze({ + MOS6502: 0, + CMOS65C02: 1, + CMOS65C12: 2, +}); class Model { - constructor(name, synonyms, os, nmos, isMaster, swram, fdc, tube, cmosOverride) { + constructor(name, synonyms, os, cpuModel, isMaster, swram, fdc, tube, cmosOverride) { this.name = name; this.synonyms = synonyms; this.os = os; - this.nmos = nmos; + this._cpuModel = cpuModel; this.isMaster = isMaster; this.Fdc = fdc; this.swram = swram; @@ -16,16 +23,34 @@ class Model { this.tube = tube; this.cmosOverride = cmosOverride; } + + get nmos() { + return this._cpuModel === CpuModel.MOS6502; + } + + get opcodesFactory() { + switch (this._cpuModel) { + case CpuModel.MOS6502: + return opcodes.Cpu6502; + case CpuModel.CMOS65C02: + return opcodes.Cpu65c02; + case CpuModel.CMOS65C12: + return opcodes.Cpu65c12; + } + throw new Error("Unknown CPU model"); + } } function pickAdfs(cmos) { cmos[19] = (cmos[19] & 0xf0) | 13; return cmos; } + function pickAnfs(cmos) { cmos[19] = (cmos[19] & 0xf0) | 8; return cmos; } + function pickDfs(cmos) { cmos[19] = (cmos[19] & 0xf0) | 9; return cmos; @@ -74,7 +99,7 @@ export const allModels = [ "BBC B with DFS 1.2", ["B-DFS1.2"], ["os.rom", "BASIC.ROM", "b/DFS-1.2.rom"], - true, + CpuModel.MOS6502, false, beebSwram, NoiseAwareIntelFdc, @@ -83,7 +108,7 @@ export const allModels = [ "BBC B with DFS 0.9", ["B-DFS0.9", "B"], ["os.rom", "BASIC.ROM", "b/DFS-0.9.rom"], - true, + CpuModel.MOS6502, false, beebSwram, NoiseAwareIntelFdc, @@ -92,7 +117,7 @@ export const allModels = [ "BBC B with 1770 (DFS)", ["B1770"], ["os.rom", "BASIC.ROM", "b1770/dfs1770.rom", "b1770/zADFS.ROM"], - true, + CpuModel.MOS6502, false, beebSwram, NoiseAwareWdFdc, @@ -102,7 +127,7 @@ export const allModels = [ "BBC B with 1770 (ADFS)", ["B1770A"], ["os.rom", "BASIC.ROM", "b1770/zADFS.ROM", "b1770/dfs1770.rom"], - true, + CpuModel.MOS6502, false, beebSwram, NoiseAwareWdFdc, @@ -111,7 +136,7 @@ export const allModels = [ "BBC Master 128 (DFS)", ["Master"], ["master/mos3.20"], - false, + CpuModel.CMOS65C12, true, masterSwram, NoiseAwareWdFdc, @@ -122,7 +147,7 @@ export const allModels = [ "BBC Master 128 (ADFS)", ["MasterADFS"], ["master/mos3.20"], - false, + CpuModel.CMOS65C12, true, masterSwram, NoiseAwareWdFdc, @@ -133,14 +158,14 @@ export const allModels = [ "BBC Master 128 (ANFS)", ["MasterANFS"], ["master/mos3.20"], - false, + CpuModel.CMOS65C12, true, masterSwram, NoiseAwareWdFdc, null, pickAnfs, ), - new Model("Tube65C02", [], ["tube/6502Tube.rom"], false, false), // Although this can not be explicitly selected as a model, it is required by the configuration builder later + new Model("Tube65C02", [], ["tube/6502Tube.rom"], CpuModel.CMOS65C02, false), // Although this can not be explicitly selected as a model, it is required by the configuration builder later ]; export function findModel(name) { @@ -155,16 +180,18 @@ export function findModel(name) { return null; } -export const TEST_6502 = new Model("TEST", ["TEST"], [], true, false, beebSwram, NoiseAwareIntelFdc); +export const TEST_6502 = new Model("TEST", ["TEST"], [], CpuModel.MOS6502, false, beebSwram, NoiseAwareIntelFdc); TEST_6502.isTest = true; -export const TEST_65C12 = new Model("TEST", ["TEST"], [], false, false, masterSwram, NoiseAwareIntelFdc); +export const TEST_65C02 = new Model("TEST", ["TEST"], [], CpuModel.CMOS65C02, false, masterSwram, NoiseAwareIntelFdc); +TEST_65C02.isTest = true; +export const TEST_65C12 = new Model("TEST", ["TEST"], [], CpuModel.CMOS65C12, false, masterSwram, NoiseAwareIntelFdc); TEST_65C12.isTest = true; export const basicOnly = new Model( "Basic only", ["Basic only"], ["master/mos3.20"], - false, + CpuModel.CMOS65C12, true, masterSwram, NoiseAwareWdFdc, diff --git a/package.json b/package.json index 60387598..13f7a0ee 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "test-long:cpu": "node tests/test-suite.js", "test:unit": "mocha tests/unit", "test:integration": "mocha tests/integration", - "test:dormann": "node tests/test-dormann.js", "test": "npm-run-all -p test:*", "test-long": "npm-run-all -p test-long:*", "benchmark": "node app-bench.js" diff --git a/tests/integration/dormann.js b/tests/integration/dormann.js new file mode 100644 index 00000000..192c588d --- /dev/null +++ b/tests/integration/dormann.js @@ -0,0 +1,101 @@ +"use strict"; + +import _ from "underscore"; +import * as utils from "../../utils.js"; +import { fake6502, fake65C02, fake65C12 } from "../../fake6502.js"; +import { describe, it } from "mocha"; +import assert from "assert"; + +const log = false; + +async function runTest(processor, test, name) { + const base = "tests/6502_65C02_functional_tests/bin_files/" + test; + + function parseSuccess(listing) { + let expectedPc = null; + let next = false; + let successRe = /^\s*success\b\s*(;.*)?$/; + _.each(listing.split("\n"), function (line) { + if (next) { + next = false; + expectedPc = parseInt(line.match(/^([0-9a-fA-F]+)/)[1], 16); + console.log("Found success address $" + utils.hexword(expectedPc)); + } else { + next = !!line.match(successRe); + } + }); + if (expectedPc === null) throw "Unable to parse"; + return expectedPc; + } + + const expectedPc = parseSuccess((await utils.loadData(base + ".lst")).toString()); + const data = await utils.loadData(base + ".bin"); + for (let i = 0; i < data.length; ++i) processor.writemem(i, data[i]); + + processor.pc = 0x400; + processor.debugInstruction.add(function (addr) { + if (log) { + console.log( + utils.hexword(addr) + + " : A=" + + utils.hexbyte(processor.a) + + " : X=" + + utils.hexbyte(processor.x) + + " : Y=" + + utils.hexbyte(processor.y) + + " : " + + processor.disassembler.disassemble(processor.pc)[0], + ); + } + + // Stop once we get stuck at the same address. + return addr === processor.getPrevPc(1); + }); + console.log("Running Dormann " + name + " tests..."); + processor.execute(2000000 * 60); + console.log(`Run complete at $${utils.hexword(processor.pc)}`); + const result = processor.pc === expectedPc; + if (!result) logFailure(processor); + return result; +} + +function logFailure(processor) { + console.log("Failed at " + utils.hexword(processor.pc)); + console.log("Previous PCs:"); + for (let i = 1; i < 16; ++i) { + console.log(" " + utils.hexword(processor.getPrevPc(i))); + } + console.log("A: " + utils.hexbyte(processor.a)); + console.log("X: " + utils.hexbyte(processor.x)); + console.log("Y: " + utils.hexbyte(processor.y)); + console.log("S: " + utils.hexbyte(processor.s)); + console.log("P: " + utils.hexbyte(processor.p.asByte()) + " " + processor.p.debugString()); + console.log( + utils.hd( + function (i) { + return processor.readmem(i); + }, + 0x00, + 0x40, + ), + ); +} + +describe("dormann tests", function () { + this.timeout(10000); + it("should pass 6502 functional tests", async () => { + const cpu = fake6502(); + await cpu.initialise(); + assert(await runTest(cpu, "6502_functional_test", "6502")); + }); + it("should pass 65c02 extended opcode tests", async () => { + const cpu = fake65C02(); + await cpu.initialise(); + assert(await runTest(cpu, "65C02_extended_opcodes_test", "65C02")); + }); + it("should pass 65c12 extended opcode tests", async () => { + const cpu = fake65C12(); + await cpu.initialise(); + assert(await runTest(cpu, "65C12_extended_opcodes_test", "65C12")); + }); +}); diff --git a/tests/integration/test-machine.js b/tests/integration/test-machine.js index 14e2ed56..447222dc 100644 --- a/tests/integration/test-machine.js +++ b/tests/integration/test-machine.js @@ -1,7 +1,6 @@ import * as fdc from "../../fdc.js"; import { fake6502 } from "../../fake6502.js"; import { findModel } from "../../models.js"; -import { FakeVideo } from "../../video.js"; import assert from "assert"; import * as utils from "../../utils.js"; import * as Tokeniser from "../../basic-tokenise.js"; @@ -9,9 +8,9 @@ import * as Tokeniser from "../../basic-tokenise.js"; const MaxCyclesPerIter = 100 * 1000; export class TestMachine { - constructor(model) { + constructor(model, opts) { model = model || "B-DFS1.2"; - this.processor = fake6502(findModel(model), { video: new FakeVideo() }); + this.processor = fake6502(findModel(model), opts || {}); } async initialise() { diff --git a/tests/test-dormann.js b/tests/test-dormann.js deleted file mode 100644 index 33067b35..00000000 --- a/tests/test-dormann.js +++ /dev/null @@ -1,106 +0,0 @@ -"use strict"; - -import _ from "underscore"; -import * as utils from "../utils.js"; -import { fake6502, fake65C12 } from "../fake6502.js"; - -function runTest(processor, test, name) { - let base = "tests/6502_65C02_functional_tests/bin_files/" + test; - - function parseSuccess(listing) { - let expectedPc = null; - let next = false; - let successRe = /^\s*success\b\s*(;.*)?$/; - _.each(listing.split("\n"), function (line) { - if (next) { - next = false; - expectedPc = parseInt(line.match(/^([0-9a-fA-F]+)/)[1], 16); - console.log("Found success address $" + utils.hexword(expectedPc)); - } else { - next = !!line.match(successRe); - } - }); - if (expectedPc === null) throw "Unable to parse"; - return expectedPc; - } - - return Promise.all([utils.loadData(base + ".lst"), utils.loadData(base + ".bin")]).then(function (results) { - let expectedPc = parseSuccess(results[0].toString()); - let data = results[1]; - for (let i = 0; i < data.length; ++i) processor.writemem(i, data[i]); - processor.pc = 0x400; - let log = false; - processor.debugInstruction.add(function (addr) { - if (log) { - console.log( - utils.hexword(addr) + - " : " + - utils.hexbyte(processor.a) + - " : " + - processor.disassembler.disassemble(processor.pc)[0], - ); - } - - return addr !== 0x400 && addr === processor.getPrevPc(1); - }); - console.log("Running Dormann " + name + " tests..."); - while (processor.execute(1000000)) { - // do nothing - } - return processor.pc === expectedPc; - }); -} - -function fail(processor) { - console.log("Failed at " + utils.hexword(processor.pc)); - console.log("Previous PCs:"); - for (let i = 1; i < 16; ++i) { - console.log(" " + utils.hexword(processor.getPrevPc(i))); - } - console.log("A: " + utils.hexbyte(processor.a)); - console.log("X: " + utils.hexbyte(processor.x)); - console.log("Y: " + utils.hexbyte(processor.y)); - console.log("S: " + utils.hexbyte(processor.s)); - console.log("P: " + utils.hexbyte(processor.p.asByte()) + " " + processor.p.debugString()); - console.log( - utils.hd( - function (i) { - return processor.readmem(i); - }, - 0x00, - 0x40, - ), - ); - process.exit(1); -} - -let cpu6502 = fake6502(); -let test6502 = cpu6502 - .initialise() - .then(function () { - return runTest(cpu6502, "6502_functional_test", "6502"); - }) - .then(function (success) { - if (!success) fail(cpu6502); - }); - -let test65c12 = test6502.then(function () { - let cpu65c12 = fake65C12(); - return cpu65c12 - .initialise() - .then(function () { - return runTest(cpu65c12, "65C12_extended_opcodes_test", "65C12"); - }) - .then(function (success) { - if (!success) fail(cpu65c12); - }); -}); - -test65c12 - .then(function () { - console.log("Success!"); - }) - .catch(function (e) { - console.log("Exception in test: ", e); - process.exit(1); - });