made BaseMachinePlatform, test with NewApple2Platform (work on debugging)

This commit is contained in:
Steven Hugg 2019-08-23 10:37:30 -04:00
parent 30db326f57
commit 20bc3620ac
8 changed files with 255 additions and 1158 deletions

View File

@ -27,6 +27,7 @@ export interface CpuState {
export interface EmuState {
c?:CpuState, // CPU state
b?:Uint8Array|number[], // RAM (TODO: not for vcs, support Uint8Array)
ram?:Uint8Array,
o?:{}, // verilog
T?:number, // verilog
};
@ -95,6 +96,7 @@ export interface Platform {
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
getSP?() : number;
getPC?() : number;
getOriginPC?() : number;
newCodeAnalyzer?() : CodeAnalyzer;
@ -413,8 +415,8 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
getDebugInfo(category:string, state:EmuState) : string {
switch (category) {
case 'CPU': return cpuStateToLongString_6502(state.c);
case 'ZPRAM': return dumpRAM(state.b, 0x0, 0x100);
case 'Stack': return dumpStackToString(<Platform><any>this, state.b, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
}
}
}
@ -1185,3 +1187,174 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
this.cpu.setTstates(0);
}
}
/// new style
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsInput, SavesState, HasCPU } from "./devices";
import { SampledAudio } from "./audio";
interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState> {
}
function hasVideo(arg:any): arg is VideoSource {
return typeof arg.connectVideo === 'function';
}
function hasAudio(arg:any): arg is SampledAudioSource {
return typeof arg.connectAudio === 'function';
}
function hasInput<CS>(arg:any): arg is AcceptsInput<CS> {
return typeof arg.setInput === 'function';
}
export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPlatform implements Platform {
machine : T;
mainElement : HTMLElement;
timer : AnimationTimer;
video : RasterVideo;
audio : SampledAudio;
abstract newMachine() : T;
abstract getToolForFilename(s:string) : string;
abstract getDefaultExtension() : string;
abstract getPresets() : Preset[];
constructor(mainElement : HTMLElement) {
super();
this.mainElement = mainElement;
this.machine = this.newMachine();
}
reset() { this.machine.reset(); }
loadState(s) { this.machine.loadState(s); }
saveState() { return this.machine.saveState(); }
getSP() { return this.machine.cpu.getSP(); }
getPC() { return this.machine.cpu.getPC(); }
getCPUState() { return this.machine.cpu.saveState(); }
loadControlsState(s) { if (hasInput(this.machine)) this.machine.loadControlsState(s); }
saveControlsState() { return hasInput(this.machine) && this.machine.saveControlsState(); }
start() {
var m = this.machine;
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
if (hasVideo(m)) {
var vp = m.getVideoParams();
this.video = new RasterVideo(this.mainElement, vp.width, vp.height);
this.video.create();
m.connectVideo(this.video.getFrameData());
}
if (hasAudio(m)) {
var ap = m.getAudioParams();
this.audio = new SampledAudio(ap.sampleRate);
this.audio.start();
m.connectAudio(this.audio);
}
if (hasInput(m)) {
this.video.setKeyboardEvents(m.setInput.bind(m));
// TODO: ControllerPoller
}
}
loadROM(title, data) {
this.machine.loadROM(data);
this.reset();
}
advance(novideo:boolean) {
this.machine.advanceFrame(999999, this.getDebugCallback());
if (!novideo) this.video.updateFrame();
}
isRunning() {
return this.timer.isRunning();
}
resume() {
this.timer.start();
this.audio && this.audio.start();
}
pause() {
this.timer.stop();
this.audio && this.audio.stop();
}
// TODO
breakpointHit(targetClock : number) {
console.log(this.debugTargetClock, targetClock, this.debugClock, this.machine.cpu.isStable());
this.debugTargetClock = targetClock;
this.debugBreakState = this.saveState();
console.log("Breakpoint at clk", this.debugClock, "PC", this.debugBreakState.c.PC.toString(16));
this.pause();
if (this.onBreakpointHit) {
this.onBreakpointHit(this.debugBreakState);
}
}
runEval(evalfunc : DebugEvalCondition) {
this.setDebugCondition( () => {
if (++this.debugClock >= this.debugTargetClock && this.machine.cpu.isStable()) {
var cpuState = this.getCPUState();
if (evalfunc(cpuState)) {
this.breakpointHit(this.debugClock);
return true;
} else {
return false;
}
}
});
}
runUntilReturn() {
var SP0 = this.machine.cpu.getSP();
this.runEval( (c:CpuState) : boolean => {
return c.SP > SP0;
});
}
runToFrameClock(clock : number) : void {
this.restartDebugging();
this.debugTargetClock = clock;
this.runEval(() : boolean => { return true; });
}
step() {
this.runToFrameClock(this.debugClock+1);
}
stepBack() {
var prevState;
var prevClock;
var clock0 = this.debugTargetClock;
this.restartDebugging();
this.debugTargetClock = clock0 - 25; // TODO: depends on CPU
this.runEval( (c:CpuState) : boolean => {
if (this.debugClock < clock0) {
prevState = this.saveState();
prevClock = this.debugClock;
return false;
} else {
if (prevState) {
this.loadState(prevState);
this.debugClock = prevClock;
}
return true;
}
});
}
}
export abstract class Base6502MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
getOpcodeMetadata = getOpcodeMetadata_6502;
getToolForFilename = getToolForFilename_6502;
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
}
getDebugCategories() {
return ['CPU','ZPRAM','Stack'];
}
getDebugInfo(category:string, state:EmuState) : string {
switch (category) {
case 'CPU': return cpuStateToLongString_6502(state.c);
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
}
}
}

