galaxian hardware; coalesce multiple worker msgs
This commit is contained in:
parent
a7afff1ea6
commit
f649f0ec03
|
@ -213,7 +213,7 @@ canvas.pixelated {
|
|||
<button id="dbg_go" type="button" title="Run"><span class="glyphicon glyphicon-play" aria-hidden="true"></span></button>
|
||||
<button id="dbg_step" type="submit" title="Step"><span class="glyphicon glyphicon-step-forward" aria-hidden="true"></span></button>
|
||||
<button id="dbg_toline" type="submit" title="Run To Line"><span class="glyphicon glyphicon-save" aria-hidden="true"></span></button>
|
||||
<button id="dbg_stepout" type="submit" title="Step Out of Subroutine"><span class="glyphicon glyphicon-hand-left" aria-hidden="true"></span></button>
|
||||
<button id="dbg_stepout" type="submit" title="Step Out of Subroutine"><span class="glyphicon glyphicon-hand-up" aria-hidden="true"></span></button>
|
||||
<button id="dbg_stepback" type="submit" title="Step Backwards"><span class="glyphicon glyphicon-step-backward" aria-hidden="true"></span></button>
|
||||
<button id="dbg_timing" type="submit" title="See Timing" style="display:none"><span class="glyphicon glyphicon-time" aria-hidden="true"></span></button>
|
||||
<button id="dbg_disasm" type="submit" title="Toggle Disassembly" style="display:none"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></button>
|
||||
|
|
|
@ -2314,6 +2314,10 @@ window.buildZ80 = (opts) ->
|
|||
~48T window, to support retriggered interrupts and interrupt blocking via
|
||||
chains of EI or DD/FD prefixes */
|
||||
}
|
||||
self.nonMaskableInterrupt = function() {
|
||||
iff1 = 1;
|
||||
self.requestInterrupt(0x66);
|
||||
}
|
||||
var z80Interrupt = function() {
|
||||
if (iff1) {
|
||||
if (halted) {
|
||||
|
|
File diff suppressed because one or more lines are too long
14
src/emu.js
14
src/emu.js
|
@ -16,9 +16,7 @@ function __createCanvas(mainElement, width, height) {
|
|||
// TODO
|
||||
var fsElement = document.createElement('div');
|
||||
fsElement.style.position = "relative";
|
||||
fsElement.style.padding = "20px";
|
||||
if (height > width)
|
||||
fsElement.style.margin = "20%"; // TODO
|
||||
fsElement.style.padding = "5%";
|
||||
fsElement.style.overflow = "hidden";
|
||||
fsElement.style.background = "black";
|
||||
|
||||
|
@ -43,7 +41,10 @@ var RasterVideo = function(mainElement, width, height, options) {
|
|||
this.create = function() {
|
||||
self.canvas = canvas = __createCanvas(mainElement, width, height);
|
||||
if (options && options.rotate) {
|
||||
// TODO: aspect ratio?
|
||||
canvas.style.transform = "rotate("+options.rotate+"deg)";
|
||||
if (canvas.width > canvas.height)
|
||||
canvas.style.paddingTop = canvas.style.paddingBottom = "10%";
|
||||
}
|
||||
ctx = canvas.getContext('2d');
|
||||
imageData = ctx.createImageData(width, height);
|
||||
|
@ -68,9 +69,12 @@ var RasterVideo = function(mainElement, width, height, options) {
|
|||
return datau32;
|
||||
}
|
||||
|
||||
this.updateFrame = function() {
|
||||
this.updateFrame = function(sx, sy, dx, dy, width, height) {
|
||||
imageData.data.set(buf8);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
if (width && height)
|
||||
ctx.putImageData(imageData, sx, sy, dx, dy, width, height);
|
||||
else
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,381 @@
|
|||
|
||||
"use strict";
|
||||
var GALAXIAN_PRESETS = [
|
||||
];
|
||||
|
||||
// TODO: global???
|
||||
window.buildZ80({
|
||||
applyContention: false
|
||||
});
|
||||
|
||||
var GalaxianPlatform = function(mainElement) {
|
||||
var self = this;
|
||||
this.__proto__ = new BaseZ80Platform();
|
||||
|
||||
var cpu, ram, vram, oram, membus, iobus, rom, palette, outlatches;
|
||||
var video, audio, timer, pixels, displayPCs;
|
||||
var inputs = [0xe,0x8,0x0];
|
||||
var interruptEnabled = 0;
|
||||
var starsEnabled = 0;
|
||||
var watchdog_counter;
|
||||
var frameCounter = 0;
|
||||
|
||||
var XTAL = 18432000.0;
|
||||
var scanlinesPerFrame = 264;
|
||||
var cpuFrequency = XTAL/6; // 3.072 MHz
|
||||
var hsyncFrequency = XTAL/3/192/2; // 16 kHz
|
||||
var vsyncFrequency = hsyncFrequency/132/2; // 60.606060 Hz
|
||||
var vblankDuration = 1/vsyncFrequency * (20/132); // 2500 us
|
||||
var cpuCyclesPerLine = cpuFrequency/hsyncFrequency;
|
||||
var INITIAL_WATCHDOG = 256;
|
||||
var showOffscreenObjects = false;
|
||||
var stars = [];
|
||||
for (var i=0; i<256; i++)
|
||||
stars[i] = noise();
|
||||
|
||||
function drawScanline(pixels, sl) {
|
||||
if (sl < 16 && !showOffscreenObjects) return; // offscreen
|
||||
if (sl >= 240 && !showOffscreenObjects) return; // offscreen
|
||||
// draw tiles
|
||||
var pixofs = sl*264;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
for (var xx=0; xx<32; xx++) {
|
||||
var xofs = xx;
|
||||
var scroll = oram.mem[xofs*2]; // even entries control scroll position
|
||||
var attrib = oram.mem[xofs*2+1]; // odd entries control the color base
|
||||
var sl2 = (sl + scroll) & 0xff;
|
||||
var vramofs = (sl2>>3)<<5; // offset in VRAM
|
||||
var yy = sl2 & 7; // y offset within tile
|
||||
var tile = vram.mem[vramofs+xofs];
|
||||
var color0 = (attrib & 7) << 2;
|
||||
var addr = 0x2800+(tile<<3)+yy;
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr+0x800];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = color0 + ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
pixels[outi] = palette[color];
|
||||
outi++;
|
||||
}
|
||||
}
|
||||
// draw sprites
|
||||
for (var sprnum=7; sprnum>=0; sprnum--) {
|
||||
var base = (sprnum<<2) + 0x40;
|
||||
var base0 = oram.mem[base];
|
||||
var sy = 240 - (base0 - (sprnum<3)); // the first three sprites match against y-1
|
||||
var yy = (sl - sy);
|
||||
if (yy >= 0 && yy < 16) {
|
||||
var sx = oram.mem[base+3] + 1; // +1 pixel offset from tiles
|
||||
if (sx == 0 && !showOffscreenObjects)
|
||||
continue; // drawn off-buffer
|
||||
var code = oram.mem[base+1];
|
||||
var flipx = code & 0x40; // TODO
|
||||
var flipy = code & 0x80; // TODO
|
||||
code &= 0x3f;
|
||||
var color0 = oram.mem[base+2] << 2;
|
||||
var addr = 0x2800+(code<<5)+(yy<8?yy:yy+8);
|
||||
outi = pixofs + sx; //<< 1
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr+0x800];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
if (color)
|
||||
pixels[outi+i] = palette[color0 + color];
|
||||
}
|
||||
var data1 = rom[addr+8];
|
||||
var data2 = rom[addr+0x808];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
if (color)
|
||||
pixels[outi+i+8] = palette[color0 + color];
|
||||
}
|
||||
}
|
||||
}
|
||||
// draw bullets/shells
|
||||
var shell = 0xff;
|
||||
var missile = 0xff;
|
||||
for (var which=0; which<8; which++) {
|
||||
var sy = oram.mem[0x60 + (which<<2)+1];
|
||||
if (((sy + sl - (which<3))&0xff) == 0xff) {
|
||||
if (which != 7)
|
||||
shell = which;
|
||||
else
|
||||
missile = which;
|
||||
}
|
||||
}
|
||||
for (var which of [shell,missile]) {
|
||||
if (which != 0xff) {
|
||||
var sx = 255 - oram.mem[0x60 + (which<<2)+3];
|
||||
var outi = pixofs+sx;
|
||||
var col = which == 7 ? 0xffffff00 : 0xffffffff;
|
||||
pixels[outi++] = col;
|
||||
pixels[outi++] = col;
|
||||
pixels[outi++] = col;
|
||||
pixels[outi++] = col;
|
||||
}
|
||||
}
|
||||
// draw stars
|
||||
if (starsEnabled) {
|
||||
var starx = ((frameCounter + stars[sl & 0xff]) & 0xff);
|
||||
if ((starx + sl) & 0x10) {
|
||||
var outi = pixofs + starx;
|
||||
if ((pixels[outi] & 0xffffff) == 0) {
|
||||
pixels[outi] = palette[sl & 0x1f];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var KEYCODE_MAP = {
|
||||
32:{i:0,b:4}, // space bar (P1)
|
||||
37:{i:0,b:2}, // left arrow (P1)
|
||||
39:{i:0,b:3}, // right arrow (P1)
|
||||
0x53:{i:1,b:4}, // S (P2)
|
||||
0x41:{i:1,b:2}, // A (P2)
|
||||
0x44:{i:1,b:3}, // D (P2)
|
||||
53:{i:0,b:0}, // 5
|
||||
49:{i:1,b:0}, // 1
|
||||
50:{i:1,b:1}, // 2
|
||||
}
|
||||
|
||||
this.getPresets = function() {
|
||||
return GALAXIAN_PRESETS;
|
||||
}
|
||||
|
||||
this.start = function() {
|
||||
ram = new RAM(0x400);
|
||||
vram = new RAM(0x400);
|
||||
oram = new RAM(0x100);
|
||||
outlatches = new RAM(0x8);
|
||||
membus = {
|
||||
read: function(address) {
|
||||
if (address < 0x4000) {
|
||||
return (rom ? rom[address] : 0) & 0xff;
|
||||
} else if (address < 0x4800) {
|
||||
address &= 0x3ff;
|
||||
return ram.mem[address] & 0xff;
|
||||
} else if (address >= 0x5000 && address < 0x5800) {
|
||||
address &= 0x3ff;
|
||||
return vram.mem[address] & 0xff;
|
||||
} else if (address >= 0x5800 && address < 0x6000) {
|
||||
address &= 0xff;
|
||||
return oram.mem[address] & 0xff;
|
||||
} else if (address >= 0x6000 && address < 0x6800) {
|
||||
address &= 0x7;
|
||||
switch (address) {
|
||||
case 0:
|
||||
return inputs[0];
|
||||
}
|
||||
} else if (address >= 0x6800 && address < 0x7000) {
|
||||
return inputs[1];
|
||||
} else if (address >= 0x7000 && address < 0x7800) {
|
||||
address &= 0x7;
|
||||
switch (address) {
|
||||
case 0:
|
||||
return inputs[2];
|
||||
}
|
||||
} else if (address >= 0x7800 && address < 0x8000) {
|
||||
watchdog_counter = INITIAL_WATCHDOG;
|
||||
} else {
|
||||
console.log("read", hex(address));
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
write: function(address, value) {
|
||||
//console.log("write", hex(address,4), hex(value,2));
|
||||
if (address >= 0x4000 && address < 0x4800) {
|
||||
address &= 0x3ff;
|
||||
ram.mem[address] = value;
|
||||
} else if (address >= 0x5000 && address < 0x5800) {
|
||||
address &= 0x3ff;
|
||||
vram.mem[address] = value;
|
||||
} else if (address >= 0x5800 && address < 0x6000) {
|
||||
address &= 0xff;
|
||||
oram.mem[address] = value;
|
||||
} else if (address >= 0x6000 && address < 0x6800) {
|
||||
address &= 0x7;
|
||||
outlatches.mem[address] = value;
|
||||
} else if (address >= 0x6800 && address < 0x7000) {
|
||||
address &= 0x7;
|
||||
// TODO: sound
|
||||
} else if (address >= 0x7000 && address < 0x7800) {
|
||||
address &= 0x7;
|
||||
switch (address) {
|
||||
case 1:
|
||||
interruptEnabled = value;
|
||||
break;
|
||||
case 4:
|
||||
starsEnabled = value;
|
||||
break;
|
||||
}
|
||||
} else if (address >= 0x7800 && address < 0x8000) {
|
||||
// TODO: sound
|
||||
} else {
|
||||
console.log("write", hex(address), hex(value));
|
||||
}
|
||||
},
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
console.log('IO read', hex(addr,4));
|
||||
return 0;
|
||||
},
|
||||
write: function(addr, val) {
|
||||
console.log('IO write', hex(addr,4), hex(val,2));
|
||||
}
|
||||
};
|
||||
cpu = window.Z80({
|
||||
display: {},
|
||||
memory: membus,
|
||||
ioBus: iobus
|
||||
});
|
||||
video = new RasterVideo(mainElement,264,264,{rotate:90});
|
||||
audio = new SampleAudio(cpuFrequency);
|
||||
video.create();
|
||||
var idata = video.getFrameData();
|
||||
video.setKeyboardEvents(function(key,code,flags) {
|
||||
var o = KEYCODE_MAP[key];
|
||||
if (o) {
|
||||
if (flags & 1) {
|
||||
inputs[o.i] |= (1<<o.b);
|
||||
} else {
|
||||
inputs[o.i] &= ~(1<<o.b);
|
||||
}
|
||||
}
|
||||
});
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, function() {
|
||||
if (!self.isRunning())
|
||||
return;
|
||||
var debugCond = self.getDebugCallback();
|
||||
var targetTstates = cpu.getTstates();
|
||||
for (var sl=0; sl<scanlinesPerFrame; sl++) {
|
||||
drawScanline(pixels, sl);
|
||||
targetTstates += cpuCyclesPerLine;
|
||||
if (debugCond) {
|
||||
while (cpu.getTstates() < targetTstates) {
|
||||
if (debugCond && debugCond()) { debugCond = null; }
|
||||
cpu.runFrame(cpu.getTstates() + 1);
|
||||
}
|
||||
} else {
|
||||
cpu.runFrame(targetTstates);
|
||||
}
|
||||
}
|
||||
// visible area is 256x224 (before rotation)
|
||||
video.updateFrame(0, 0, 0, 0, showOffscreenObjects ? 264 : 256, 264);
|
||||
frameCounter = (frameCounter + 1) & 0xff;
|
||||
if (watchdog_counter-- <= 0) {
|
||||
console.log("WATCHDOG FIRED, PC ", hex(cpu.getPC())); // TODO: alert on video
|
||||
self.reset();
|
||||
}
|
||||
self.restartDebugState();
|
||||
// NMI interrupt @ 0x66
|
||||
if (interruptEnabled) { cpu.nonMaskableInterrupt(); }
|
||||
});
|
||||
}
|
||||
|
||||
var bitcolors = [
|
||||
0x000021, 0x000047, 0x000097, // red
|
||||
0x002100, 0x004700, 0x009700, // green
|
||||
0x510000, 0xae0000 // blue
|
||||
];
|
||||
|
||||
this.loadROM = function(title, data) {
|
||||
rom = padBytes(data, 0x4000);
|
||||
palette = new Uint32Array(new ArrayBuffer(32*4));
|
||||
for (var i=0; i<32; i++) {
|
||||
var b = rom[0x3800+i];
|
||||
palette[i] = 0xff000000;
|
||||
for (var j=0; j<8; j++)
|
||||
if (((1<<j) & b))
|
||||
palette[i] += bitcolors[j];
|
||||
}
|
||||
self.reset();
|
||||
/*
|
||||
// skip self-test
|
||||
for (var i=0 ;i<3000; i++) {
|
||||
cpu.runFrame(cpu.getTstates() + cpuCyclesPerLine*scanlinesPerFrame);
|
||||
if (interruptEnabled) { cpu.nonMaskableInterrupt(); }
|
||||
}
|
||||
function arr2arr(a) {
|
||||
var l = 0;
|
||||
while (a[++l] >= 0) ;
|
||||
var arr = new Uint8Array(new ArrayBuffer(l));
|
||||
for (var i=0; i<l; i++)
|
||||
arr[i] = a[i];
|
||||
return arr;
|
||||
}
|
||||
state.b = arr2arr(state.b);
|
||||
state.bv = arr2arr(state.bv);
|
||||
state.bo = arr2arr(state.bo);
|
||||
this.loadState(state);
|
||||
*/
|
||||
}
|
||||
|
||||
this.loadState = function(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
vram.mem.set(state.bv);
|
||||
oram.mem.set(state.bo);
|
||||
watchdog_counter = state.wdc;
|
||||
interruptEnabled = state.ie;
|
||||
starsEnabled = state.se;
|
||||
frameCounter = fc;
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
this.saveState = function() {
|
||||
return {
|
||||
c:self.getCPUState(),
|
||||
b:ram.mem.slice(0),
|
||||
bv:vram.mem.slice(0),
|
||||
bo:oram.mem.slice(0),
|
||||
fc:frameCounter,
|
||||
ie:interruptEnabled,
|
||||
se:starsEnabled,
|
||||
wdc:watchdog_counter,
|
||||
in0:inputs[0],
|
||||
in1:inputs[1],
|
||||
in2:inputs[2],
|
||||
};
|
||||
}
|
||||
this.getRAMForState = function(state) {
|
||||
return ram.mem;
|
||||
}
|
||||
this.getCPUState = function() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
audio.stop();
|
||||
console.log(JSON.stringify(this.saveState()));
|
||||
}
|
||||
this.resume = function() {
|
||||
timer.start();
|
||||
audio.start();
|
||||
}
|
||||
this.reset = function() {
|
||||
cpu.reset();
|
||||
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
|
||||
watchdog_counter = INITIAL_WATCHDOG;
|
||||
}
|
||||
this.readAddress = function(addr) {
|
||||
return membus.read(addr);
|
||||
}
|
||||
|
||||
this.ramStateToLongString = function(state) {
|
||||
return "";
|
||||
//var stack = state.b.slice(state.c.SP & 0x1fff, 0x400);
|
||||
//return "\n" + dumpRAM(stack, state.c.SP, stack.length);
|
||||
}
|
||||
}
|
||||
|
||||
PLATFORMS['galaxian'] = GalaxianPlatform;
|
|
@ -21,7 +21,7 @@ var SpaceInvadersPlatform = function(mainElement) {
|
|||
var bitshift_register = 0;
|
||||
var watchdog_counter;
|
||||
var cpuFrequency = 1996800;
|
||||
var cpuCyclesPerLine = Math.round(cpuFrequency/(60*224)); // TODO
|
||||
var cpuCyclesPerLine = cpuFrequency/(60*224); // TODO
|
||||
var INITIAL_WATCHDOG = 256;
|
||||
var PIXEL_ON = 0xffeeeeee;
|
||||
var PIXEL_OFF = 0xff000000;
|
||||
|
|
|
@ -116,6 +116,7 @@ var sourcefile = null;
|
|||
var pcvisits;
|
||||
var trace_pending_at_pc;
|
||||
var store;
|
||||
var pendingWorkerMessages;
|
||||
|
||||
var editor = CodeMirror(document.getElementById('editor'), {
|
||||
theme: 'mbo',
|
||||
|
@ -312,6 +313,8 @@ function updateSelector() {
|
|||
}
|
||||
|
||||
function setCode(text) {
|
||||
if (pendingWorkerMessages++ > 0)
|
||||
return;
|
||||
worker.postMessage({code:text, platform:platform_id,
|
||||
tool:platform.getToolForFilename(current_preset_id)});
|
||||
}
|
||||
|
@ -328,6 +331,10 @@ function arrayCompare(a,b) {
|
|||
}
|
||||
|
||||
worker.onmessage = function(e) {
|
||||
if (pendingWorkerMessages > 1) {
|
||||
setCode(editor.getValue());
|
||||
}
|
||||
pendingWorkerMessages = 0;
|
||||
// errors?
|
||||
var toolbar = $("#controls_top");
|
||||
function addErrorMarker(line, msg) {
|
||||
|
|
Loading…
Reference in New Issue