Skip to content

Commit

Permalink
Implement 65c02 and use it on Tube (#434)
Browse files Browse the repository at this point in the history
- Support the various 65c02 instructions
- Moves dormann tests into integration
- Use 65c02 in Tubes

Closes #298
  • Loading branch information
mattgodbolt authored Sep 11, 2024
1 parent 1d2b8fc commit fd69c20
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 131 deletions.
3 changes: 1 addition & 2 deletions 6502.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
Expand Down
105 changes: 101 additions & 4 deletions 6502.opcodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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":
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}`);
}
}

Expand Down Expand Up @@ -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);
}
9 changes: 7 additions & 2 deletions fake6502.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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);
}
53 changes: 40 additions & 13 deletions models.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,55 @@

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;
this.isTest = false;
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;
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -111,7 +136,7 @@ export const allModels = [
"BBC Master 128 (DFS)",
["Master"],
["master/mos3.20"],
false,
CpuModel.CMOS65C12,
true,
masterSwram,
NoiseAwareWdFdc,
Expand All @@ -122,7 +147,7 @@ export const allModels = [
"BBC Master 128 (ADFS)",
["MasterADFS"],
["master/mos3.20"],
false,
CpuModel.CMOS65C12,
true,
masterSwram,
NoiseAwareWdFdc,
Expand All @@ -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) {
Expand All @@ -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,
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit fd69c20

Please sign in to comment.