mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
import assert from "assert";
|
|
import { describe, it } from "mocha";
|
|
import {
|
|
BaseDebugPlatform,
|
|
CpuState,
|
|
EmuState,
|
|
} from "../../src/common/baseplatform";
|
|
import { disassemble6502 } from "../../src/common/cpu/disasm6502";
|
|
|
|
// Minimal 6502-ish platform for exercising BaseDebugPlatform.stepOver().
|
|
// readAddress() relies on 'this' like real platforms (e.g. c64.ts), so it
|
|
// catches bugs where the method reference is detached from its object.
|
|
class TestStepPlatform extends BaseDebugPlatform {
|
|
mem = new Uint8Array(0x10000);
|
|
cpu = { PC: 0, SP: 0xff };
|
|
paused = false;
|
|
|
|
getPC(): number { return this.cpu.PC; }
|
|
getSP(): number { return this.cpu.SP; }
|
|
getCPUState(): CpuState { return { PC: this.cpu.PC, SP: this.cpu.SP }; }
|
|
isStable(): boolean { return true; }
|
|
// uses 'this' -- must not be called unbound
|
|
readAddress(a: number): number { return this.mem[a & 0xffff]; }
|
|
// like real platforms, goes through the passed-in read callback
|
|
disassemble(pc: number, read: (a: number) => number) {
|
|
return disassemble6502(pc, read(pc), read(pc + 1), read(pc + 2));
|
|
}
|
|
saveState(): EmuState { return { c: { PC: this.cpu.PC, SP: this.cpu.SP } }; }
|
|
loadState(state: EmuState) { this.cpu.PC = state.c.PC; this.cpu.SP = state.c.SP; }
|
|
pause() { this.paused = true; }
|
|
// like a real platform, resuming starts the emulation loop running
|
|
// until the next breakpoint
|
|
resume() { this.paused = false; this.advance(); }
|
|
isRunning() { return !this.paused; }
|
|
|
|
advance(novideo?: boolean): number {
|
|
// execute instructions until breakpoint hit or budget exceeded
|
|
var n = 0;
|
|
while (!this.wasBreakpointHit() && n < 1000) {
|
|
this.execInstruction();
|
|
n++;
|
|
this.debugClock++;
|
|
this.evalDebugCondition();
|
|
}
|
|
return n;
|
|
}
|
|
|
|
execInstruction() {
|
|
var op = this.readAddress(this.cpu.PC);
|
|
switch (op) {
|
|
case 0x20: { // JSR abs
|
|
var target = this.readAddress(this.cpu.PC + 1) | (this.readAddress(this.cpu.PC + 2) << 8);
|
|
var ret = (this.cpu.PC + 2) & 0xffff;
|
|
this.push((ret >> 8) & 0xff);
|
|
this.push(ret & 0xff);
|
|
this.cpu.PC = target;
|
|
break;
|
|
}
|
|
case 0x60: { // RTS
|
|
var lo = this.pop();
|
|
var hi = this.pop();
|
|
this.cpu.PC = ((lo | (hi << 8)) + 1) & 0xffff;
|
|
break;
|
|
}
|
|
default: // NOP and anything else: treat as 1-byte instruction
|
|
this.cpu.PC = (this.cpu.PC + 1) & 0xffff;
|
|
break;
|
|
}
|
|
}
|
|
|
|
push(b: number) {
|
|
this.mem[0x100 + this.cpu.SP] = b & 0xff;
|
|
this.cpu.SP = (this.cpu.SP - 1) & 0xff;
|
|
}
|
|
pop(): number {
|
|
this.cpu.SP = (this.cpu.SP + 1) & 0xff;
|
|
return this.mem[0x100 + this.cpu.SP];
|
|
}
|
|
}
|
|
|
|
describe('BaseDebugPlatform stepOver', function () {
|
|
|
|
function makePlatform(): TestStepPlatform {
|
|
var p = new TestStepPlatform();
|
|
// JSR $0005 ; NOP ; BRK
|
|
p.mem.set([0x20, 0x05, 0x00, 0xEA, 0x00], 0x0000);
|
|
// subroutine at $0005: LDA #$01 ; RTS
|
|
p.mem.set([0xA9, 0x01, 0x60], 0x0005);
|
|
p.cpu.PC = 0x0000;
|
|
return p;
|
|
}
|
|
|
|
it('should step over a JSR to the following instruction', function () {
|
|
var p = makePlatform();
|
|
var sp0 = p.getSP();
|
|
p.stepOver(); // runs the runEval loop via advance()
|
|
assert.strictEqual(p.getPC(), 0x0003); // not inside the subroutine
|
|
assert.strictEqual(p.getSP(), sp0); // subroutine returned
|
|
});
|
|
|
|
it('should single-step when instruction is not a call', function () {
|
|
var p = makePlatform();
|
|
p.cpu.PC = 0x0003; // NOP
|
|
p.stepOver();
|
|
assert.strictEqual(p.getPC(), 0x0004);
|
|
});
|
|
|
|
it('should detect iscall on JSR opcode', function () {
|
|
var p = makePlatform();
|
|
var d = p.disassemble(0x0000, (a) => p.readAddress(a));
|
|
assert.strictEqual(d.iscall, true);
|
|
assert.strictEqual(d.nbytes, 3);
|
|
});
|
|
|
|
});
|