Improve debug performance, abstract debugger. (#61)

Stop stringifying opcodes during runtime and only do so upon inspection. Moves all the debugging logic to a common place to allow building an interface.
This commit is contained in:
Will Scullin 2021-03-11 22:03:05 -08:00 committed by GitHub
parent 5f0d07623d
commit bb09a1ec33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 554 additions and 343 deletions

View File

@ -41,6 +41,7 @@
],
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": ["error"],
"no-dupe-class-members": "off",
"no-console": [
"error",
{

View File

@ -20,9 +20,10 @@ import { Apple2IOState } from './apple2io';
import CPU6502, { CpuState } from './cpu6502';
import MMU, { MMUState } from './mmu';
import RAM, { RAMState } from './ram';
import { debug } from './util';
import SYMBOLS from './symbols';
import Debugger, { DebuggerContainer } from './debugger';
import { Restorable, rom } from './types';
import { processGamepad } from './ui/gamepad';
@ -44,13 +45,10 @@ interface State {
ram?: RAMState[],
}
export class Apple2 implements Restorable<State> {
export class Apple2 implements Restorable<State>, DebuggerContainer {
private paused = false;
private DEBUG = false;
private TRACE = false;
private MAX_TRACE = 256;
private trace: string[] = [];
private theDebugger?: Debugger;
private runTimer: number | null = null;
private runAnimationFrame: number | null = null;
@ -120,6 +118,9 @@ export class Apple2 implements Restorable<State> {
return; // already running
}
this.theDebugger = new Debugger(this);
this.theDebugger.addSymbols(SYMBOLS);
const interval = 30;
let now, last = Date.now();
@ -134,19 +135,8 @@ export class Apple2 implements Restorable<State> {
step = stepMax;
}
if (this.DEBUG) {
this.cpu.stepCyclesDebug(this.TRACE ? 1 : step, () => {
const line = this.cpu.dumpRegisters() + ' ' +
this.cpu.dumpPC(undefined, SYMBOLS);
if (this.TRACE) {
debug(line);
} else {
this.trace.push(line);
if (this.trace.length > this.MAX_TRACE) {
this.trace.shift();
}
}
});
if (this.theDebugger) {
this.theDebugger.stepCycles(step);
} else {
this.cpu.stepCycles(step);
}
@ -234,7 +224,16 @@ export class Apple2 implements Restorable<State> {
return this.io;
}
getMMU() {
return this.mmu;
}
getVideoModes() {
return this.vm;
}
getDebugger() {
return this.theDebugger;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
* Copyright 2010-2021 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
@ -13,8 +13,6 @@
import { Memory, MemoryPages, byte, word } from './types';
import { debug, toHex } from './util';
type symbols = { [key: number]: string };
export interface CpuOptions {
'65C02'?: boolean;
}
@ -29,7 +27,7 @@ export interface CpuState {
cycles: number
}
type Mode =
export type Mode =
'accumulator' | // A (Accumulator)
'implied' | // Implied
'immediate' | // # Immediate
@ -47,10 +45,10 @@ type Mode =
'absoluteXIndirect' | // (a, X),
'zeroPage_relative'; // zp, Relative
type Modes = Record<Mode, number>;
export type Modes = Record<Mode, number>;
/** Addressing mode name to instruction size mapping. */
const sizes: Modes = {
export const sizes: Modes = {
accumulator: 1,
implied: 1,
immediate: 2,
@ -74,10 +72,20 @@ const sizes: Modes = {
};
/** Status register flag numbers. */
type flag = 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01;
export type flag = 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01;
export type DebugInfo = {
pc: word,
ar: byte,
xr: byte,
yr: byte,
sr: byte,
sp: byte,
cmd: byte[],
};
/** Flags to status byte mask. */
const flags: { [key: string]: flag } = {
export const flags: { [key: string]: flag } = {
N: 0x80, // Negative
V: 0x40, // oVerflow
B: 0x10, // Break
@ -141,12 +149,12 @@ export default class CPU6502 {
private readonly is65C02: boolean;
/* Registers */
private pc = 0; // Program Counter
private sr = 0x20; // Process Status Register
private ar = 0; // Accumulator
private xr = 0; // X Register
private yr = 0; // Y Register
private sp = 0xff; // Stack Pointer
private pc: word = 0; // Program Counter
private sr: byte = 0x20; // Process Status Register
private ar: byte = 0; // Accumulator
private xr: byte = 0; // X Register
private yr: byte = 0; // Y Register
private sp: byte = 0xff; // Stack Pointer
private memPages: Memory[] = new Array(0x100);
private resetHandlers: ResettablePageHandler[] = [];
@ -294,10 +302,6 @@ export default class CPU6502 {
return this.readByte(addr) | (this.readByte(addr + 1) << 8);
}
private readWordDebug(addr: word): word {
return this.readByteDebug(addr) | (this.readByteDebug(addr + 1) << 8);
}
private readWordPC(): word {
return this.readBytePC() | (this.readBytePC() << 8);
}
@ -991,105 +995,24 @@ export default class CPU6502 {
return unk;
}
private dumpArgs(addr: word, m: Mode, symbols: symbols) {
function toHexOrSymbol(v: word, n?: number) {
if (symbols && symbols[v]) {
return symbols[v];
} else {
return '$' + toHex(v, n);
}
}
let off, val;
let result = '';
switch (m) {
case 'implied':
break;
case 'immediate':
result = '#' + toHexOrSymbol(this.readByteDebug(addr));
break;
case 'absolute':
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4);
break;
case 'zeroPage':
result = '' + toHexOrSymbol(this.readByteDebug(addr));
break;
case 'relative':
{
let off = this.readByteDebug(addr);
if (off > 127) {
off -= 256;
}
addr += off + 1;
result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
}
break;
case 'absoluteX':
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X';
break;
case 'absoluteY':
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',Y';
break;
case 'zeroPageX':
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',X';
break;
case 'zeroPageY':
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',Y';
break;
case 'absoluteIndirect':
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ')';
break;
case 'zeroPageXIndirect':
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ',X)';
break;
case 'zeroPageIndirectY':
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + '),Y';
break;
case 'accumulator':
result = 'A';
break;
case 'zeroPageIndirect':
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ')';
break;
case 'absoluteXIndirect':
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X)';
break;
case 'zeroPage_relative':
val = this.readByteDebug(addr);
off = this.readByteDebug(addr + 1);
if (off > 127) {
off -= 256;
}
addr += off + 2;
result = '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
break;
default:
break;
}
return result;
}
public step(cb: callback) {
public step(cb?: callback) {
this.sync = true;
const op = this.opary[this.readBytePC()];
this.sync = false;
op.op(op.modeFn);
if (cb) {
cb(this);
}
cb?.(this);
}
public stepDebug(n: number, cb: callback) {
public stepN(n: number, cb?: callback) {
for (let idx = 0; idx < n; idx++) {
this.sync = true;
const op = this.opary[this.readBytePC()];
this.sync = false;
op.op(op.modeFn);
if (cb) {
cb(this);
}
cb?.(this);
}
}
@ -1104,7 +1027,7 @@ export default class CPU6502 {
}
}
public stepCyclesDebug(c: number, cb: callback): void {
public stepCyclesDebug(c: number, cb?: callback): void {
const end = this.cycles + c;
while (this.cycles < end) {
@ -1113,9 +1036,7 @@ export default class CPU6502 {
this.sync = false;
op.op(op.modeFn);
if (cb) {
cb(this);
}
cb?.(this);
}
}
@ -1173,83 +1094,26 @@ export default class CPU6502 {
this.pc = pc;
}
public dumpPC(pc: word | undefined, symbols: symbols) {
if (pc === undefined) {
pc = this.pc;
}
const b = this.readByteDebug(pc),
op = this.opary[b],
size = sizes[op.mode];
let result = toHex(pc, 4) + '- ';
if (symbols) {
if (symbols[pc]) {
result += symbols[pc] +
' '.substring(symbols[pc].length);
} else {
result += ' ';
}
public getDebugInfo(): DebugInfo {
const b = this.readByteDebug(this.pc);
const op = this.opary[b];
const size = sizes[op.mode];
const cmd = new Array(size);
cmd[0] = b;
for (let idx = 1; idx < size; idx++) {
cmd[idx] = this.readByteDebug(this.pc + idx);
}
for (let idx = 0; idx < 4; idx++) {
if (idx < size) {
result += toHex(this.readByteDebug(pc + idx)) + ' ';
} else {
result += ' ';
}
}
if (op === undefined)
result += '??? (' + toHex(b) + ')';
else
result += op.name + ' ' + this.dumpArgs(pc + 1, op.mode, symbols);
return result;
return {
pc: this.pc,
ar: this.ar,
xr: this.xr,
yr: this.yr,
sr: this.sr,
sp: this.sp,
cmd
};
}
public dumpPage(start?: word, end?: word) {
let result = '';
if (start === undefined) {
start = this.pc >> 8;
}
if (end === undefined) {
end = start;
}
for (let page = start; page <= end; page++) {
for (let idx = 0; idx < 16; idx++) {
result += toHex(page) + toHex(idx << 4) + ': ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.readByteDebug(page * 256 + idx * 16 + jdx);
result += toHex(b) + ' ';
}
result += ' ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.readByte(page * 256 + idx * 16 + jdx) & 0x7f;
if (b >= 0x20 && b < 0x7f) {
result += String.fromCharCode(b);
} else {
result += '.';
}
}
result += '\n';
}
}
return result;
}
public list(_pc: word, symbols: symbols) {
if (_pc === undefined) {
_pc = this.pc;
}
const results = [];
for (let jdx = 0; jdx < 20; jdx++) {
const b = this.readByte(_pc), op = this.opary[b];
results.push(this.dumpPC(_pc, symbols));
_pc += sizes[op.mode];
}
return results;
}
public getSync() {
return this.sync;
}
@ -1258,8 +1122,8 @@ export default class CPU6502 {
return this.cycles;
}
public registers() {
return [this.pc, this.ar, this.xr, this.yr, this.sr, this.sp];
public getOpInfo(opcode: byte) {
return this.opary[opcode];
}
public getState(): CpuState {
@ -1284,29 +1148,36 @@ export default class CPU6502 {
this.cycles = state.cycles;
}
public dumpRegisters() {
return toHex(this.pc, 4) +
'- A=' + toHex(this.ar) +
' X=' + toHex(this.xr) +
' Y=' + toHex(this.yr) +
' P=' + toHex(this.sr) +
' S=' + toHex(this.sp) +
' ' +
((this.sr & flags.N) ? 'N' : '-') +
((this.sr & flags.V) ? 'V' : '-') +
'-' +
((this.sr & flags.B) ? 'B' : '-') +
((this.sr & flags.D) ? 'D' : '-') +
((this.sr & flags.I) ? 'I' : '-') +
((this.sr & flags.Z) ? 'Z' : '-') +
((this.sr & flags.C) ? 'C' : '-');
}
public read(addr: word): byte;
public read(page: byte, off: byte): byte;
public read(page: byte, off: byte): byte {
public read(a: number, b?: number): byte {
let page, off;
if (b !== undefined) {
page = a;
off = b;
} else {
page = a >> 8;
off = a & 0xff;
}
return this.memPages[page].read(page, off);
}
public write(page: byte, off: byte, val: byte) {
public write(addr: word, val: byte): void;
public write(page: byte, off: byte, val: byte): void;
public write(a: number, b: number, c?: byte): void {
let page, off, val;
if (c !== undefined ) {
page = a;
off = b;
val = c;
} else {
page = a >> 8;
off = a & 0xff;
val = b;
}
this.memPages[page].write(page, off, val);
}

284
js/debugger.ts Normal file
View File

@ -0,0 +1,284 @@
import { debug, toHex } from './util';
import { byte, word } from './types';
import CPU6502, { DebugInfo, flags, sizes } from './cpu6502';
export interface DebuggerContainer {
run: () => void;
stop: () => void;
getCPU: () => CPU6502;
}
type symbols = { [key: number]: string };
type breakpointFn = (info: DebugInfo) => boolean
const alwaysBreak = (_info: DebugInfo) => { return true; };
export default class Debugger {
private cpu: CPU6502;
private verbose = false;
private maxTrace = 256;
private trace: DebugInfo[] = [];
private breakpoints: Map<word, breakpointFn> = new Map();
private symbols: symbols = {};
constructor(private container: DebuggerContainer) {
this.cpu = container.getCPU();
}
stepCycles(cycles: number) {
this.cpu.stepCyclesDebug(this.verbose ? 1 : cycles, () => {
const info = this.cpu.getDebugInfo();
if (this.breakpoints.get(info.pc)?.(info)) {
debug('breakpoint', this.printDebugInfo(info));
this.container.stop();
return;
}
if (this.verbose) {
debug(this.printDebugInfo(info));
} else {
this.trace.push(info);
if (this.trace.length > this.maxTrace) {
this.trace.shift();
}
}
});
}
continue() {
this.container.run();
}
setVerbose(verbose: boolean) {
this.verbose = verbose;
}
setMaxTrace(maxTrace: number) {
this.maxTrace = maxTrace;
}
getTrace() {
return this.trace.map(this.printDebugInfo).join('\n');
}
printTrace() {
debug(this.getTrace());
}
setBreakpoint(addr: word, exp?: breakpointFn) {
this.breakpoints.set(addr, exp || alwaysBreak);
}
clearBreakpoint(addr: word) {
this.breakpoints.delete(addr);
}
listBreakpoints() {
for(const [addr, fn] of this.breakpoints.entries()) {
debug(toHex(addr, 4), fn);
}
}
addSymbols(symbols: symbols) {
this.symbols = { ...this.symbols, ...symbols };
}
printDebugInfo(info: DebugInfo) {
const { pc, cmd } = info;
const symbol = this.padWithSymbol(pc);
return [
toHex(pc, 4),
'- ', symbol,
this.dumpRegisters(info),
' ',
this.dumpRawOp(cmd),
' ',
this.dumpOp(pc, cmd)
].join('');
}
dumpPC(pc: word) {
const b = this.cpu.read(pc);
const op = this.cpu.getOpInfo(b);
const size = sizes[op.mode];
let result = toHex(pc, 4) + '- ';
result += this.padWithSymbol(pc);
const cmd = new Array(size);
for (let idx = 0, jdx = pc; idx < size; idx++, jdx++) {
cmd[idx] = this.cpu.read(jdx);
}
result += this.dumpRawOp(cmd) + ' ' + this.dumpOp(pc, cmd);
return result;
}
dumpRegisters(debugInfo?: DebugInfo) {
if (debugInfo === undefined) {
debugInfo = this.cpu.getDebugInfo();
}
const { pc, ar, xr, yr, sr, sp } = debugInfo;
return [
toHex(pc, 4),
'- ',
' A=' + toHex(ar),
' X=' + toHex(xr),
' Y=' + toHex(yr),
' P=' + toHex(sr),
' S=' + toHex(sp),
' ',
((sr & flags.N) ? 'N' : '-'),
((sr & flags.V) ? 'V' : '-'),
'-',
((sr & flags.B) ? 'B' : '-'),
((sr & flags.D) ? 'D' : '-'),
((sr & flags.I) ? 'I' : '-'),
((sr & flags.Z) ? 'Z' : '-'),
((sr & flags.C) ? 'C' : '-')
].join('');
}
dumpPage(start: byte, end?: byte) {
let result = '';
if (end === undefined) {
end = start;
}
for (let page = start; page <= end; page++) {
for (let idx = 0; idx < 16; idx++) {
result += toHex(page) + toHex(idx << 4) + ': ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.cpu.read(page, idx * 16 + jdx);
result += toHex(b) + ' ';
}
result += ' ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.cpu.read(page, idx * 16 + jdx) & 0x7f;
if (b >= 0x20 && b < 0x7f) {
result += String.fromCharCode(b);
} else {
result += '.';
}
}
result += '\n';
}
}
return result;
}
list(pc: word) {
const results = [];
for (let idx = 0; idx < 20; idx++) {
const b = this.cpu.read(pc);
const op = this.cpu.getOpInfo(b);
results.push(this.dumpPC(pc));
pc += sizes[op.mode];
}
return results;
}
private padWithSymbol(pc: word): string {
const padding = ' ';
const symbol = this.symbols[pc];
let result: string = padding;
if (symbol) {
result = `${symbol}${padding.substring(symbol.length)}`;
}
return result;
}
private dumpRawOp(parts: byte[]) {
const result = new Array(4);
for (let idx = 0; idx < 4; idx++) {
if (idx < parts.length) {
result[idx] = toHex(parts[idx]);
} else {
result[idx] = ' ';
}
}
return result.join(' ');
}
private dumpOp(pc: word, parts: byte[]) {
const op = this.cpu.getOpInfo(parts[0]);
const lsb = parts[1];
const msb = parts[2];
const addr = (msb << 8) | lsb;
let val;
let off;
const toHexOrSymbol = (v: word, n?: number) => (
this.symbols[v] || ('$' + toHex(v, n))
);
let result = op.name + ' ';
switch (op.mode) {
case 'implied':
break;
case 'immediate':
result += '#' + toHexOrSymbol(lsb);
break;
case 'absolute':
result += '' + toHexOrSymbol(addr, 4);
break;
case 'zeroPage':
result += '' + toHexOrSymbol(lsb);
break;
case 'relative':
{
off = lsb;
if (off > 127) {
off -= 256;
}
pc += off + 1;
result += '' + toHexOrSymbol(pc, 4) + ' (' + off + ')';
}
break;
case 'absoluteX':
result += '' + toHexOrSymbol(addr, 4)+ ',X';
break;
case 'absoluteY':
result += '' + toHexOrSymbol(addr, 4) + ',Y';
break;
case 'zeroPageX':
result += '' + toHexOrSymbol(lsb) + ',X';
break;
case 'zeroPageY':
result += '' + toHexOrSymbol(lsb) + ',Y';
break;
case 'absoluteIndirect':
result += '(' + toHexOrSymbol(addr, 4) + ')';
break;
case 'zeroPageXIndirect':
result += '(' + toHexOrSymbol(lsb) + ',X)';
break;
case 'zeroPageIndirectY':
result += '(' + toHexOrSymbol(lsb) + '),Y';
break;
case 'accumulator':
result += 'A';
break;
case 'zeroPageIndirect':
result += '(' + toHexOrSymbol(lsb) + ')';
break;
case 'absoluteXIndirect':
result += '(' + toHexOrSymbol(addr, 4) + ',X)';
break;
case 'zeroPage_relative':
val = lsb;
off = msb;
if (off > 127) {
off -= 256;
}
pc += off + 2;
result += '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(pc, 4) + ' (' + off + ')';
break;
default:
break;
}
return result;
}
}

View File

@ -70,7 +70,7 @@ var options = {
tick: updateUI
};
var apple2 = new Apple2(options);
export var apple2 = new Apple2(options);
var cpu = apple2.getCPU();
var io = apple2.getIO();

View File

@ -52,7 +52,7 @@ var options = {
tick: updateUI
};
var apple2 = new Apple2(options);
export var apple2 = new Apple2(options);
var io = apple2.getIO();
var cpu = apple2.getCPU();

View File

@ -172,9 +172,9 @@ export interface MMUState {
}
export default class MMU implements Memory, Restorable<MMUState> {
private _readPages = new Array(0x100);
private _writePages = new Array(0x100);
private _pages = new Array(0x100);
private _readPages = new Array<Memory>(0x100);
private _writePages = new Array<Memory>(0x100);
private _pages = new Array<Memory[]>(0x100);
// Language Card RAM Softswitches
private _bank1: boolean;
@ -833,6 +833,10 @@ export default class MMU implements Memory, Restorable<MMUState> {
this._writePages[page].write(page, off, val);
}
public writeBank(bank: number,page: byte, off: byte, val: byte) {
this._pages[page][bank].write(page, off, val);
}
public resetVB() {
this._vbEnd = this.cpu.getCycles() + 1000;
}

View File

@ -1,96 +1,6 @@
import CPU6502 from '../js/cpu6502';
function assertByte(b) {
expect(b <= 0xFF).toEqual(true);
expect(b >= 0x00).toEqual(true);
}
function Memory(size) {
var data = Buffer.alloc(size << 8);
return {
start: function() {
return 0;
},
end: function() {
return size - 1;
},
read: function(page, off) {
assertByte(page);
assertByte(off);
return data[(page << 8) | off];
},
write: function(page, off, val) {
assertByte(page);
assertByte(off);
assertByte(val);
data[(page << 8) | off] = val;
},
reset: function() {
}
};
}
function Program(page, code) {
var data = Buffer.from(code);
return {
start: function() {
return page;
},
end: function() {
return page;
},
read: function(page, off) {
assertByte(page);
assertByte(off);
return data[off];
}
};
}
var bios = new Program(0xff, [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x0D, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, 0xff
]);
import { TestMemory } from './util/memory';
import { bios, Program } from './util/bios';
var FLAGS = {
N: 0x80, // Negative
@ -178,14 +88,14 @@ function testCode(code, steps, setupState, expectedState) {
cpu.addPageHandler(program);
cpu.setState(initialState);
cpu.stepDebug(steps);
cpu.stepN(steps);
expectState(initialState, finalState);
}
describe('CPU6502', function() {
beforeEach(function() {
cpu = new CPU6502();
memory = new Memory(4);
memory = new TestMemory(4);
cpu.addPageHandler(memory);
cpu.addPageHandler(bios);
@ -1516,34 +1426,12 @@ describe('CPU6502', function() {
});
});
});
describe('#utility', function() {
it('should list', function() {
var listing = cpu.list(0xff00);
expect(listing[0]).toEqual('FF00- 00 00 BRK #$00');
});
it('should list with symbols', function() {
var listing = cpu.list(0xff00, {0x00: 'ZERO', 0xFF00: 'ENTRY'});
expect(listing[0]).toEqual('FF00- ENTRY 00 00 BRK #ZERO');
});
it('should dump page', function() {
var page = cpu.dumpPage(0xff);
expect(page).toContain('FF80: 48 45 4C 4C 4F 0D 00 00 00 00 00 00 00 00 00 00 HELLO...........');
});
it('should dump registers', function() {
var regs = cpu.dumpRegisters();
expect(regs).toEqual('0000- A=00 X=00 Y=00 P=20 S=FF --------');
});
});
});
describe('65c02', function() {
beforeEach(function() {
cpu = new CPU6502({'65C02': true});
memory = new Memory(4);
memory = new TestMemory(4);
cpu.addPageHandler(memory);
cpu.addPageHandler(bios);

59
test/js/debugger.spec.ts Normal file
View File

@ -0,0 +1,59 @@
import CPU6502 from '../../js/cpu6502';
import Debugger, { DebuggerContainer } from '../../js/debugger';
import { MemoryPages } from '../../js/types';
import { TestMemory } from '../util/memory';
import { bios } from '../util/bios';
describe('Debugger', () => {
let cpu: CPU6502;
let debuggerContainer: DebuggerContainer;
let theDebugger: Debugger;
let memory: MemoryPages;
beforeEach(() => {
cpu = new CPU6502();
memory = new TestMemory(4);
cpu.addPageHandler(memory);
cpu.addPageHandler(bios);
debuggerContainer = {
getCPU: () => cpu,
run: jest.fn(),
stop: jest.fn(),
};
theDebugger = new Debugger(debuggerContainer);
});
describe('#utility', () => {
it('should list without symbols', () => {
const listing = theDebugger.list(0xff00);
expect(listing[0]).toEqual(
'FF00- 00 00 BRK #$00'
);
});
it('should list with symbols', () => {
theDebugger.addSymbols({0x00: 'ZERO', 0xFF00: 'ENTRY'});
const listing = theDebugger.list(0xff00);
expect(listing[0]).toEqual(
'FF00- ENTRY 00 00 BRK #ZERO'
);
});
it('should dump page', () => {
const page = theDebugger.dumpPage(0xff);
expect(page).toContain(
'FF80: 48 45 4C 4C 4F 0D 00 00 00 00 00 00 00 00 00 00 HELLO...........'
);
});
it('should dump registers', () => {
const regs = theDebugger.dumpRegisters();
expect(regs).toEqual(
'0000- A=00 X=00 Y=00 P=20 S=FF --------'
);
});
});
});

6
test/util/asserts.ts Normal file
View File

@ -0,0 +1,6 @@
import { byte } from '../../js/types';
export const assertByte = (b: byte) => {
expect(b <= 0xFF).toEqual(true);
expect(b >= 0x00).toEqual(true);
};

62
test/util/bios.ts Normal file
View File

@ -0,0 +1,62 @@
import { MemoryPages, byte } from '../../js/types';
import { assertByte } from './asserts';
export class Program implements MemoryPages {
private data: Buffer;
constructor(private page: byte, code: byte[]) {
this.data = Buffer.from(code);
}
start() {
return this.page;
}
end() {
return this.page;
}
read(page: byte, off: byte) {
assertByte(page);
assertByte(off);
return this.data[off];
}
write(_page: byte, _off: byte, _val: byte) {
}
}
export const bios = new Program(0xff, [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x0D, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, 0xff
]);

37
test/util/memory.ts Normal file
View File

@ -0,0 +1,37 @@
import { MemoryPages, byte } from '../../js/types';
import { assertByte } from './asserts';
export class TestMemory implements MemoryPages {
private data: Buffer;
constructor(private size: number) {
this.data = Buffer.alloc(size << 8);
}
start() {
return 0;
}
end() {
return this.size - 1;
}
read(page: byte, off: byte) {
assertByte(page);
assertByte(off);
return this.data[(page << 8) | off];
}
write(page: byte, off: byte, val: byte) {
assertByte(page);
assertByte(off);
assertByte(val);
this.data[(page << 8) | off] = val;
}
reset() {
}
}