diff --git a/src/devices.ts b/src/devices.ts index 500dc266..27180216 100644 --- a/src/devices.ts +++ b/src/devices.ts @@ -234,6 +234,7 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A abstract cpu : CPU; abstract ram : Uint8Array; + // TODO? abstract handler; // keyboard handler rom : Uint8Array; pixels : Uint32Array; diff --git a/src/machine/mw8080bw.ts b/src/machine/mw8080bw.ts new file mode 100644 index 00000000..c1477caa --- /dev/null +++ b/src/machine/mw8080bw.ts @@ -0,0 +1,151 @@ + +import { Z80, Z80State } from "../cpu/ZilogZ80"; +import { BasicScanlineMachine } from "../devices"; +import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../emu"; +import { TssChannelAdapter, MasterAudio, AY38910_Audio } from "../audio"; + +// http://www.computerarcheology.com/Arcade/ + +const MW8080BW_PRESETS = [ + { id: 'gfxtest.c', name: 'Graphics Test' }, + { id: 'shifter.c', name: 'Sprite w/ Bit Shifter' }, + { id: 'game2.c', name: 'Cosmic Impalas' }, +]; + +const SPACEINV_KEYCODE_MAP = makeKeycodeMap([ + [Keys.A, 1, 0x10], // P1 + [Keys.LEFT, 1, 0x20], + [Keys.RIGHT, 1, 0x40], + [Keys.P2_A, 2, 0x10], // P2 + [Keys.P2_LEFT, 2, 0x20], + [Keys.P2_RIGHT, 2, 0x40], + [Keys.SELECT, 1, 0x1], + [Keys.START, 1, 0x4], + [Keys.P2_START, 1, 0x2], +]); + +const INITIAL_WATCHDOG = 256; +const PIXEL_ON = 0xffeeeeee; +const PIXEL_OFF = 0xff000000; + +export class Midway8080 extends BasicScanlineMachine { + + cpuFrequency = 1996800; // MHz + canvasWidth = 256; + numTotalScanlines = 262; + numVisibleScanlines = 224; + cpuCyclesPerLine = Math.floor(1996800 / (262*60)); + defaultROMSize = 0x2000; + rotate = -90; + sampleRate = 1; + + bitshift_offset = 0; + bitshift_register = 0; + watchdog_counter; + + cpu: Z80 = new Z80(); + ram = new Uint8Array(0x2000); + handler; + + constructor() { + super(); + this.connectCPUMemoryBus(this); + this.connectCPUIOBus(this.newIOBus()); + this.handler = newKeyboardHandler(this.inputs, SPACEINV_KEYCODE_MAP); + } + + setKeyInput(key:number, code:number, flags:number) : void { + this.handler(key,code,flags); + } + + read = newAddressDecoder([ + [0x0000, 0x1fff, 0x1fff, (a) => { return this.rom ? this.rom[a] : 0; }], + [0x2000, 0x3fff, 0x1fff, (a) => { return this.ram[a]; }], + ]); + write = newAddressDecoder([ + [0x2000, 0x23ff, 0x3ff, (a, v) => { this.ram[a] = v; }], + [0x2400, 0x3fff, 0x1fff, (a, v) => { + this.ram[a] = v; + var ofs = (a - 0x400) << 3; + for (var i = 0; i < 8; i++) { + this.pixels[ofs + i] = (v & (1 << i)) ? PIXEL_ON : PIXEL_OFF; + } + //if (displayPCs) displayPCs[a] = cpu.getPC(); // save program counter + }], + ]); + + newIOBus() { + return { + read: (addr) => { + addr &= 0x3; + //console.log('IO read', hex(addr,4)); + switch (addr) { + case 0: + case 1: + case 2: + return this.inputs[addr]; + case 3: + return (this.bitshift_register >> (8 - this.bitshift_offset)) & 0xff; + } + return 0; + }, + write: (addr, val) => { + addr &= 0x7; + val &= 0xff; + //console.log('IO write', hex(addr,4), hex(val,2)); + switch (addr) { + case 2: + this.bitshift_offset = val & 0x7; + break; + case 3: + case 5: + // TODO: sound + break; + case 4: + this.bitshift_register = (this.bitshift_register >> 8) | (val << 8); + break; + case 6: + this.watchdog_counter = INITIAL_WATCHDOG; + break; + } + } + }; + } + + startScanline() { + } + + drawScanline() { + // at end of scanline + if (this.scanline == 95) + this.cpu.interrupt(0x8); // RST $8 + else if (this.scanline == 223) + this.cpu.interrupt(0x10); // RST $10 + } + + advanceFrame(maxCycles, trap) : number { + if (this.watchdog_counter-- <= 0) { + console.log("WATCHDOG FIRED"); // TODO: alert on video + this.reset(); + } + return super.advanceFrame(maxCycles, trap); + } + + loadState(state) { + super.loadState(state); + this.bitshift_register = state.bsr; + this.bitshift_offset = state.bso; + this.watchdog_counter = state.wdc; + } + saveState() { + var state: any = super.saveState(); + state.bsr = this.bitshift_register; + state.bso = this.bitshift_offset; + state.wdc = this.watchdog_counter; + return state; + } + reset() { + super.reset(); + this.watchdog_counter = INITIAL_WATCHDOG; + } +} diff --git a/src/platform/mw8080bw.ts b/src/platform/mw8080bw.ts index 87e6a35b..d0d4e3ac 100644 --- a/src/platform/mw8080bw.ts +++ b/src/platform/mw8080bw.ts @@ -1,8 +1,9 @@ "use strict"; -import { Platform, BasicZ80ScanlinePlatform, BaseZ80Platform } from "../baseplatform"; -import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu"; -import { hex } from "../util"; +import { Midway8080 } from "../machine/mw8080bw"; +import { BaseZ80MachinePlatform } from "../baseplatform"; +import { Platform } from "../baseplatform"; +import { PLATFORMS } from "../emu"; // http://www.computerarcheology.com/Arcade/ @@ -12,142 +13,14 @@ const MW8080BW_PRESETS = [ { id: 'game2.c', name: 'Cosmic Impalas' }, ]; -const SPACEINV_KEYCODE_MAP = makeKeycodeMap([ - [Keys.A, 1, 0x10], // P1 - [Keys.LEFT, 1, 0x20], - [Keys.RIGHT, 1, 0x40], - [Keys.P2_A, 2, 0x10], // P2 - [Keys.P2_LEFT, 2, 0x20], - [Keys.P2_RIGHT, 2, 0x40], - [Keys.SELECT, 1, 0x1], - [Keys.START, 1, 0x4], - [Keys.P2_START, 1, 0x2], -]); +class Midway8080BWPlatform extends BaseZ80MachinePlatform implements Platform { -const INITIAL_WATCHDOG = 256; -const PIXEL_ON = 0xffeeeeee; -const PIXEL_OFF = 0xff000000; + newMachine() { return new Midway8080(); } + getPresets() { return MW8080BW_PRESETS; } + getDefaultExtension() { return ".c"; }; + readAddress(a) { return this.machine.read(a); } - -class Midway8080BWPlatform extends BasicZ80ScanlinePlatform implements Platform { - cpuFrequency = 1996800; // MHz - canvasWidth = 256; - numTotalScanlines = 262; - numVisibleScanlines = 224; - defaultROMSize = 0x2000; - - bitshift_offset = 0; - bitshift_register = 0; - watchdog_counter; - - getPresets() { return MW8080BW_PRESETS; } - getKeyboardMap() { return SPACEINV_KEYCODE_MAP; } - getVideoOptions() { return { rotate: -90 }; } - newRAM() { return new Uint8Array(0x2000); } - - newMembus() { - return { - read: newAddressDecoder([ - [0x0000, 0x1fff, 0x1fff, (a) => { return this.rom ? this.rom[a] : 0; }], - [0x2000, 0x3fff, 0x1fff, (a) => { return this.ram[a]; }], - ]), - write: newAddressDecoder([ - [0x2000, 0x23ff, 0x3ff, (a, v) => { this.ram[a] = v; }], - [0x2400, 0x3fff, 0x1fff, (a, v) => { - this.ram[a] = v; - var ofs = (a - 0x400) << 3; - for (var i = 0; i < 8; i++) { - this.pixels[ofs + i] = (v & (1 << i)) ? PIXEL_ON : PIXEL_OFF; - } - //if (displayPCs) displayPCs[a] = cpu.getPC(); // save program counter - }], - ]), - }; - } - - newIOBus() { - return { - read: (addr) => { - addr &= 0x3; - //console.log('IO read', hex(addr,4)); - switch (addr) { - case 0: - case 1: - case 2: - return this.inputs[addr]; - case 3: - return (this.bitshift_register >> (8 - this.bitshift_offset)) & 0xff; - } - return 0; - }, - write: (addr, val) => { - addr &= 0x7; - val &= 0xff; - //console.log('IO write', hex(addr,4), hex(val,2)); - switch (addr) { - case 2: - this.bitshift_offset = val & 0x7; - break; - case 3: - case 5: - // TODO: sound - break; - case 4: - this.bitshift_register = (this.bitshift_register >> 8) | (val << 8); - break; - case 6: - this.watchdog_counter = INITIAL_WATCHDOG; - break; - } - } - }; - } - - startScanline(sl: number) { - } - - drawScanline(sl: number) { - // at end of scanline - if (sl == 95) - this.cpu.requestInterrupt(0x8); // RST $8 - else if (sl == 223) - this.cpu.requestInterrupt(0x10); // RST $10 - } - - advance(novideo: boolean) { - super.advance(novideo); - if (this.watchdog_counter-- <= 0) { - console.log("WATCHDOG FIRED"); // TODO: alert on video - this.reset(); - } - } - - loadState(state) { - super.loadState(state); - this.bitshift_register = state.bsr; - this.bitshift_offset = state.bso; - this.watchdog_counter = state.wdc; - } - saveState() { - var state: any = super.saveState(); - state.bsr = this.bitshift_register; - state.bso = this.bitshift_offset; - state.wdc = this.watchdog_counter; - return state; - } - reset() { - super.reset(); - this.watchdog_counter = INITIAL_WATCHDOG; - } } -/* - $(video.canvas).click(function(e) { - var x = Math.floor(e.offsetX * video.canvas.width / $(video.canvas).width()); - var y = Math.floor(e.offsetY * video.canvas.height / $(video.canvas).height()); - var addr = (x>>3) + (y*32) + 0x400; - if (displayPCs) console.log(x, y, hex(addr,4), "PC", hex(displayPCs[addr],4)); - }); - */ PLATFORMS['mw8080bw'] = Midway8080BWPlatform; diff --git a/src/recorder.ts b/src/recorder.ts index b27b94cd..37d6ec38 100644 --- a/src/recorder.ts +++ b/src/recorder.ts @@ -225,10 +225,28 @@ export class ProbeRecorder implements ProbeAll { if (this.idx >= this.buf.length) return; this.buf[this.idx++] = a; } + relog(a:number) { + this.buf[this.idx-1] = a; + } + lastOp() { + if (this.idx > 0) + return this.buf[this.idx-1] & 0xff000000; + else + return -1; + } + lastAddr() { + if (this.idx > 0) + return this.buf[this.idx-1] & 0xffffff; + else + return -1; + } logClocks(clocks:number) { - if (clocks) { + if (clocks > 0) { this.fclk += clocks; - this.log(clocks | ProbeFlags.CLOCKS); + if (this.lastOp() == ProbeFlags.CLOCKS) + this.relog((this.lastAddr() + clocks) | ProbeFlags.CLOCKS); // coalesce clocks + else + this.log(clocks | ProbeFlags.CLOCKS); } } logNewScanline() {