mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-01-11 08:30:02 +00:00
made BaseMachinePlatform, test with NewApple2Platform (work on debugging)
This commit is contained in:
parent
30db326f57
commit
20bc3620ac
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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
@ -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!
|
||||
|
@ -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', () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user