This commit is contained in:
Ian Flanigan 2021-06-07 15:42:31 -07:00 committed by GitHub
commit 4f4f1427a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 32423 additions and 1 deletions

91
bin/bin2js Executable file
View File

@ -0,0 +1,91 @@
#!/usr/bin/env node
const { exit } = require('process');
const readFile = require('fs').promises.readFile;
const argv = require('yargs').argv;
const fileName = argv._[0];
const name = argv.n || argv.name;
const start = argv.s !== undefined ? argv.s : argv.start;
function toHex(v, n) {
if (!n) {
n = 2
}
let r = v.toString(16);
r = r.padStart(n, '0');
return r;
}
function usage(message) {
if (message) {
console.error(message);
}
console.error("bin2js -n name -s start binfile");
}
if (argv.h || argv.help) {
usage();
process.exit(0);
}
if (!name || !fileName || (typeof name != "string")) {
usage("must specify a file name for binfile");
process.exit(1);
}
if (start === "" || start === undefined) {
usage("must specify a start page");
process.exit(2);
}
if (typeof start === "string") {
if (start.startsWith("0x")) {
start = parseInt(start.slice(2), 16);
} else {
start = parseInt(start, 10);
}
}
readFile(fileName, { flag: 'r' }).then((fileData => {
if (fileData.length % 256 != 0) {
console.error(`${filename} length is not a multiple of 256`);
process.exit(2);
}
let end = start + fileData.length / 256 - 1;
console.log('const MEMORY = [');
const step = 0x08;
for (let i = 0; i < fileData.length; i += step) {
let line = ' ';
for (let j = i; j < fileData.length && j < i + step; j++) {
line += '0x' + toHex(fileData[j]) + ', ';
}
line += '// ' + toHex(i, 4);
console.log(line);
}
console.log('];');
console.log();
console.log(`export default function ${name}() {`);
console.log(' let mem = [...MEMORY];');
console.log(' return {');
console.log(' start: function() {');
console.log(` return 0x${toHex(start)};`);
console.log(' },');
console.log(' end: function() {');
console.log(` return 0x${toHex(end)};`);
console.log(' },');
console.log(' read: function(page, off) {');
console.log(` return mem[(page - 0x${toHex(start)}) << 8 | off];`);
console.log(' },');
console.log(' write: function(page, off, val) {');
console.log(` mem[(page - 0x${toHex(start)}) << 8 | off] = val;`);
console.log(' },');
console.log(' reset: function() {');
console.log(' mem = [...MEMORY];');
console.log(' },');
console.log(' };');
console.log('}');
})).catch((reason) => {
console.error('Unable to read binary file:', reason);
process.exit(1);
});

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>
<head>
<title>Benchmark for 6502</title>
<script async="false" type="module" src="/dist/cpu_benchmark.js"></script>
<script>
window.addEventListener('load', async (e) => {
await window.benchmark();
console.log('done');
});
</script>
<style>
table {
border-collapse: collapse;
empty-cells: hide;
}
tr.bottom-header {
border-bottom: 2px solid black;
}
th.right-header {
border-right: 2px solid black;
border-top: 1px solid gray;
}
td {
text-align: right;
border-top: 1px solid gray;
padding: 4px 10px;
}
</style>
</head>
<body>
<h1>Benchmark for 6502</h1>
<table id="benchmarks">
<thead>
<tr id="impl">
<th></th>
<th colspan="2">cpu6502.js</th>
</tr>
<tr class="bottom-header" id="emul">
<th></th>
<th>6502</th>
<th>65C02</th>
</tr>
</thead>
<tr id="min">
<th class="right-header">min</th>
<td id="min_js_6502"></td>
<td id="min_js_65C02"></td>
</tr>
<tr id="max">
<th class="right-header">max</th>
<td id="max_js_6502"></td>
<td id="max_js_65C02"></td>
</tr>
<tr id="mean">
<th class="right-header">mean</th>
<td id="mean_js_6502"></td>
<td id="mean_js_65C02"></td>
</tr>
<tr id="median">
<th class="right-header">median</th>
<td id="median_js_6502"></td>
<td id="median_js_65C02"></td>
</tr>
<tr id="runs">
<th class="right-header">runs</th>
<td id="runs_js_6502"></td>
<td id="runs_js_65C02"></td>
</tr>
</table>
</body>
</html>

