2019-08-22 02:55:32 +00:00
|
|
|
|
2019-08-23 14:37:30 +00:00
|
|
|
export interface SavesState<S> {
|
|
|
|
saveState() : S;
|
|
|
|
loadState(state:S) : void;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface Bus {
|
|
|
|
read(a:number) : number;
|
|
|
|
write(a:number, v:number) : void;
|
2019-08-23 22:00:42 +00:00
|
|
|
readConst?(a:number) : number;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface ClockBased {
|
|
|
|
advanceClock() : void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface InstructionBased {
|
|
|
|
advanceInsn() : number;
|
|
|
|
}
|
|
|
|
|
2019-08-22 20:10:15 +00:00
|
|
|
export type TrapCondition = () => boolean;
|
|
|
|
|
2019-08-22 02:55:32 +00:00
|
|
|
export interface FrameBased {
|
2019-09-08 17:57:19 +00:00
|
|
|
advanceFrame(trap:TrapCondition) : number;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
2019-08-22 20:10:15 +00:00
|
|
|
export interface VideoSource {
|
2019-08-22 02:55:32 +00:00
|
|
|
getVideoParams() : VideoParams;
|
|
|
|
connectVideo(pixels:Uint32Array) : void;
|
|
|
|
}
|
|
|
|
|
2019-08-22 20:10:15 +00:00
|
|
|
export interface RasterFrameBased extends FrameBased, VideoSource {
|
2019-08-23 22:52:59 +00:00
|
|
|
getRasterY() : number;
|
|
|
|
getRasterX() : number;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface VideoParams {
|
|
|
|
width : number;
|
|
|
|
height : number;
|
|
|
|
overscan? : boolean;
|
|
|
|
rotate? : number;
|
2019-12-18 17:20:15 +00:00
|
|
|
videoFrequency? : number; // default = 60
|
2021-11-22 16:49:16 +00:00
|
|
|
aspect? : number;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: frame buffer optimization (apple2, etc)
|
|
|
|
|
|
|
|
export interface SampledAudioParams {
|
|
|
|
sampleRate : number;
|
|
|
|
stereo : boolean;
|
|
|
|
}
|
|
|
|
|
2019-08-22 20:10:15 +00:00
|
|
|
export interface SampledAudioSink {
|
|
|
|
feedSample(value:number, count:number) : void;
|
|
|
|
//sendAudioFrame(samples:Uint16Array) : void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface SampledAudioSource {
|
|
|
|
getAudioParams() : SampledAudioParams;
|
|
|
|
connectAudio(audio : SampledAudioSink) : void;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface AcceptsROM {
|
|
|
|
loadROM(data:Uint8Array, title?:string) : void;
|
|
|
|
}
|
|
|
|
|
2020-07-18 20:38:39 +00:00
|
|
|
export interface AcceptsBIOS {
|
|
|
|
loadBIOS(data:Uint8Array, title?:string) : void;
|
|
|
|
}
|
|
|
|
|
2019-08-22 02:55:32 +00:00
|
|
|
export interface Resettable {
|
|
|
|
reset() : void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface MemoryBusConnected {
|
|
|
|
connectMemoryBus(bus:Bus) : void;
|
|
|
|
}
|
|
|
|
|
2019-08-23 02:01:05 +00:00
|
|
|
export interface IOBusConnected {
|
|
|
|
connectIOBus(bus:Bus) : void;
|
|
|
|
}
|
|
|
|
|
2019-08-23 14:37:30 +00:00
|
|
|
export interface CPU extends MemoryBusConnected, Resettable, SavesState<any> {
|
2019-08-22 02:55:32 +00:00
|
|
|
getPC() : number;
|
|
|
|
getSP() : number;
|
2019-08-23 14:37:30 +00:00
|
|
|
isStable() : boolean;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
2019-08-23 19:05:12 +00:00
|
|
|
export interface HasCPU extends Resettable {
|
2019-08-23 14:37:30 +00:00
|
|
|
cpu : CPU;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Interruptable<IT> {
|
|
|
|
interrupt(type:IT) : void;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
2019-08-23 22:52:59 +00:00
|
|
|
export interface SavesInputState<CS> {
|
2019-08-23 22:00:42 +00:00
|
|
|
loadControlsState(cs:CS) : void;
|
2019-08-23 14:37:30 +00:00
|
|
|
saveControlsState() : CS;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
2019-08-23 22:52:59 +00:00
|
|
|
export interface AcceptsKeyInput {
|
|
|
|
setKeyInput(key:number, code:number, flags:number) : void;
|
|
|
|
}
|
|
|
|
|
2019-08-26 16:58:42 +00:00
|
|
|
export interface AcceptsPaddleInput {
|
|
|
|
setPaddleInput(controller:number, value:number) : void;
|
|
|
|
}
|
|
|
|
|
2021-07-25 02:45:55 +00:00
|
|
|
// TODO: interface not yet used (setKeyInput() handles joystick)
|
|
|
|
export interface AcceptsJoyInput {
|
|
|
|
setJoyInput(joy:number, bitmask:number) : void;
|
|
|
|
}
|
|
|
|
|
2020-10-17 19:34:08 +00:00
|
|
|
// SERIAL I/O
|
|
|
|
|
2021-06-14 19:38:51 +00:00
|
|
|
export interface SerialEvent {
|
|
|
|
op: 'read' | 'write';
|
|
|
|
value: number;
|
|
|
|
nbits: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: all these needed?
|
2020-10-17 19:34:08 +00:00
|
|
|
export interface SerialIOInterface {
|
|
|
|
// from machine to platform
|
|
|
|
clearToSend() : boolean;
|
|
|
|
sendByte(b : number);
|
|
|
|
// from platform to machine
|
|
|
|
byteAvailable() : boolean;
|
|
|
|
recvByte() : number;
|
|
|
|
// implement these too
|
|
|
|
reset() : void;
|
|
|
|
advance(clocks: number) : void;
|
2021-06-14 19:38:51 +00:00
|
|
|
// refresh() : void;
|
2020-10-17 19:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface HasSerialIO {
|
|
|
|
connectSerialIO(serial: SerialIOInterface);
|
2021-06-14 19:38:51 +00:00
|
|
|
serialOut?: SerialEvent[]; // outgoing event log
|
|
|
|
serialIn?: SerialEvent[]; // incoming queue
|
2020-10-17 19:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// PROFILER
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
export interface Probeable {
|
|
|
|
connectProbe(probe: ProbeAll) : void;
|
|
|
|
}
|
|
|
|
|
2019-08-24 15:28:04 +00:00
|
|
|
export interface ProbeTime {
|
|
|
|
logClocks(clocks:number);
|
|
|
|
logNewScanline();
|
|
|
|
logNewFrame();
|
|
|
|
}
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
export interface ProbeCPU {
|
2019-10-16 20:06:56 +00:00
|
|
|
logExecute(address:number, SP:number);
|
2019-08-22 20:10:15 +00:00
|
|
|
logInterrupt(type:number);
|
2019-09-09 16:21:35 +00:00
|
|
|
logIllegal(address:number);
|
2022-09-03 20:00:43 +00:00
|
|
|
logWait(address:number);
|
2019-08-22 20:10:15 +00:00
|
|
|
}
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
export interface ProbeBus {
|
2019-08-24 15:28:04 +00:00
|
|
|
logRead(address:number, value:number);
|
|
|
|
logWrite(address:number, value:number);
|
2022-09-03 20:00:43 +00:00
|
|
|
logDMARead(address:number, value:number);
|
|
|
|
logDMAWrite(address:number, value:number);
|
2019-08-22 20:10:15 +00:00
|
|
|
}
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
export interface ProbeIO {
|
2019-08-24 15:28:04 +00:00
|
|
|
logIORead(address:number, value:number);
|
|
|
|
logIOWrite(address:number, value:number);
|
2019-08-22 20:10:15 +00:00
|
|
|
}
|
|
|
|
|
2019-08-27 03:17:07 +00:00
|
|
|
export interface ProbeVRAM {
|
|
|
|
logVRAMRead(address:number, value:number);
|
|
|
|
logVRAMWrite(address:number, value:number);
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO, ProbeVRAM {
|
2020-07-14 02:14:47 +00:00
|
|
|
logData(data:number); // entire 32 bits
|
2020-07-16 19:47:28 +00:00
|
|
|
addLogBuffer(src: Uint32Array);
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export class NullProbe implements ProbeAll {
|
2019-08-24 15:28:04 +00:00
|
|
|
logClocks() {}
|
|
|
|
logNewScanline() {}
|
|
|
|
logNewFrame() {}
|
2019-08-24 02:32:10 +00:00
|
|
|
logExecute() {}
|
|
|
|
logInterrupt() {}
|
|
|
|
logRead() {}
|
|
|
|
logWrite() {}
|
|
|
|
logIORead() {}
|
|
|
|
logIOWrite() {}
|
2019-08-27 03:17:07 +00:00
|
|
|
logVRAMRead() {}
|
|
|
|
logVRAMWrite() {}
|
2019-09-09 16:21:35 +00:00
|
|
|
logIllegal() {}
|
2022-09-03 20:00:43 +00:00
|
|
|
logWait() {}
|
|
|
|
logDMARead() {}
|
|
|
|
logDMAWrite() {}
|
2020-07-14 02:14:47 +00:00
|
|
|
logData() {}
|
2020-07-16 19:47:28 +00:00
|
|
|
addLogBuffer(src: Uint32Array) {}
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
/// CONVENIENCE
|
|
|
|
|
|
|
|
export interface BasicMachineControlsState {
|
2019-08-24 15:28:04 +00:00
|
|
|
inputs: Uint8Array;
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
2019-08-22 02:55:32 +00:00
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
export interface BasicMachineState extends BasicMachineControlsState {
|
|
|
|
c: any; // TODO
|
2019-08-24 15:28:04 +00:00
|
|
|
ram: Uint8Array;
|
2019-08-22 02:55:32 +00:00
|
|
|
}
|
|
|
|
|
2019-09-08 15:39:54 +00:00
|
|
|
export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, Probeable,
|
2019-08-24 02:32:10 +00:00
|
|
|
SavesState<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
|
|
|
|
|
|
|
|
abstract cpuFrequency : number;
|
|
|
|
abstract defaultROMSize : number;
|
2019-09-08 15:39:54 +00:00
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
abstract cpu : CPU;
|
2019-09-08 15:39:54 +00:00
|
|
|
abstract ram : Uint8Array;
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
rom : Uint8Array;
|
|
|
|
inputs : Uint8Array = new Uint8Array(32);
|
2019-08-26 17:44:06 +00:00
|
|
|
handler : (key,code,flags) => void; // keyboard handler
|
2019-08-24 02:32:10 +00:00
|
|
|
|
2019-08-24 15:28:04 +00:00
|
|
|
nullProbe = new NullProbe();
|
|
|
|
probe : ProbeAll = this.nullProbe;
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
abstract read(a:number) : number;
|
|
|
|
abstract write(a:number, v:number) : void;
|
|
|
|
|
2019-08-26 17:44:06 +00:00
|
|
|
setKeyInput(key:number, code:number, flags:number) : void {
|
|
|
|
this.handler && this.handler(key,code,flags);
|
|
|
|
}
|
2019-08-24 15:28:04 +00:00
|
|
|
connectProbe(probe: ProbeAll) : void {
|
|
|
|
this.probe = probe || this.nullProbe;
|
|
|
|
}
|
2019-08-24 02:32:10 +00:00
|
|
|
reset() {
|
|
|
|
this.cpu.reset();
|
|
|
|
}
|
|
|
|
loadROM(data:Uint8Array, title?:string) : void {
|
|
|
|
if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize);
|
|
|
|
this.rom.set(data);
|
|
|
|
}
|
|
|
|
loadState(state) {
|
|
|
|
this.cpu.loadState(state.c);
|
2019-08-24 15:28:04 +00:00
|
|
|
this.ram.set(state.ram);
|
|
|
|
this.inputs.set(state.inputs);
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
|
|
|
saveState() {
|
|
|
|
return {
|
|
|
|
c:this.cpu.saveState(),
|
2019-08-24 15:28:04 +00:00
|
|
|
ram:this.ram.slice(0),
|
|
|
|
inputs:this.inputs.slice(0),
|
2019-08-24 02:32:10 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
loadControlsState(state) {
|
2019-08-24 15:28:04 +00:00
|
|
|
this.inputs.set(state.inputs);
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
|
|
|
saveControlsState() {
|
|
|
|
return {
|
2019-08-24 15:28:04 +00:00
|
|
|
inputs:this.inputs.slice(0)
|
2019-08-24 02:32:10 +00:00
|
|
|
};
|
|
|
|
}
|
2019-08-25 13:25:26 +00:00
|
|
|
advanceCPU() {
|
2019-08-24 02:32:10 +00:00
|
|
|
var c = this.cpu as any;
|
2019-08-24 15:28:04 +00:00
|
|
|
var n = 1;
|
2019-10-16 20:06:56 +00:00
|
|
|
if (this.cpu.isStable()) { this.probe.logExecute(this.cpu.getPC(), this.cpu.getSP()); }
|
2019-08-25 14:45:36 +00:00
|
|
|
if (c.advanceClock) { c.advanceClock(); }
|
|
|
|
else if (c.advanceInsn) { n = c.advanceInsn(1); }
|
2019-08-24 15:28:04 +00:00
|
|
|
this.probe.logClocks(n);
|
|
|
|
return n;
|
|
|
|
}
|
2019-08-26 23:00:01 +00:00
|
|
|
probeMemoryBus(membus:Bus) : Bus {
|
|
|
|
return {
|
2019-08-24 15:28:04 +00:00
|
|
|
read: (a) => {
|
|
|
|
let val = membus.read(a);
|
|
|
|
this.probe.logRead(a,val);
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
write: (a,v) => {
|
|
|
|
this.probe.logWrite(a,v);
|
|
|
|
membus.write(a,v);
|
|
|
|
}
|
2019-08-26 23:00:01 +00:00
|
|
|
};
|
2019-08-24 15:28:04 +00:00
|
|
|
}
|
2019-08-26 23:00:01 +00:00
|
|
|
connectCPUMemoryBus(membus:Bus) : void {
|
|
|
|
this.cpu.connectMemoryBus(this.probeMemoryBus(membus));
|
|
|
|
}
|
|
|
|
probeIOBus(iobus:Bus) : Bus {
|
|
|
|
return {
|
2019-08-24 15:28:04 +00:00
|
|
|
read: (a) => {
|
|
|
|
let val = iobus.read(a);
|
|
|
|
this.probe.logIORead(a,val);
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
write: (a,v) => {
|
|
|
|
this.probe.logIOWrite(a,v);
|
|
|
|
iobus.write(a,v);
|
|
|
|
}
|
2019-08-26 23:00:01 +00:00
|
|
|
};
|
|
|
|
}
|
2022-09-03 20:00:43 +00:00
|
|
|
probeDMABus(iobus:Bus) : Bus {
|
|
|
|
return {
|
|
|
|
read: (a) => {
|
|
|
|
let val = iobus.read(a);
|
|
|
|
this.probe.logDMARead(a,val);
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
write: (a,v) => {
|
|
|
|
this.probe.logDMAWrite(a,v);
|
|
|
|
iobus.write(a,v);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2019-08-26 23:00:01 +00:00
|
|
|
connectCPUIOBus(iobus:Bus) : void {
|
|
|
|
this.cpu['connectIOBus'](this.probeIOBus(iobus));
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 15:39:54 +00:00
|
|
|
export abstract class BasicMachine extends BasicHeadlessMachine implements SampledAudioSource {
|
|
|
|
|
|
|
|
abstract canvasWidth : number;
|
|
|
|
abstract numVisibleScanlines : number;
|
|
|
|
abstract sampleRate : number;
|
|
|
|
overscan : boolean = false;
|
|
|
|
rotate : number = 0;
|
2022-09-02 16:05:03 +00:00
|
|
|
aspectRatio : number;
|
2019-09-08 15:39:54 +00:00
|
|
|
|
|
|
|
pixels : Uint32Array;
|
|
|
|
audio : SampledAudioSink;
|
|
|
|
|
|
|
|
scanline : number;
|
|
|
|
|
|
|
|
getAudioParams() : SampledAudioParams {
|
|
|
|
return {sampleRate:this.sampleRate, stereo:false};
|
|
|
|
}
|
|
|
|
connectAudio(audio : SampledAudioSink) : void {
|
|
|
|
this.audio = audio;
|
|
|
|
}
|
|
|
|
getVideoParams() : VideoParams {
|
2022-08-30 02:00:52 +00:00
|
|
|
return {width:this.canvasWidth,
|
|
|
|
height:this.numVisibleScanlines,
|
|
|
|
aspect:this.aspectRatio,
|
|
|
|
overscan:this.overscan,
|
|
|
|
rotate:this.rotate};
|
2019-09-08 15:39:54 +00:00
|
|
|
}
|
|
|
|
connectVideo(pixels:Uint32Array) : void {
|
|
|
|
this.pixels = pixels;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-24 02:32:10 +00:00
|
|
|
export abstract class BasicScanlineMachine extends BasicMachine implements RasterFrameBased {
|
|
|
|
|
|
|
|
abstract numTotalScanlines : number;
|
|
|
|
abstract cpuCyclesPerLine : number;
|
|
|
|
|
2019-08-25 13:25:26 +00:00
|
|
|
abstract startScanline() : void;
|
|
|
|
abstract drawScanline() : void;
|
2019-09-08 15:39:54 +00:00
|
|
|
|
|
|
|
frameCycles : number;
|
2019-08-25 13:25:26 +00:00
|
|
|
|
2019-09-08 17:57:19 +00:00
|
|
|
advanceFrame(trap: TrapCondition) : number {
|
2019-08-25 13:25:26 +00:00
|
|
|
this.preFrame();
|
2019-08-24 02:32:10 +00:00
|
|
|
var endLineClock = 0;
|
2020-07-04 14:16:45 +00:00
|
|
|
var steps = 0;
|
2019-08-24 15:28:04 +00:00
|
|
|
this.probe.logNewFrame();
|
2020-07-24 02:52:53 +00:00
|
|
|
this.frameCycles = 0;
|
2019-08-24 02:32:10 +00:00
|
|
|
for (var sl=0; sl<this.numTotalScanlines; sl++) {
|
2020-07-11 17:26:37 +00:00
|
|
|
endLineClock += this.cpuCyclesPerLine; // could be fractional
|
2019-08-24 02:32:10 +00:00
|
|
|
this.scanline = sl;
|
|
|
|
this.startScanline();
|
2020-07-24 02:52:53 +00:00
|
|
|
while (this.frameCycles < endLineClock) {
|
2019-08-24 02:32:10 +00:00
|
|
|
if (trap && trap()) {
|
|
|
|
sl = 999;
|
|
|
|
break;
|
|
|
|
}
|
2020-07-24 02:52:53 +00:00
|
|
|
this.frameCycles += this.advanceCPU();
|
2020-07-04 14:16:45 +00:00
|
|
|
steps++;
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
|
|
|
this.drawScanline();
|
2019-08-24 15:28:04 +00:00
|
|
|
this.probe.logNewScanline();
|
2020-07-24 02:52:53 +00:00
|
|
|
this.probe.logClocks(Math.floor(this.frameCycles - endLineClock)); // remainder of prev. line
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
2019-08-25 13:25:26 +00:00
|
|
|
this.postFrame();
|
2020-07-04 14:16:45 +00:00
|
|
|
return steps; // TODO: return steps, not clock? for recorder
|
2019-08-24 02:32:10 +00:00
|
|
|
}
|
2019-08-25 13:25:26 +00:00
|
|
|
preFrame() { }
|
|
|
|
postFrame() { }
|
2019-08-24 02:32:10 +00:00
|
|
|
getRasterY() { return this.scanline; }
|
|
|
|
getRasterX() { return this.frameCycles % this.cpuCyclesPerLine; }
|
|
|
|
}
|