Z80 Space Invaders starting to work; deferred worker module load

This commit is contained in:
Steven Hugg 2017-01-13 21:31:04 -05:00
parent bd551f7311
commit 7fce763f64
14 changed files with 3309 additions and 167 deletions

View File

@ -165,6 +165,14 @@ a.dropdown-toggle {
-moz-border-radius:6px 0 6px 6px;
border-radius:6px 0 6px 6px;
}
canvas {
image-rendering: optimizeSpeed; /* Older versions of FF */
image-rendering: -moz-crisp-edges; /* FF 6.0+ */
image-rendering: -webkit-optimize-contrast; /* Safari */
image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */
}
</style>
</head>
<body>
@ -177,14 +185,14 @@ a.dropdown-toggle {
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<li><a class="dropdown-item" href="#" id="item_new_file">New File...</a></li>
<li><a class="dropdown-item" href="#" id="item_share_file">Share File...</a></li>
<li><a class="dropdown-item" href="#" id="item_reset_file">Reset to Original...</a></li>
<li><a class="dropdown-item" href="#" id="item_reset_file">Revert to Original...</a></li>
<li class="dropdown dropdown-submenu">
<a tabindex="-1" href="#">Platform</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="?platform=vcs" id="item_platform_vcs">Atari VCS</a></li>
<li><a class="dropdown-item" href="?platform=apple2" id="item_platform_apple2">Apple ][</a></li>
<li><a class="dropdown-item" href="?platform=atarivec" id="item_platform_atarivec">Asteroids</a></li>
<li><a class="dropdown-item" href="?platform=exidy" id="item_platform_exidy">Exidy</a></li>
<li><a class="dropdown-item" href="?platform=spaceinv" id="item_platform_spaceinv">Space Invaders</a></li>
</ul>
</li>
<li class="dropdown dropdown-submenu">
@ -202,9 +210,9 @@ a.dropdown-toggle {
<button id="dbg_go" type="button" title="Run"><img src="images/play.png"></button>
<button id="dbg_step" type="submit" title="Step"><img src="images/singlestep.png"></button>
<button id="dbg_toline" type="submit" title="Run To Line"><img src="images/runtoline.png"></button>
<button id="dbg_reset" type="submit" title="Reset and Run To Line"><img src="images/resetandrun.png"></button>
<button id="dbg_stepout" type="submit" title="Step Out of Subroutine">RTS</button>
<button id="dbg_stepback" type="submit" title="Step Backwards">&lt;&lt;</button>
<button id="dbg_reset" type="submit" title="Reset and Break"><img src="images/resetandrun.png"></button>
<button id="dbg_timing" type="submit" title="See Timing" style="display:none"><img src="images/timing.png"></button>
<button id="dbg_disasm" type="submit" title="Toggle Disassembly">#</button>
</span>
@ -261,13 +269,14 @@ a.dropdown-toggle {
<link rel="stylesheet" href="codemirror/addon/dialog/dialog.css">
<script src="javatari.js/release/javatari/javatari.js"></script>
<script src="src/cpu/z80.js"></script>
<script src="src/emu.js"></script>
<script src="src/util.js"></script>
<script src="src/disasm.js"></script>
<script src="src/platform/vcs.js"></script>
<script src="src/platform/apple2.js"></script>
<script src="src/platform/atarivec.js"></script>
<script src="src/platform/exidy.js"></script>
<script src="src/platform/spaceinv.js"></script>
<script src="src/ui.js"></script>
</body>

2661
src/cpu/z80.coffee Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,10 @@
// Emulator classes
function noise() {
return (Math.random() * 256) & 0xff;
}
function _metakeyflags(e) {
return (e.shiftKey?2:0) | (e.ctrlKey?4:0) | (e.altKey?8:0) | (e.metaKey?16:0);
}
@ -11,6 +15,8 @@ function __createCanvas(mainElement, width, height) {
var fsElement = document.createElement('div');
fsElement.style.position = "relative";
fsElement.style.padding = "20px";
if (height > width)
fsElement.style.margin = "20%"; // TODO
fsElement.style.overflow = "hidden";
fsElement.style.background = "black";
@ -31,8 +37,11 @@ var RasterVideo = function(mainElement, width, height, options) {
var canvas, ctx;
var imageData, buf8, datau32;
this.start = function() {
this.create = function() {
canvas = __createCanvas(mainElement, width, height);
if (options.rotate) {
canvas.style.transform = "rotate("+options.rotate+"deg)";
}
ctx = canvas.getContext('2d');
imageData = ctx.createImageData(width, height);
var buf = new ArrayBuffer(imageData.data.length);
@ -303,6 +312,23 @@ var SampleAudio = function(clockfreq) {
}
}
function cpuStateToLongString_6502(c) {
function decodeFlags(c, flags) {
var s = "";
s += c.N ? " N" : " -";
s += c.V ? " V" : " -";
s += c.D ? " D" : " -";
s += c.Z ? " Z" : " -";
s += c.C ? " C" : " -";
// s += c.I ? " I" : " -";
return s;
}
return "PC " + hex(c.PC,4) + " " + decodeFlags(c) + " " + getTIAPosString() + "\n"
+ " A " + hex(c.A) + " " + (c.R ? "" : "BUSY") + "\n"
+ " X " + hex(c.X) + "\n"
+ " Y " + hex(c.Y) + " " + "SP " + hex(c.SP) + "\n";
}
var Base6502Platform = function() {
this.getOpcodeMetadata = function(opcode, offset) {
@ -345,6 +371,7 @@ var Base6502Platform = function() {
}
this.clearDebug = function() {
debugSavedState = null;
debugBreakState = null;
debugTargetClock = 0;
debugClock = 0;
onBreakpointHit = null;
@ -416,4 +443,170 @@ var Base6502Platform = function() {
}
});
}
this.runUntilReturn = function() {
var depth = 1;
self.runEval(function(c) {
if (depth <= 0 && c.T == 0)
return true;
if (c.o == 0x20)
depth++;
else if (c.o == 0x60 || c.o == 0x40)
--depth;
return false;
});
}
this.disassemble = function(mem, start, end, pcvisits) {
return new Disassembler6502().disassemble(mem, start, end, pcvisits);
}
this.cpuStateToLongString = function(c) {
return cpuStateToLongString_6502(c);
}
}
function dumpRAM(ram, ramofs, ramlen) {
var s = "";
// TODO: show scrollable RAM for other platforms
for (var ofs=0; ofs<ramlen; ofs+=0x10) {
s += '$' + hex(ofs+ramofs) + ':';
for (var i=0; i<0x10; i++) {
if (ofs+i < ram.length) {
if (i == 8) s += " ";
s += " " + hex(ram[ofs+i]);
}
}
s += "\n";
}
return s;
}
function cpuStateToLongString_Z80(c) {
function decodeFlags(flags) {
var flagspec = "SZ-H-VNC";
var s = "";
for (var i=0; i<8; i++)
s += (flags & (128>>i)) ? flagspec.slice(i,i+1) : "-";
return s; // TODO
}
return "PC " + hex(c.PC,4) + " " + decodeFlags(c.AF) + " " + getTIAPosString() + "\n"
+ "SP " + hex(c.SP,4) + " IR " + hex(c.IR,4) + "\n"
+ "IX " + hex(c.IX,4) + " IY " + hex(c.IY,4) + "\n"
+ "AF " + hex(c.AF,4) + " BC " + hex(c.BC,4) + "\n"
+ "DE " + hex(c.DE,4) + " HL " + hex(c.HL,4) + "\n"
;
}
var BaseZ80Platform = function() {
var onBreakpointHit;
var debugCondition;
var debugSavedState = null;
var debugBreakState = null;
var debugTargetClock = 0;
this.setDebugCondition = function(debugCond) {
if (debugSavedState) {
this.loadState(debugSavedState);
} else {
debugSavedState = this.saveState();
}
debugCondition = debugCond;
this.resume();
}
/*
this.restartDebugState = function() {
if (debugCondition && !debugBreakState) {
debugTargetClock -= cpu.getTstates();
cpu.setTstates(0);
debugSavedState = this.saveState();
}
}
*/
this.getDebugCallback = function() {
return debugCondition;
}
this.setupDebug = function(callback) {
onBreakpointHit = callback;
}
this.clearDebug = function() {
debugSavedState = null;
debugBreakState = null;
debugTargetClock = 0;
onBreakpointHit = null;
debugCondition = null;
}
this.breakpointHit = function() {
debugBreakState = this.saveState();
//debugBreakState.c.PC = (debugBreakState.c.PC-1) & 0xffff;
console.log("Breakpoint at clk", debugBreakState.c.tstates, "PC", debugBreakState.c.PC.toString(16));
this.pause();
if (onBreakpointHit) {
onBreakpointHit(debugBreakState);
}
}
// TODO: lower bound of clock value
this.step = function() {
var self = this;
this.setDebugCondition(function() {
var cpuState = self.getCPUState();
if (cpuState.tstates > debugTargetClock) {
debugTargetClock = cpuState.tstates;
self.breakpointHit();
return true;
}
return false;
});
}
this.stepBack = function() {
var self = this;
var prevState;
var prevClock;
this.setDebugCondition(function() {
var cpuState = self.getCPUState();
var debugClock = cpuState.tstates;
if (debugClock >= debugTargetClock && prevState) {
self.loadState(prevState);
debugTargetClock = prevClock;
self.breakpointHit();
return true;
} else if (debugClock > debugTargetClock-20 && debugClock < debugTargetClock) {
prevState = self.saveState();
prevClock = debugClock;
}
return false;
});
}
this.runEval = function(evalfunc) {
var self = this;
this.setDebugCondition(function() {
var cpuState = self.getCPUState();
if (cpuState.tstates > debugTargetClock) {
if (evalfunc(cpuState)) {
debugTargetClock = cpuState.tstates;
self.breakpointHit();
return true;
}
}
return false;
});
}
this.runUntilReturn = function() {
var self = this;
var depth = 1;
self.runEval(function(c) {
if (depth <= 0)
return true;
var op = self.readAddress(c.PC);
if (op == 0xcd) // CALL
depth++;
else if (op == 0xc0 || op == 0xc8 || op == 0xc9 || op == 0xd0) // RET (TODO?)
--depth;
return false;
});
}
this.disassemble = function(mem, start, end, pcvisits) {
return new Disassembler6502().disassemble(mem, start, end, pcvisits);
}
this.cpuStateToLongString = function(c) {
return cpuStateToLongString_Z80(c);
}
}

View File

@ -35,10 +35,6 @@ var Apple2Platform = function(mainElement) {
return APPLE2_PRESETS;
}
function noise() {
return (Math.random() * 256) & 0xff;
}
this.start = function() {
cpu = new jt.M6502();
ram = new RAM(0x13000); // 64K + 16K LC RAM - 4K hardware
@ -138,8 +134,7 @@ var Apple2Platform = function(mainElement) {
// create video/audio
video = new RasterVideo(mainElement,280,192);
audio = new SampleAudio(cpuFrequency);
video.start();
audio.start();
video.create();
video.setKeyboardEvents(function(key,code,flags) {
// since we're an Apple II+, we don't do lowercase
if (flags & 1) {
@ -158,8 +153,6 @@ var Apple2Platform = function(mainElement) {
var colors = [0xffff0000, 0xff00ff00];
timer = new AnimationTimer(60, function() {
// 262.5 scanlines per frame
var iaddr = 0x2000;
var iofs = 0;
var clock = 0;
var debugCond = self.getDebugCallback();
for (var sl=0; sl<262; sl++) {
@ -254,10 +247,6 @@ var Apple2Platform = function(mainElement) {
this.reset();
}
this.getRasterPosition = function() {
return {x:0, y:0};
}
this.isRunning = function() {
return timer.isRunning();
}

View File

@ -66,7 +66,7 @@ var AtariVectorPlatform = function(mainElement) {
video = new VectorVideo(mainElement,1024,1024);
dvg = new DVGStateMachine(bus, video);
audio = new SampleAudio(cpuFrequency);
video.start();
video.create();
timer = new AnimationTimer(60, function() {
video.clear();
// 262.5 scanlines per frame

View File

@ -47,9 +47,8 @@ var ExidyPlatform = function(mainElement) {
cpu.connectBus(bus);
// create video/audio
video = new RasterVideo(mainElement,256,256);
video.start();
video.create();
audio = new SampleAudio(cpuFrequency); // TODO
audio.start();
var idata = video.getFrameData();
timer = new AnimationTimer(60, function() {
var clock = 0;

224
src/platform/spaceinv.js Normal file
View File

@ -0,0 +1,224 @@
"use strict";
// http://www.computerarcheology.com/Arcade/SpaceInvaders/Hardware.html
var SPACEINV_PRESETS = [
];
// TODO: global???
window.buildZ80({
applyContention: false
});
var SpaceInvadersPlatform = function(mainElement) {
var self = this;
this.__proto__ = new BaseZ80Platform();
var cpu, ram, membus, iobus, rom;
var video, audio, timer, pixels;
var inputs = [0xe,0x8,0x0];
var bitshift_offset = 0;
var bitshift_register = 0;
var watchdog_counter;
var cpuFrequency = 1996800;
var cpuCyclesPerLine = Math.round(cpuFrequency/(60*224)); // TODO
var INITIAL_WATCHDOG = 256;
var PIXEL_ON = 0xffeeeeee;
var PIXEL_OFF = 0xff000000;
var KEYCODE_MAP = {
32:{i:1,b:4}, // space bar (P1)
37:{i:1,b:5}, // left arrow (P1)
39:{i:1,b:6}, // right arrow (P1)
0x53:{i:2,b:4}, // S (P2)
0x41:{i:2,b:5}, // A (P2)
0x44:{i:2,b:6}, // D (P2)
53:{i:1,b:0}, // 5
49:{i:1,b:2}, // 1
50:{i:1,b:1}, // 2
}
this.getOpcodeMetadata = function() {
return {
opcode:0,
mnenomic:'?',
minCycles:0,
maxCycles:0,
insnlength:1
};
}
this.getPresets = function() {
return SPACEINV_PRESETS;
}
this.start = function() {
ram = new RAM(0x2000);
membus = {
read: function(address) {
if (address < 0x2000) {
return rom ? rom[address] : 0;
} else {
address &= 0x1fff;
return ram.mem[address];
}
},
write: function(address, value) {
//console.log("write", hex(address,4), hex(value,2));
if (address >= 0x2000) {
address &= 0x1fff;
value &= 0xff;
ram.mem[address] = value;
if (address >= 0x400) {
// TODO: dirty flags
var ofs = (address - 0x400)*8;
for (var i=0; i<8; i++)
pixels[ofs+i] = (value & (1<<i)) ? PIXEL_ON : PIXEL_OFF;
}
}
},
isContended: function() { return false; },
};
iobus = {
read: function(addr) {
addr &= 0x3;
//console.log('IO read', hex(addr,4));
switch (addr) {
case 0:
case 1:
case 2:
return inputs[addr];
case 3:
return (bitshift_register << bitshift_offset) & 0xff;
}
return 0;
},
write: function(addr, val) {
addr &= 0x7;
val &= 0xff;
//console.log('IO write', hex(addr,4), hex(val,2));
switch (addr) {
case 2:
bitshift_offset = val & 0x7;
break;
case 3:
case 5:
// TODO: sound
break;
case 4:
bitshift_register = (bitshift_register >> 8) | (val << 8);
break;
case 6:
watchdog_counter = INITIAL_WATCHDOG;
break;
}
}
};
cpu = window.Z80({
display: {},
memory: membus,
ioBus: iobus
});
video = new RasterVideo(mainElement,256,224,{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;
cpu.setTstates(0);
var debugCond = self.getDebugCallback();
for (var sl=0; sl<224; sl++) {
var targetTstates = cpu.getTstates() + cpuCyclesPerLine;
if (debugCond) {
while (cpu.getTstates() < targetTstates) {
if (debugCond && debugCond()) { debugCond = null; }
cpu.runFrame(cpu.getTstates() + 1);
}
} else {
cpu.runFrame(targetTstates);
}
if (sl == 95)
cpu.requestInterrupt(0x8); // RST $8
else if (sl == 223)
cpu.requestInterrupt(0x10); // RST $10
}
video.updateFrame();
if (watchdog_counter-- <= 0) {
console.log("WATCHDOG FIRED"); // TODO: alert on video
self.reset();
}
//self.restartDebugState();
});
}
this.loadROM = function(title, data) {
rom = data;
self.reset();
}
this.loadState = function(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
bitshift_register = state.bsr;
bitshift_offset = state.bso;
watchdog_counter = state.wdc;
inputs[0] = state.in0;
inputs[1] = state.in1;
inputs[2] = state.in2;
}
this.saveState = function() {
return {
c:self.getCPUState(),
b:ram.mem.slice(0),
bsr:bitshift_register,
bso:bitshift_offset,
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();
}
this.resume = function() {
timer.start();
audio.start();
}
this.reset = function() {
cpu.reset();
cpu.setTstates(0);
watchdog_counter = INITIAL_WATCHDOG;
}
this.readAddress = function(addr) {
return membus.read(addr);
}
this.ramStateToLongString = function(state) {
var stack = state.b.slice(state.c.SP & 0x1fff, 0x400);
return "\n" + dumpRAM(stack, state.c.SP, stack.length);
}
}

View File

@ -88,9 +88,29 @@ var VCSPlatform = function() {
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
}
this.readAddress = function(addr) {
return current_output[addr - 0xf000]; // TODO: use bus to read
return current_output[addr & 0xfff]; // TODO: use bus to read
}
this.runUntilReturn = function() {
var depth = 1;
self.runEval(function(c) {
if (depth <= 0 && c.T == 0)
return true;
if (c.o == 0x20)
depth++;
else if (c.o == 0x60 || c.o == 0x40)
--depth;
return false;
});
}
this.cpuStateToLongString = function(c) {
return cpuStateToLongString_6502(c);
}
this.getRAMForState = function(state) {
return jt.Util.byteStringToUInt8Array(atob(state.r.b));
}
this.ramStateToLongString = function(state) {
// TODO: customize RAM dump per-platform
var ram = self.getRAMForState(state);
return "\n" + dumpRAM(ram, 0x80, 0x80);
}
};

208
src/ui.js
View File

@ -1,5 +1,21 @@
"use strict";
// catch errors
if (typeof window.onerror == "object") {
window.onerror = function (msgevent, url, line, col, error) {
console.log(msgevent, url, line, col);
console.log(error);
//$("#editor").hide();
if (window.location.host.endsWith('8bitworkshop.com')) {
ga('send', 'exception', {
'exDescription': msgevent + " " + url + " " + " " + line + ":" + col + ", " + error,
'exFatal': true
});
}
alert(msgevent+"");
};
}
// 8bitworkshop IDE user interface
var PRESETS; // presets array
@ -260,6 +276,7 @@ function getToolForFilename(fn) {
if (fn.endsWith(".pla")) return "plasm";
if (fn.endsWith(".c")) return "cc65";
if (fn.endsWith(".s")) return "ca65";
if (fn.endsWith(".asm")) return "z80asm";
return "dasm";
}
@ -323,8 +340,8 @@ worker.onmessage = function(e) {
offset2line = {};
line2offset = {};
for (var info of e.data.listing.lines) {
if (info.offset) {
var textel = document.createTextNode(info.offset.toString(16));
if (info.offset >= 0) {
var textel = document.createTextNode(hex(info.offset,4));
editor.setGutterMarker(info.line-1, "gutter-offset", textel);
offset2line[info.offset] = info.line;
line2offset[info.line] = info.offset;
@ -354,7 +371,7 @@ function findLineForOffset(PC) {
if (offset2line) {
for (var i=0; i<256; i++) {
var line = offset2line[PC];
if (line) {
if (line >= 0) {
return line;
}
PC--;
@ -367,36 +384,11 @@ function setCurrentLine(line) {
editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
}
function hex(v, nd) {
try {
if (!nd) nd = 2;
var s = v.toString(16).toUpperCase();
while (s.length < nd)
s = "0" + s;
return s;
} catch (e) {
return v+"";
}
}
function decodeFlags(c, flags) {
var s = "";
s += c.N ? " N" : " -";
s += c.V ? " V" : " -";
s += c.D ? " D" : " -";
s += c.Z ? " Z" : " -";
s += c.C ? " C" : " -";
// s += c.I ? " I" : " -";
return s;
}
function cpuStateToLongString(c) {
return "PC " + hex(c.PC,4) + " " + decodeFlags(c) + " " + getTIAPosString() + "\n"
+ " A " + hex(c.A) + " " + (c.R ? "" : "BUSY") + "\n"
+ " X " + hex(c.X) + "\n"
+ " Y " + hex(c.Y) + " " + "SP " + hex(c.SP) + "\n";
}
function getTIAPosString() {
var pos = platform.getRasterPosition();
return "V" + pos.y + " H" + pos.x;
if (platform.getRasterPosition) {
var pos = platform.getRasterPosition();
return "V" + pos.y + " H" + pos.x;
} else return "";
}
var lastDebugInfo;
@ -431,19 +423,9 @@ function highlightDifferences(s1, s2) {
function showMemory(state) {
var s = "";
if (state) {
s = cpuStateToLongString(state.c);
s += "\n";
var ram = platform.getRAMForState(state);
var ramlen = ram.length <= 128 ? 128 : 256; // TODO
var ramofs = ram.length == 128 ? 0x80 : 0;
// TODO: show scrollable RAM for other platforms
for (var ofs=0; ofs<ramlen; ofs+=0x10) {
s += '$' + hex(ofs+ramofs) + ':';
for (var i=0; i<0x10; i++) {
if (i == 8) s += " ";
s += " " + hex(ram[ofs+i]);
}
s += "\n";
s = platform.cpuStateToLongString(state.c);
if (platform.ramStateToLongString) {
s += platform.ramStateToLongString(state);
}
var hs = lastDebugInfo ? highlightDifferences(lastDebugInfo, s) : s;
$("#mem_info").show().html(hs);
@ -460,9 +442,12 @@ function setupBreakpoint() {
lastDebugState = state;
var PC = state.c.PC;
var line = findLineForOffset(PC);
if (line) {
if (line >= 0) {
console.log("BREAKPOINT", hex(PC), line);
setCurrentLine(line);
} else {
console.log("BREAKPOINT", hex(PC));
// TODO: switch to disasm
}
pcvisits[PC] = pcvisits[PC] ? pcvisits[PC]+1 : 1;
showMemory(state);
@ -498,7 +483,7 @@ function runToCursor() {
setupBreakpoint();
var line = getCurrentLine();
var pc = line2offset[line];
if (pc) {
if (pc >= 0) {
console.log("Run to", line, pc.toString(16));
platform.runEval(function(c) {
return c.PC == pc;
@ -508,16 +493,7 @@ function runToCursor() {
function runUntilReturn() {
setupBreakpoint();
var depth = 1;
platform.runEval(function(c) {
if (depth <= 0 && c.T == 0)
return true;
if (c.o == 0x20)
depth++;
else if (c.o == 0x60 || c.o == 0x40)
--depth;
return false;
});
platform.runUntilReturn();
}
function runStepBackwards() {
@ -735,7 +711,8 @@ function updateDisassembly() {
function disassemble(start, end) {
if (start < 0) start = 0;
if (end > mem.length) end = mem.length;
var disasm = new Disassembler6502().disassemble(mem, start, end, pcvisits);
// TODO: use platform.readMemory()
var disasm = platform.disassemble(mem, start, end, pcvisits);
var s = "";
for (a in disasm) {
var srclinenum = offset2line[a];
@ -777,11 +754,11 @@ function toggleDisassembly() {
function resetAndDebug() {
clearBreakpoint();
platform.reset();
runToCursor();
platform.breakpointHit();
}
function _breakExpression() {
var exprs = window.prompt("Enter break expression", "c.PC == 0x6000");
var exprs = window.prompt("Enter break expression", "c.PC == 0x6000"); // TODO
if (exprs) {
var fn = new Function('c', 'return (' + exprs + ');');
setupBreakpoint();
@ -798,7 +775,10 @@ function setupDebugControls(){
$("#dbg_stepout").click(runUntilReturn);
$("#dbg_stepback").click(runStepBackwards);
$("#dbg_timing").click(traceTiming);
$("#dbg_disasm").click(toggleDisassembly);
if (platform.disassemble)
$("#dbg_disasm").click(toggleDisassembly);
else
$("#dbg_disasm").hide();
$("#disassembly").hide();
$(".dropdown-menu").collapse({toggle: false});
$("#item_new_file").click(_createNewFile);
@ -865,61 +845,59 @@ var qs = (function (a) {
})(window.location.search.substr(1).split('&'));
// start
setupDebugControls();
showWelcomeMessage();
// parse query string
try {
// is this a share URL?
if (qs['sharekey']) {
var sharekey = qs['sharekey'];
console.log("Loading shared file ", sharekey);
$.getJSON( ".storage/" + sharekey, function( result ) {
console.log(result);
var newid = 'shared/' + result['filename'];
updatePreset(newid, result['text']);
qs['file'] = newid;
delete qs['sharekey'];
window.location = "?" + $.param(qs);
}, 'text');
} else {
// add default platform?
platform_id = qs['platform'] || localStorage.getItem("__lastplatform");
if (!platform_id) {
platform_id = qs['platform'] = "vcs";
}
// load and start platform object
// TODO: self-register platforms
if (platform_id == 'vcs') {
platform = new VCSPlatform();
$("#booklink_vcs").show();
} else if (platform_id == 'apple2') {
platform = new Apple2Platform($("#emulator")[0]);
} else if (platform_id == 'atarivec') {
platform = new AtariVectorPlatform($("#emulator")[0]);
} else if (platform_id == 'exidy') {
platform = new ExidyPlatform($("#emulator")[0]);
} else {
alert("Platform " + platform_id + " not recognized");
}
store = new FileStore(localStorage, platform_id + '/');
PRESETS = platform.getPresets();
platform.start();
// reset file?
if (qs['file'] && qs['reset']) {
store.deleteFile(qs['file']);
qs['reset'] = '';
window.location = "?" + $.param(qs);
} else if (qs['file']) {
// load file
loadPreset(qs['file']);
updateSelector();
} else {
// try to load last file
var lastid = localStorage.getItem("__lastid_"+platform_id) || localStorage.getItem("__lastid");
localStorage.removeItem("__lastid");
gotoPresetNamed(lastid || PRESETS[0].id);
}
// is this a share URL?
if (qs['sharekey']) {
var sharekey = qs['sharekey'];
console.log("Loading shared file ", sharekey);
$.getJSON( ".storage/" + sharekey, function( result ) {
console.log(result);
var newid = 'shared/' + result['filename'];
updatePreset(newid, result['text']);
qs['file'] = newid;
delete qs['sharekey'];
window.location = "?" + $.param(qs);
}, 'text');
} else {
// add default platform?
platform_id = qs['platform'] || localStorage.getItem("__lastplatform");
if (!platform_id) {
platform_id = qs['platform'] = "vcs";
}
// load and start platform object
// TODO: self-register platforms
if (platform_id == 'vcs') {
platform = new VCSPlatform();
$("#booklink_vcs").show();
} else if (platform_id == 'apple2') {
platform = new Apple2Platform($("#emulator")[0]);
} else if (platform_id == 'atarivec') {
platform = new AtariVectorPlatform($("#emulator")[0]);
} else if (platform_id == 'exidy') {
platform = new ExidyPlatform($("#emulator")[0]);
} else if (platform_id == 'spaceinv') {
platform = new SpaceInvadersPlatform($("#emulator")[0]);
} else {
alert("Platform " + platform_id + " not recognized");
}
store = new FileStore(localStorage, platform_id + '/');
PRESETS = platform.getPresets();
setupDebugControls();
platform.start();
// reset file?
if (qs['file'] && qs['reset']) {
store.deleteFile(qs['file']);
qs['reset'] = '';
window.location = "?" + $.param(qs);
} else if (qs['file']) {
// load file
loadPreset(qs['file']);
updateSelector();
} else {
// try to load last file
var lastid = localStorage.getItem("__lastid_"+platform_id) || localStorage.getItem("__lastid");
localStorage.removeItem("__lastid");
gotoPresetNamed(lastid || PRESETS[0].id);
}
} catch (e) {
alert(e+""); // TODO?
}

View File

@ -1,4 +1,16 @@
function hex(v, nd) {
try {
if (!nd) nd = 2;
var s = v.toString(16).toUpperCase();
while (s.length < nd)
s = "0" + s;
return s;
} catch (e) {
return v+"";
}
}
function lzgmini() {
// Constants

View File

@ -1,15 +1,12 @@
"use strict";
// set up require.js for worker
importScripts("dasm.js");
importScripts("acme.js");
importScripts("plasm.js");
importScripts("cc65.js");
importScripts("ca65.js");
importScripts("ld65.js");
importScripts("z80asm.js");
importScripts("sdcc.js");
var loaded = {}
function load(modulename) {
if (!loaded[modulename]) {
importScripts(modulename+".js");
loaded[modulename] = 1;
}
}
// shim out window and document objects for security
// https://github.com/mbostock/d3/issues/1053
var noop = function() { return new Function(); };
@ -49,6 +46,20 @@ function setupFS(FS) {
}, '/share');
}
function extractErrors(strings, regex) {
var errors = [];
for (var i=0; i<strings.length; i++) {
var m = regex.exec(strings[i]);
if (m) {
errors.push({
line: m[1],
msg: m[2]
});
}
}
return errors;
}
// main worker start
var DASM_MAIN_FILENAME = "main.a";
@ -69,13 +80,36 @@ function match_msvc(s) {
console.log(s, matches);
if (matches) {
errline = parseInt(matches[1]);
errors.push({
msvc_errors.push({
line:errline,
msg:matches[2]
});
}
}
function parseListing(code, lineMatch) {
var lines = [];
for (var line of code.split(/\r?\n/)) {
var linem = lineMatch.exec(line);
if (linem && linem[1]) {
var linenum = parseInt(linem[1]);
var filename = linem[2];
var offset = parseInt(linem[3], 16);
var insns = linem[4];
var restline = linem[5];
if (insns) {
lines.push({
line:linenum,
offset:offset,
insns:insns,
iscode:restline[0] != '.'
});
}
}
}
return lines;
}
function parseDASMListing(code, unresolved) {
var errorMatch = /main.a [(](\d+)[)]: error: (.+)/;
// 4 08ee a9 00 start lda #01workermain.js:23:5
@ -137,6 +171,7 @@ function parseDASMListing(code, unresolved) {
}
function assembleDASM(code) {
load("dasm");
var re_usl = /(\w+)\s+0000\s+[?][?][?][?]/;
var unresolved = {};
function match_fn(s) {
@ -165,6 +200,7 @@ function assembleDASM(code) {
// TODO: not quite done
function assembleACME(code) {
load("acme");
// stderr
var re_err2 = /(Error|Warning) - File (.+?), line (\d+) ([^:]+) (.*)/;
var errors = [];
@ -210,6 +246,7 @@ function setupStdin(fs, code) {
}
function compilePLASMA(code) {
load("plasm");
// stdout
var outstr = "";
function out_fn(s) { outstr += s; outstr += "\n"; }
@ -278,6 +315,8 @@ function parseCA65Listing(code, mapfile) {
}
function assemblelinkCA65(code, platform, warnings) {
load("ca65");
load("ld65");
if (!platform)
platform = 'apple2'; // TODO
var objout, lstout;
@ -321,6 +360,7 @@ function assemblelinkCA65(code, platform, warnings) {
}
function compileCC65(code, platform) {
load("cc65");
if (!platform)
platform = 'apple2'; // TODO
// stderr
@ -357,6 +397,7 @@ function compileCC65(code, platform) {
}
function assembleZ80ASM(code, platform) {
load("z80asm");
if (!platform)
platform = 'apple2'; // TODO
var Module = z80asm({
@ -364,6 +405,7 @@ function assembleZ80ASM(code, platform) {
//logReadFiles:true,
print:print_fn,
printErr:print_fn,
TOTAL_MEMORY:64*1024*1024,
//locateFile: function(s) { return "" + s; },
});
var FS = Module['FS'];
@ -377,7 +419,9 @@ function assembleZ80ASM(code, platform) {
Module.callMain(["-b", "-s", "-l", "-m", "main.asm"]);
try {
var aerr = FS.readFile("main.err", {'encoding':'utf8'}); // TODO
console.log("ERRORS", aerr); // TODO
if (aerr.length) {
return {listing:{errors:extractErrors(aerr.split("\n"), /.+? line (\d+): (.+)/)}};
}
// Warning at file 'test.asm' line 9: 'XREF' is deprecated, use 'EXTERN' instead
} catch (e) {
}
@ -396,15 +440,19 @@ l_main00101 = 0003, L: test
return {
exitstatus:Module.EXITSTATUS,
output:aout,
listing:listing,
listing:{
errors:[],
lines:parseListing(alst, /(\d+)(\s+)([0-9A-F]+)\s+([0-9A-F][0-9A-F ]*[0-9A-F])\s+([A-Z_.].+)/i)
},
intermediate:{listing:alst, mapfile:amap},
};
} catch (e) {
throw Error(e);
throw (e);
}
}
function compileSDCC(code, platform) {
load("sdcc");
var SDCC = sdcc({
noInitialRun:true,
noFSInit:true,

View File

@ -59,12 +59,16 @@ global.postMessage = null;
includeInThisContext("src/worker/workermain.js");
function compile(tool, code, callback, outlen) {
function compile(tool, code, callback, outlen, nlines, nerrors) {
global.postMessage = function(msg) {
if (msg.listing.errors.length) console.log(msg.listing.errors);
assert.ok(msg.listing.lines);
assert.equal(msg.listing.errors.length, msg.listing.errors);
assert.equal(msg.output.length, outlen);
if (msg.listing.errors && msg.listing.errors.length) {
console.log(msg.listing.errors);
assert.equal(nerrors, msg.listing.errors.length, "errors");
} else {
assert.equal(nerrors||0, 0, "errors");
assert.equal(msg.output.length, outlen, "output binary");
assert.equal(msg.listing.lines.length, nlines, "listing lines");
}
callback(null, msg);
};
global.onmessage({
@ -74,18 +78,21 @@ function compile(tool, code, callback, outlen) {
describe('Worker', function() {
it('should assemble DASM', function(done) {
compile('dasm', '\tprocessor 6502\n\torg $f000\nfoo lda #0\n', done, 2);
compile('dasm', '\tprocessor 6502\n\torg $f000\nfoo lda #0\n', done, 2, 1);
});
it('should compile PLASMA', function(done) {
compile('plasm', 'word x = 0', done, 5);
compile('plasm', 'word x = 0', done, 5, 0);
});
it('should compile CC65', function(done) {
compile('cc65', '#include <stdio.h>\nint main() {\nint x=1;\nprintf("%d",x);\nreturn x+2;\n}', done, 2947);
compile('cc65', '#include <stdio.h>\nint main() {\nint x=1;\nprintf("%d",x);\nreturn x+2;\n}', done, 2947, 4);
});
it('should NOT assemble Z80ASM', function(done) {
compile('z80asm', 'ddwiuweq', done, 0, 0, 1);
});
it('should assemble Z80ASM', function(done) {
compile('z80asm', '\tMODULE test\n\tXREF _puts\n\tld hl,$0000\n\tret\n', done, 4);
compile('z80asm', '\tMODULE test\n\tXREF _puts\n\tld hl,$0000\n\tret\n', done, 4, 2, 0);
});
it('should compile SDCC', function(done) {
compile('sdcc', 'int main(int argc) {\nint x=1; int y=2;\nreturn x+y+argc;\n}', done, 16);
compile('sdcc', 'int main(int argc) {\nint x=1; int y=2;\nreturn x+y+argc;\n}', done, 16, 8, 0);
});
});

View File

@ -2,7 +2,9 @@
require('../../src/cpu/z80.js');
global.buildZ80({
var _global = window;
_global.buildZ80({
applyContention: true
});
@ -92,7 +94,7 @@ function runTest(input, expected) {
var memory = Memory(dump);
var ioBus = IOBus();
var z80 = global.Z80({
var z80 = _global.Z80({
display: {},
memory: memory,
ioBus: ioBus