View File

@ -1755,7 +1755,8 @@ var _MOS6502 = function() {
this.saveState = function():MOS6502State {
return {
PC: PC, A: A, X: X, Y: Y, SP: SP,
PC: (PC-1) & 0xffff,
A: A, X: X, Y: Y, SP: SP,
N: N, V: V, D: D, I: I, Z: Z, C: C,
T: T, o: opcode, R: RDY?1:0,
d: data, AD: AD, BA: BA, BC: BALCrossed?1:0, IA: IA,
@ -1764,7 +1765,8 @@ var _MOS6502 = function() {
};
this.loadState = function(state:MOS6502State) {
PC = state.PC; A = state.A; X = state.X; Y = state.Y; SP = state.SP;
PC = (state.PC+1) & 0xffff;
A = state.A; X = state.X; Y = state.Y; SP = state.SP;
N = state.N; V = state.V; D = state.D; I = state.I; Z = state.Z; C = state.C;
T = state.T; opcode = state.o; RDY = !!state.R;
data = state.d; AD = state.AD; BA = state.BA; BALCrossed = !!state.BC; IA = state.IA;
@ -1887,12 +1889,10 @@ var _MOS6502 = function() {
}
this.getSP = function() { return SP; }
this.getPC = function() { return PC; }
this.getPC = function() { return (PC-1) & 0xffff; }
this.getT = function() { return T; }
this.isPCStable = function() {
// TODO: gotta fix base class first
//return T < 0 || T == instruction.length-1;
return T == 0;
}
};
@ -1936,7 +1936,7 @@ export class MOS6502 implements CPU, ClockBased, SavesState<MOS6502State>, Inter
interrupt(itype:number) {
this.interruptType = itype;
}
getSP() {
getSP() {
return this.cpu.getSP();
}
getPC() {

View File

@ -1742,6 +1742,7 @@ export class Z80 implements CPU, InstructionBased, IOBusConnected, SavesState<Z8
loadState(s) {
this.cpu.loadState(s);
}
isStable() { return true; }
// TODO: metadata
// TODO: disassembler
}

View File

@ -1,7 +1,7 @@
export interface SavesState<T> {
saveState() : T;
loadState(state:T) : void;
export interface SavesState<S> {
saveState() : S;
loadState(state:S) : void;
}
export interface Bus {
@ -72,20 +72,25 @@ export interface IOBusConnected {
connectIOBus(bus:Bus) : void;
}
export interface CPU extends MemoryBusConnected, Resettable {
export interface CPU extends MemoryBusConnected, Resettable, SavesState<any> {
getPC() : number;
getSP() : number;
isStable() : boolean;
}
export interface Interruptable<T> {
interrupt(type:T) : void;
export interface HasCPU {
cpu : CPU;
}
export interface Interruptable<IT> {
interrupt(type:IT) : void;
}
// TODO
export interface AcceptsInput {
export interface AcceptsInput<CS> {
setInput(key:number, code:number, flags:number) : void;
//loadControlState();
//saveControlState();
loadControlsState(cs:CS);
saveControlsState() : CS;
}
// TODO?

View File

@ -1,7 +1,7 @@
"use strict";
import { MOS6502, MOS6502State } from "../cpu/MOS6502";
import { Bus, RasterFrameBased, SavesState, AcceptsROM, noise, Resettable, SampledAudioSource, SampledAudioSink } from "../devices";
import { Bus, RasterFrameBased, SavesState, AcceptsROM, AcceptsInput, noise, Resettable, SampledAudioSource, SampledAudioSink, HasCPU } from "../devices";
import { KeyFlags } from "../emu"; // TODO
import { lzgmini } from "../util";
@ -16,18 +16,22 @@ const HDR_SIZE = PGM_BASE - LOAD_BASE;
interface AppleIIStateBase {
ram : Uint8Array;
rnd,kbdlatch,soundstate : number;
rnd,soundstate : number;
auxRAMselected,writeinhibit : boolean;
auxRAMbank,bank2rdoffset,bank2wroffset : number;
grdirty : boolean[];
auxRAMbank : number;
}
interface AppleIIState extends AppleIIStateBase {
interface AppleIIControlsState {
kbdlatch : number;
}
interface AppleIIState extends AppleIIStateBase, AppleIIControlsState {
c : MOS6502State;
grswitch : number;
}
export class AppleII implements Bus, Resettable, RasterFrameBased, SampledAudioSource, AcceptsROM,
AppleIIStateBase, SavesState<AppleIIState> {
export class AppleII implements HasCPU, Bus, Resettable, RasterFrameBased, SampledAudioSource, AcceptsROM,
AppleIIStateBase, SavesState<AppleIIState>, AcceptsInput<AppleIIControlsState> {
ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM
rom : Uint8Array;
@ -58,7 +62,7 @@ export class AppleII implements Bus, Resettable, RasterFrameBased, SampledAudioS
this.ram[0xbf6f] = 0x01; // fake DOS detect for C
this.cpu.connectMemoryBus(this);
}
saveState() {
saveState() : AppleIIState {
// TODO: automagic
return {
c: this.cpu.saveState(),
@ -66,27 +70,45 @@ export class AppleII implements Bus, Resettable, RasterFrameBased, SampledAudioS
rnd: this.rnd,
kbdlatch: this.kbdlatch,
soundstate: this.soundstate,
grswitch: this.grparams.grswitch,
auxRAMselected: this.auxRAMselected,
writeinhibit: this.writeinhibit,
auxRAMbank: this.auxRAMbank,
bank2rdoffset: this.bank2rdoffset,
bank2wroffset: this.bank2wroffset,
grdirty: this.grdirty.slice(),
writeinhibit: this.writeinhibit,
};
}
loadState(s) {
loadState(s:AppleIIState) {
this.cpu.loadState(s.c);
this.ram.set(s.ram);
// TODO
this.rnd = s.rnd;
this.kbdlatch = s.kbdlatch;
this.soundstate = s.soundstate;
this.grparams.grswitch = s.grswitch;
this.auxRAMselected = s.auxRAMselected;
this.auxRAMbank = s.auxRAMbank;
this.writeinhibit = s.writeinhibit;
this.setupLanguageCardConstants();
this.ap2disp.invalidate(); // repaint entire screen
}
saveControlsState() : AppleIIControlsState {
return {kbdlatch:this.kbdlatch};
}
loadControlsState(s:AppleIIControlsState) {
this.kbdlatch = s.kbdlatch;
}
reset() {
this.cpu.reset();
// execute until $c600 boot
for (var i=0; i<2000000; i++) {
this.cpu.advanceClock();
if (this.cpu.getPC() == 0xc602) {
break;
}
}
}
noise() : number {
return (this.rnd = noise(this.rnd)) & 0xff;
}
read(address:number) : number {
address &= 0xffff;
readConst(address:number) : number {
if (address < 0xc000) {
return this.ram[address];
} else if (address >= 0xd000) {
@ -96,6 +118,13 @@ export class AppleII implements Bus, Resettable, RasterFrameBased, SampledAudioS
return this.ram[address];
else
return this.ram[address + this.bank2rdoffset];
} else
return 0;
}
read(address:number) : number {
address &= 0xffff;
if (address < 0xc000 || address >= 0xd000) {
return this.readConst(address);
} else if (address < 0xc100) {
var slot = (address >> 4) & 0x0f;
switch (slot)
@ -191,7 +220,7 @@ export class AppleII implements Bus, Resettable, RasterFrameBased, SampledAudioS
advanceFrame(maxCycles, trap) : number {
maxCycles = Math.min(maxCycles, cpuCyclesPerFrame);
for (var i=0; i<maxCycles; i++) {
if (trap && this.cpu.isStable() && trap()) break;
if (trap && trap()) break;
this.cpu.advanceClock();
this.audio.feedSample(this.soundstate, 1);
}
@ -301,46 +330,6 @@ const GR_HIRES = 8;
type AppleGRParams = {dirty:boolean[], grswitch:number, mem:Uint8Array};
/*
readAddress(addr : number) {
return ((addr & 0xf000) != 0xc000) ? bus.read(addr) : null; // ignore I/O space
}
loadState(state) {
this.unfixPC(state.c);
cpu.loadState(state.c);
this.fixPC(state.c);
ram.mem.set(state.b);
kbdlatch = state.kbd;
grswitch = state.gr;
auxRAMselected = state.lc.s;
auxRAMbank = state.lc.b;
writeinhibit = state.lc.w;
setupLanguageCardConstants();
ap2disp.invalidate(); // repaint entire screen
}
saveState() {
return {
c:this.getCPUState(),
b:ram.mem.slice(0),
kbd:kbdlatch,
gr:grswitch,
lc:{s:auxRAMselected,b:auxRAMbank,w:writeinhibit},
};
}
loadControlsState(state) {
kbdlatch = state.kbd;
}
saveControlsState() {
return {
kbd:kbdlatch
};
}
getCPUState() {
return this.fixPC(cpu.saveState());
}
*/
var Apple2Display = function(pixels : Uint32Array, apple : AppleGRParams) {
var XSIZE = 280;
var YSIZE = 192;

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ describe('MOS6502', function() {
s0.PC = 0x400;
cpu.loadState(s0);
for (var i=0; i<100000000; i++) {
//console.log(cpu.isStable(), cpu.saveState().o);
cpu.advanceClock();
var pc = cpu.getPC();
if (pc == 0x3469) break; // success!

View File

@ -27,11 +27,14 @@ includeInThisContext('tss/js/tss/AudioLooper.js');
//includeInThisContext("jsnes/dist/jsnes.min.js");
global.jsnes = require("jsnes/dist/jsnes.min.js");
//var devices = require('gen/devices.js');
var emu = require('gen/emu.js');
var Keys = emu.Keys;
var audio = require('gen/audio.js');
var recorder = require('gen/recorder.js');
//var _6502 = require('gen/cpu/MOS6502.js');
var _apple2 = require('gen/platform/apple2.js');
//var m_apple2 = require('gen/machine/apple2.js');
var _vcs = require('gen/platform/vcs.js');
var _nes = require('gen/platform/nes.js');
var _vicdual = require('gen/platform/vicdual.js');
@ -171,7 +174,7 @@ describe('Platform Replay', () => {
keycallback(32, 32, 128); // space bar
}
});
assert.equal(platform.saveState().kbd, 0x20); // strobe cleared
assert.equal(platform.saveState().kbdlatch, 0x20); // strobe cleared
});
it('Should run > 120 secs', () => {