239
test/perf/cpu_benchmark.js Normal file
View File

@ -0,0 +1,239 @@
import JSCPU6502 from './impl/jscpu6502';
import TSCPU6502 from './impl/tscpu6502';
import TSCPU6502v2 from './impl/tscpu6502v2';
import TSCPU6502v5 from './impl/tscpu6502v5';
import TSCPU6502v6 from './impl/tscpu6502v6';
import Test6502 from './test6502rom';
import Test65C02 from './test65c02rom';
let cpu;
let memory;
let done;
let lastPC;
let callbacks = 0;
function traceCB() {
let pc = cpu.getPC();
done = (lastPC == pc) || (pc < 0x100);
lastPC = pc;
callbacks++;
}
function setup6502(cpuType) {
cpu = new cpuType();
memory = new Test6502();
cpu.addPageHandler(memory);
}
function setup65C02(cpuType) {
cpu = new cpuType({ '65C02': true });
memory = new Test65C02();
cpu.addPageHandler(memory);
}
function runCPU(stopPC, maxCallbacks) {
done = false;
lastPC = 0x0000;
callbacks = 0;
cpu.reset();
memory.reset();
cpu.setPC(0x400);
do {
cpu.stepCyclesDebug(1000, traceCB);
} while (!done && callbacks <= maxCallbacks);
if (cpu.getPC() < 0x100) {
console.log('PC in zero page');
}
if (cpu.getPC() != stopPC) {
console.log(`stop PC incorrect: ${cpu.getPC().toString(16)} != ${stopPC.toString(16)}`);
}
if (callbacks > maxCallbacks) {
console.log('too many callbacks');
}
}
const tests = [
{
impl: 'cpu6502.js',
emul: '6502',
setup: () => setup6502(JSCPU6502),
test: () => runCPU(0x3469, 30648245),
},
{
impl: 'cpu6502.js',
emul: '65C02',
setup: () => setup65C02(JSCPU6502),
test: () => runCPU(0x24f1, 21987280),
},
{
impl: 'cpu6502.ts',
emul: '6502',
setup: () => setup6502(TSCPU6502),
test: () => runCPU(0x3469, 30648245),
},
{
impl: 'cpu6502.ts',
emul: '65C02',
setup: () => setup65C02(TSCPU6502),
test: () => runCPU(0x24f1, 21987280),
},
{
impl: 'cpu6502v2.ts',
emul: '6502',
setup: () => setup6502(TSCPU6502v2),
test: () => runCPU(0x3469, 30648245),
},
{
impl: 'cpu6502v2.ts',
emul: '65C02',
setup: () => setup65C02(TSCPU6502v2),
test: () => runCPU(0x24f1, 21987280),
},
{
impl: 'cpu6502v5.ts',
emul: '6502',
setup: () => setup6502(TSCPU6502v5),
test: () => runCPU(0x3469, 30648245),
},
// {
// impl: 'cpu6502v5.ts',
// emul: '6502',
// setup: () => setup65C02(TSCPU6502v5),
// test: () => runCPU(0x24f1, 21987280),
// },
{
impl: 'cpu6502v6.ts',
emul: '6502',
setup: () => setup6502(TSCPU6502v6),
test: () => runCPU(0x3469, 30648245),
},
{
impl: 'cpu6502v6.ts',
emul: '65C02',
setup: () => setup65C02(TSCPU6502v6),
test: () => runCPU(0x24f1, 21987280),
},
];
const IMPLS = [...new Set(tests.map((e) => e.impl))];
const RUNS = 50;
const WARMUP = 10;
function pause() {
return new Promise(resolve => setTimeout(resolve, 0));
}
function round(x, n) {
return Math.round(x * Math.pow(10, n)) / Math.pow(10, n);
}
function shuffle(/** @type Array<*> */ a) {
for (let i = a.length - 1; i > 1; i--) {
let j = Math.floor(Math.random() * (i + 1));
if (j !== i) {
let t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
function ensureSibling(
/** @type Element */ e,
/** @type function(): Element */ f) {
if (!e.nextElementSibling) {
e.parentElement.appendChild(f());
}
return e.nextElementSibling;
}
function clearSiblings(/** @type Element */ e) {
while (e.nextElementSibling) {
e.parentElement.removeChild(e.nextElementSibling);
}
}
function expandTable(
/** @type Element */ e,
/** @type string */ name,
/** @type number */ i) {
e = ensureSibling(e, () => document.createElement("td"));
e.setAttribute("id", name + "_" + i + "_6502");
e = ensureSibling(e, () => document.createElement("td"));
e.setAttribute("id", name + "_" + i + "_65C02");
return e;
}
function buildTable() {
let table = document.getElementById("benchmarks");
let implElement = document.getElementById("impl").firstElementChild;
let emulElement = document.getElementById("emul").firstElementChild;
let minElement = document.getElementById("min").firstElementChild;
let maxElement = document.getElementById("max").firstElementChild;
let meanElement = document.getElementById("mean").firstElementChild;
let medianElement = document.getElementById("median").firstElementChild;
let runsElement = document.getElementById("runs").firstElementChild;
for (let i = 0; i < IMPLS.length; i++) {
let impl = IMPLS[i];
implElement = ensureSibling(implElement, () => document.createElement("th"));
implElement.setAttribute("colspan", "2");
implElement.innerText = impl;
emulElement = ensureSibling(emulElement, () => document.createElement("th"));
emulElement.innerText = "6502";
emulElement = ensureSibling(emulElement, () => document.createElement("th"));
emulElement.innerText = "65C02";
minElement = expandTable(minElement, "min", i);
maxElement = expandTable(maxElement, "max", i);
meanElement = expandTable(meanElement, "mean", i);
medianElement = expandTable(medianElement, "median", i);
runsElement = expandTable(runsElement, "runs", i);
}
clearSiblings(implElement);
}
function name(test) {
return test.impl + " " + test.emul;
}
export async function benchmark() {
console.log('benchmark');
buildTable();
console.log('table built');
await pause();
let stats = {};
for (let i = 0; i < tests.length; i++) {
console.log('setting up', name(tests[i]));
tests[i].setup();
console.log('starting warmup of', name(tests[i]));
for (let w = 0; w < WARMUP; w++) {
tests[i].test();
await pause();
}
console.log('running ', name(tests[i]));
let runs = [];
for (let r = 0; r < RUNS; r++) {
let start = performance.now();
tests[i].test();
let end = performance.now();
let delta = round(end - start, 2);
runs.push(delta);
runs.sort((a, b) => a - b);
let id = IMPLS.indexOf(tests[i].impl) + "_" + tests[i].emul;
document.getElementById('min_' + id).innerText = runs[0];
document.getElementById('max_' + id).innerText = runs[runs.length - 1];
document.getElementById('median_' + id).innerText = runs[Math.floor(runs.length / 2)];
document.getElementById('mean_' + id).innerText = round(runs.reduce((a, b) => (a + b)) / runs.length, 2);
document.getElementById('runs_' + id).innerText = runs.length;
await pause();
}
console.log(`${name(tests[i])} runs = `, runs);
}
}
window.benchmark = benchmark;

1633
test/perf/impl/jscpu6502.js Normal file

File diff suppressed because it is too large Load Diff

1759
test/perf/impl/tscpu6502.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

8215
test/perf/test6502rom.js Normal file

File diff suppressed because it is too large Load Diff

8215
test/perf/test65c02rom.js Normal file

File diff suppressed because it is too large Load Diff

2240
test/tscpu6502.spec.js Normal file

File diff suppressed because it is too large Load Diff

2240
test/tscpu6502v2.spec.js Normal file

File diff suppressed because it is too large Load Diff

2240
test/tscpu6502v6.spec.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,8 @@ module.exports =
mode: 'development',
entry: {
main2: path.resolve('js/entry2.js'),
main2e: path.resolve('js/entry2e.js')
main2e: path.resolve('js/entry2e.js'),
cpu_benchmark: path.resolve('test/perf/cpu_benchmark.js')
},
output: {
path: path.resolve('dist/'),