started on asteroids vector driver

This commit is contained in:
Steven Hugg 2017-01-02 20:42:15 -05:00
parent f0cb1602e4
commit 14f3667e7e
8 changed files with 1022 additions and 27 deletions

View File

@ -22,3 +22,4 @@ TODO:
- break on BRK/illegal opcode?
- start analysis from vector address
cat ~/Downloads/appleiigo.rom | hexdump -v -e '"\n" 32/1 "%u,"' ; echo

View File

@ -25,12 +25,12 @@ body {
.gutter-info {
width: 1em;
}
.tooltip {
.tooltipbox {
position: relative;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
.tooltipbox .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
@ -43,7 +43,7 @@ body {
position: absolute;
z-index: 10;
}
.tooltip:hover .tooltiptext {
.tooltipbox:hover .tooltiptext {
visibility: visible;
}
#notebook {
@ -57,7 +57,7 @@ div.editor {
bottom:0;
right:0;
width:50%;
height:100vh;
height:120vh;
background-color:#999;
line-height:1.25;
font-size:12pt;
@ -67,16 +67,17 @@ div.emulator {
left:50%;
top:0;
width:50%;
height:100vh;
height:120vh;
overflow-y: scroll;
background-color: #666;
margin-top: 20px auto 0;
}
div.mem_info {
position: absolute;
position: fixed;
left: 51%;
bottom: 10px;
background-color: #333;
color: #66ff66;
left: 20px;
white-space: pre;
padding: 20px;
z-index: 2;
@ -144,7 +145,7 @@ a.dropdown-toggle {
<span class="dbg_info" id="dbg_info"></span>
</div>
</div>
<div style="margin-top: 30px auto 0; min-height: 594px;" class="emulator" id="emulator">
<div class="emulator" id="emulator">
<div id="javatari-screen" style="margin: 0 auto; box-shadow: 2px 2px 10px rgb(60, 60, 60);"></div>
<div id="javatari-console-panel" style="margin: 0 auto; box-shadow: 2px 2px 10px rgb(60, 60, 60);"></div>
<div id="mem_info" class="mem_info" style="display:none">
@ -186,8 +187,11 @@ a.dropdown-toggle {
<link rel="stylesheet" href="codemirror/addon/dialog/dialog.css">
<script src="javatari.js/release/javatari/javatari.js"></script>
<script src="src/emu.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="local/vecrom.js"></script>
<script src="src/ui.js"></script>
</body>

198
src/emu.js Normal file
View File

@ -0,0 +1,198 @@
// Emulator classes
RasterVideo = function(mainElement, width, height, options) {
var self = this;
var canvas, ctx;
var imageData, buf8, datau32;
this.start = function() {
// TODO
fsElement = document.createElement('div');
fsElement.style.position = "relative";
fsElement.style.padding = "50px";
//fsElement.style.width = "100%";
//fsElement.style.height = "100%";
fsElement.style.overflow = "hidden";
fsElement.style.background = "black";
canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
fsElement.appendChild(canvas);
mainElement.appendChild(fsElement);
ctx = canvas.getContext('2d');
imageData = ctx.createImageData(width, height);
var buf = new ArrayBuffer(imageData.data.length);
buf8 = new Uint8ClampedArray(buf);
datau32 = new Uint32Array(buf);
}
this.getFrameData = function() {
return datau32;
}
this.updateFrame = function() {
imageData.data.set(buf8);
ctx.putImageData(imageData, 0, 0);
}
/*
mainElement.style.position = "relative";
mainElement.style.overflow = "hidden";
mainElement.style.outline = "none";
mainElement.tabIndex = "-1"; // Make it focusable
borderElement = document.createElement('div');
borderElement.style.position = "relative";
borderElement.style.overflow = "hidden";
borderElement.style.background = "black";
borderElement.style.border = "0 solid black";
borderElement.style.borderWidth = "" + borderTop + "px " + borderLateral + "px " + borderBottom + "px";
if (Javatari.SCREEN_CONTROL_BAR === 2) {
borderElement.style.borderImage = "url(" + IMAGE_PATH + "screenborder.png) " +
borderTop + " " + borderLateral + " " + borderBottom + " repeat stretch";
}
fsElement = document.createElement('div');
fsElement.style.position = "relative";
fsElement.style.width = "100%";
fsElement.style.height = "100%";
fsElement.style.overflow = "hidden";
fsElement.style.background = "black";
document.addEventListener("fullscreenchange", fullScreenChanged);
document.addEventListener("webkitfullscreenchange", fullScreenChanged);
document.addEventListener("mozfullscreenchange", fullScreenChanged);
document.addEventListener("msfullscreenchange", fullScreenChanged);
borderElement.appendChild(fsElement);
canvas.style.position = "absolute";
canvas.style.display = "block";
canvas.style.left = canvas.style.right = 0;
canvas.style.top = canvas.style.bottom = 0;
canvas.style.margin = "auto";
canvas.tabIndex = "-1"; // Make it focusable
canvas.style.outline = "none";
fsElement.appendChild(canvas);
setElementsSizes(jt.CanvasDisplay.DEFAULT_STARTING_WIDTH, jt.CanvasDisplay.DEFAULT_STARTING_HEIGHT);
mainElement.appendChild(borderElement);
*/
}
VectorVideo = function(mainElement, width, height) {
var self = this;
var canvas, ctx;
var persistenceAlpha = 0.5;
var jitter = 1.0;
this.start = function() {
// TODO
var fsElement = document.createElement('div');
fsElement.style.position = "relative";
fsElement.style.padding = "20px";
fsElement.style.overflow = "hidden";
fsElement.style.background = "black";
canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.tabIndex = "-1"; // Make it focusable
fsElement.appendChild(canvas);
mainElement.appendChild(fsElement);
ctx = canvas.getContext('2d');
}
this.setKeyboardEvents = function(callback) {
canvas.onkeydown = function(e) {
callback(e.key, 1);
};
canvas.onkeyup = function(e) {
callback(e.key, 0);
};
}
this.clear = function() {
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = persistenceAlpha;
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, width, height);
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'lighter';
}
this.drawLine = function(x1, y1, x2, y2, intensity) {
//console.log(x1, y1, x2, y2, intensity);
if (intensity > 0) {
// TODO: landscape vs portrait
ctx.beginPath();
// TODO: dots
var jx = jitter * (Math.random() - 0.5);
var jy = jitter * (Math.random() - 0.5);
x1 += jx;
x2 += jx;
y1 += jy;
y2 += jy;
ctx.moveTo(x1, height-y1);
ctx.lineTo(x2+1, height-y2);
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = intensity*0.1;
ctx.stroke();
}
}
}
SampleAudio = function(clockfreq) {
}
RAM = function(size) {
var memArray = new ArrayBuffer(size);
this.mem = new Uint8Array(memArray);
}
// TODO
AnimationTimer = function(frequencyHz, callback) {
var intervalMsec = 1000.0 / frequencyHz;
var curTime = 0;
var running;
var useReqAnimFrame = false; // TODO: disable on OS X
function scheduleFrame() {
if (useReqAnimFrame)
window.requestAnimationFrame(nextFrame);
else
setTimeout(nextFrame, intervalMsec);
}
var nextFrame = function(timestamp) {
// TODO: calculate framerate
callback();
if (running) {
scheduleFrame();
}
}
this.isRunning = function() {
return running;
}
this.start = function() {
if (!running) {
running = true;
scheduleFrame();
}
}
this.stop = function() {
running = false;
}
}
//

235
src/platform/apple2.js Normal file
View File

@ -0,0 +1,235 @@
var PRESETS = [
]
Apple2Platform = function(mainElement) {
var self = this;
var cpuFrequency = 1.023;
var cpuCyclesPerLine = 65;
var cpu, ram, rom, bus;
var video, audio, timer;
this.start = function() {
cpu = new jt.M6502();
ram = new RAM(0xc000);
//rom = new lzgmini().decode(APPLEIIGO_LZG).slice(0,12288);
// bus
bus = {
read: function(address) {
if (address >= 0xd000 && address <= 0xffff) {
return rom[address - 0xd000];
} else if (address < 0xc000) {
return ram.mem[address];
}
},
write: function(address, val) {
if (address < 0xc000) {
ram.mem[address] = val;
}
}
};
cpu.connectBus(bus);
// create video/audio
video = new RasterVideo(mainElement,280,192);
audio = new SampleAudio(cpuFrequency);
video.start();
var idata = video.getFrameData();
var colors = [0xffff0000, 0xff00ff00];
timer = new AnimationTimer(60, function() {
// 262.5 scanlines per frame
var iaddr = 0x2000;
var iofs = 0;
breakClock = -1;
clock = 0;
for (var sl=0; sl<262; sl++) {
for (var i=0; i<cpuCyclesPerLine; i++) {
if (debugCondition && breakClock < 0 && debugCondition()) { breakClock = clock; }
clock++;
cpu.clockPulse();
// TODO: audio
}
if (sl < 192) {
for (var xo=0; xo<35; xo++) {
var b = ram.mem[iaddr++];
idata[iofs++] = colors[(b>>0)&1];
idata[iofs++] = colors[(b>>1)&1];
idata[iofs++] = colors[(b>>2)&1];
idata[iofs++] = colors[(b>>3)&1];
idata[iofs++] = colors[(b>>4)&1];
idata[iofs++] = colors[(b>>5)&1];
idata[iofs++] = colors[(b>>6)&1];
}
}
}
video.updateFrame();
});
}
this.getOpcodeMetadata = function(opcode, offset) {
return Javatari.getOpcodeMetadata(opcode, offset); // TODO
}
this.loadROM = function(title, data) {
if(data.length != 0x3000) {
throw "ROM length must be == 0x3000";
}
rom = data;
this.reset();
}
this.getRasterPosition = function() {
return {x:xpos, y:ypos};
}
this.isRunning = function() {
return timer.isRunning();
}
this.pause = function() {
timer.stop();
}
this.resume = function() {
timer.start();
}
this.reset = function() {
cpu.reset();
}
this.getOriginPC = function() {
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
}
this.readAddress = function(addr) {
return bus.read(addr);
}
var onBreakpointHit;
var debugCondition;
var debugSavedState = null;
var debugBreakState = null;
var debugTargetClock = 0;
var debugClock = 0;
var debugFrameStartClock = 0;
var breakClock;
this.setDebugCondition = function(debugCond) {
if (debugSavedState) {
self.loadState(debugSavedState);
} else {
debugSavedState = self.saveState();
}
debugClock = 0;
debugCondition = debugCond;
self.resume();
}
this.setupDebug = function(callback) {
onBreakpointHit = callback;
}
this.clearDebug = function() {
debugSavedState = null;
debugTargetClock = 0;
debugClock = 0;
debugFrameStartClock = 0;
onBreakpointHit = null;
debugCondition = null;
}
this.breakpointHit = function() {
debugBreakState = self.saveState();
console.log("Breakpoint at clk", debugClock, "PC", debugBreakState.c.PC.toString(16));
this.pause();
if (onBreakpointHit) {
onBreakpointHit(debugBreakState);
}
}
this.step = function() {
var previousPC = -1;
self.setDebugCondition(function() {
if (debugClock++ >= debugTargetClock) {
var thisState = cpu.saveState();
if (previousPC < 0) {
previousPC = thisState.PC;
} else {
if (thisState.PC != previousPC && thisState.T == 0) {
//console.log(previousPC.toString(16), thisPC.toString(16));
debugTargetClock = debugClock-1;
self.breakpointHit();
return true;
}
}
}
return false;
});
}
this.runToPC = function(targetPC) {
var self = this;
self.setDebugCondition(function() {
if (debugClock++ >= debugTargetClock) {
var thisPC = cpu.saveState().PC;
if (thisPC == targetPC) {
self.breakpointHit();
debugTargetClock = debugClock;
return true;
} else {
return false;
}
}
});
}
this.loadState = function(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
}
this.saveState = function() {
return {
c:cpu.saveState(),
b:ram.mem.slice(0),
};
}
};
APPLEIIGO_LZG = [
76,90,71,0,0,48,0,0,0,5,159,47,60,46,159,1,21,25,30,52,65,80,80,76,69,73,73,71,79,32,82,79,
77,49,46,48,0,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,28,52,6,32,0,224,25,30,67,52,
30,52,28,25,31,174,52,31,52,30,52,28,52,6,25,63,107,52,31,52,30,52,28,25,63,101,52,19,25,31,139,52,
31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,25,160,32,52,28,32,32,160,
25,14,40,1,16,16,12,5,19,15,6,20,32,14,15,20,32,1,22,1,9,12,1,2,12,5,25,16,40,198,207,210,
160,205,207,210,197,160,201,206,30,3,205,193,212,201,207,206,160,208,204,197,193,211,197,160,195,204,201,195,203,160,30,8,
160,25,8,40,212,200,197,160,193,208,30,25,201,201,199,207,160,204,207,52,129,194,197,204,207,215,30,28,52,10,25,29,
224,52,22,76,3,224,32,88,252,162,39,189,0,223,157,128,4,202,16,247,30,3,48,223,157,0,5,30,195,30,78,25,
5,3,96,30,3,6,30,195,144,30,25,7,30,3,76,64,224,52,65,25,30,131,52,31,52,31,52,31,52,31,52,31,
52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,
52,31,52,24,201,206,176,238,201,201,144,234,201,204,240,230,208,232,234,52,11,72,74,41,3,9,4,133,41,104,41,24,
144,2,105,127,133,40,10,10,5,40,133,40,96,25,30,116,0,165,37,32,193,251,101,32,25,29,75,52,21,165,34,72,
32,36,252,165,40,133,42,165,41,133,43,164,33,136,104,105,1,197,35,176,13,30,78,177,40,145,42,136,16,249,48,225,
160,0,32,158,252,176,134,164,36,169,160,145,40,200,196,33,144,249,25,30,199,52,27,164,36,177,40,72,41,63,9,64,
145,40,104,108,56,25,19,27,32,12,253,32,165,251,52,161,201,155,240,243,25,28,141,52,6,32,142,253,165,51,32,237,
253,162,1,138,240,243,202,32,53,253,201,149,208,2,177,40,201,224,144,2,41,223,157,0,2,201,141,208,178,32,156,252,
169,141,208,91,164,61,166,60,30,39,32,64,249,160,0,169,173,76,237,253,25,28,87,52,31,52,31,52,31,52,31,52,
31,52,31,52,31,52,31,52,31,52,31,52,31,52,31,52,28,52,7,169,0,133,28,165,230,133,27,160,0,132,26,165,
28,145,26,32,126,244,200,208,246,230,27,165,27,41,31,208,238,96,133,226,134,224,132,225,72,41,192,133,38,74,74,5,
38,133,38,104,133,39,10,10,10,38,39,52,66,102,38,165,39,41,31,5,230,133,39,138,192,0,240,5,160,35,105,4,
200,233,7,176,251,132,229,170,189,185,244,133,48,152,74,165,228,133,28,176,21,28,0,35,52,4,10,201,192,16,6,165,
28,73,127,133,28,25,254,218,52,31,52,31,52,31,52,31,52,31,52,31,52,28,52,10,74,8,32,71,248,40,169,15,
144,2,105,224,133,46,177,38,69,48,37,46,81,38,145,38,96,32,0,248,196,44,176,17,200,32,14,248,144,246,105,1,
72,30,8,104,197,45,144,245,96,160,47,208,2,160,39,132,45,160,39,169,0,133,48,32,40,248,136,16,246,96,21,5,
4,126,39,21,6,4,126,38,10,10,25,130,52,96,165,48,24,105,3,41,15,133,48,10,52,1,5,48,133,48,25,103,
223,144,4,74,52,1,41,15,25,107,240,168,74,144,9,106,176,16,201,162,240,12,41,135,74,170,189,98,249,32,121,248,
208,4,160,128,169,0,170,189,166,249,133,46,41,3,133,47,152,41,143,170,152,160,3,224,138,240,11,74,144,8,74,74,
9,32,136,208,250,200,136,208,242,25,159,59,52,31,52,31,52,20,216,32,132,254,32,47,251,32,147,254,32,137,254,173,
88,192,173,90,192,173,93,192,173,95,192,173,255,207,44,16,192,216,32,58,255,32,96,251,169,0,133,0,169,198,133,1,
108,25,30,111,52,29,21,3,19,108,221,219,199,207,25,10,13,173,112,192,160,0,234,234,189,100,192,16,4,200,208,248,
136,96,169,0,133,72,173,86,192,173,84,192,173,81,192,169,0,240,11,173,80,192,173,83,192,32,54,248,169,20,133,34,
30,22,32,169,40,133,33,169,24,133,35,169,23,133,37,76,34,252,32,88,252,160,9,185,8,251,153,14,4,136,208,247,
96,173,243,3,73,165,141,244,3,96,201,141,208,24,172,0,192,16,19,192,147,208,15,44,16,192,30,68,251,192,131,240,
3,30,4,76,253,251,25,12,148,21,29,7,248,40,133,40,96,201,135,208,18,169,64,32,168,252,160,192,169,12,52,193,
173,48,192,136,208,245,96,164,36,145,40,230,36,165,36,197,33,176,102,96,201,160,176,239,168,16,236,201,141,240,90,201,
138,52,97,136,208,201,198,36,16,232,165,33,133,36,198,36,165,34,197,37,176,11,198,37,21,28,7,248,0,72,32,36,
252,32,158,252,160,0,104,105,0,197,35,144,240,176,202,165,34,133,37,160,0,132,36,240,228,169,0,133,36,230,30,62,
30,16,182,198,37,21,29,7,248,21,6,7,248,56,72,233,1,208,252,104,52,129,246,96,230,66,208,2,230,67,165,60,
197,62,165,61,229,63,230,60,30,6,61,25,125,244,52,18,21,13,7,248,230,78,208,2,230,79,44,0,192,16,245,145,
40,173,0,192,44,16,192,96,21,10,7,248,254,96,165,50,72,169,255,133,50,189,0,2,32,237,253,104,30,129,201,136,
240,29,201,152,240,10,224,248,144,3,32,58,255,232,208,19,169,220,30,21,21,10,7,248,254,21,30,7,248,52,27,0,
72,25,162,88,32,229,253,104,41,15,9,176,201,186,144,2,105,6,108,54,0,201,160,144,2,37,50,132,53,72,32,120,
251,104,164,53,25,49,47,64,25,10,5,25,11,24,177,60,145,66,32,180,252,144,247,25,190,97,52,1,160,63,208,2,
160,255,132,50,25,98,82,62,162,56,160,27,208,8,30,130,54,160,240,165,62,41,15,240,6,9,192,160,0,240,2,169,
253,148,0,149,1,96,234,234,76,21,31,30,67,52,7,169,135,76,237,253,165,72,72,165,69,166,70,164,71,25,110,22,
25,62,27,52,30,52,14,245,3,251,3,98,250,98,250
];

334
src/platform/atarivec.js Normal file
View File

@ -0,0 +1,334 @@
var PRESETS = [
]
AtariVectorPlatform = function(mainElement) {
var self = this;
var cpuFrequency = 1500000.0;
var cpuCyclesPerNMI = 6000;
var cpuCyclesPerFrame = Math.round(cpuFrequency/60);
var cpu, cpuram, dvgram, rom, vecrom, bus, dvg;
var video, audio, timer;
var clock;
var switches = new RAM(16).mem;
var nmicount = cpuCyclesPerNMI;
this.start = function() {
cpu = new jt.M6502();
cpuram = new RAM(0x400);
dvgram = new RAM(0x2000);
vecrom = VECROM;
//switches[5] = 0xff;
//switches[7] = 0xff;
// bus
bus = {
read: function(address) {
address &= 0x7fff;
if (address >= 0x6800 && address <= 0x7fff) {
return rom[address - 0x6800];
} else if (address <= 0x3ff) {
return cpuram.mem[address];
} else if (address >= 0x5000 && address <= 0x5fff) {
return vecrom[address - 0x5000];
} else if (address >= 0x4000 && address <= 0x5fff) {
return dvgram.mem[address - 0x4000];
} else if (address >= 0x2000 && address <= 0x3fff) {
if (address == 0x2001)
return ((clock/500) & 1) ? 0xff : 0x00;
else if (address >= 0x2000 && address <= 0x2007)
return switches[address - 0x2000];
else if (address >= 0x2400 && address <= 0x2407)
return switches[address - 0x2400 + 8];
}
return 0xff;
},
write: function(address, val) {
address &= 0x7fff;
if (address < 0x3ff) {
cpuram.mem[address] = val;
} else if (address >= 0x4000 && address <= 0x5fff) {
dvgram.mem[address - 0x4000] = val;
} else if (address >= 0x3000 && address <= 0x3fff) {
//console.log(address.toString(16), val);
if (address == 0x3000) dvg.runUntilHalt();
// TODO: draw asynchronous or allow poll of HALT ($2002)
}
}
};
cpu.connectBus(bus);
// create video/audio
video = new VectorVideo(mainElement,1024,1024);
dvg = new DVGStateMachine(bus, video);
audio = new SampleAudio(cpuFrequency);
video.start();
timer = new AnimationTimer(60, function() {
video.clear();
// 262.5 scanlines per frame
var iaddr = 0x4000;
var iofs = 0;
breakClock = -1;
clock = 0;
for (var i=0; i<cpuCyclesPerFrame; i++) {
if (debugCondition && breakClock < 0 && debugCondition()) { breakClock = clock; }
clock++;
if (--nmicount == 0) {
//console.log("NMI", cpu.saveState());
var n = cpu.setNMIAndWait();
clock += n;
nmicount = cpuCyclesPerNMI - n;
//console.log(n, clock, nmicount);
}
cpu.clockPulse();
}
});
video.setKeyboardEvents(function(key,flags) {
var KEY2ADDR = {
"Shift": 3,
" ": 4,
"5": 8+0,
"6": 8+1,
"7": 8+2,
"1": 8+3,
"2": 8+4,
ArrowUp: 8+5,
ArrowRight: 8+6,
ArrowLeft: 8+7,
};
var addr = KEY2ADDR[key];
console.log(key,flags,addr);
if (addr >= 0) {
switches[addr] = (flags&1) ? 0xff : 0x00;
}
});
}
this.getOpcodeMetadata = function(opcode, offset) {
return Javatari.getOpcodeMetadata(opcode, offset); // TODO
}
this.loadROM = function(title, data) {
if(data.length != 0x1800) {
throw "ROM length must be == 0x1800";
}
rom = data;
this.reset();
}
this.getRasterPosition = function() {
return {x:0, y:0};
}
this.isRunning = function() {
return timer.isRunning();
}
this.pause = function() {
timer.stop();
}
this.resume = function() {
timer.start();
}
this.reset = function() {
this.clearDebug();
cpu.reset();
}
this.getOriginPC = function() {
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
}
this.readAddress = function(addr) {
return bus.read(addr);
}
var onBreakpointHit;
var debugCondition;
var debugSavedState = null;
var debugBreakState = null;
var debugTargetClock = 0;
var debugClock = 0;
var debugFrameStartClock = 0;
var breakClock;
this.setDebugCondition = function(debugCond) {
if (debugSavedState) {
self.loadState(debugSavedState);
} else {
debugSavedState = self.saveState();
}
debugClock = 0;
debugCondition = debugCond;
self.resume();
}
this.setupDebug = function(callback) {
onBreakpointHit = callback;
}
this.clearDebug = function() {
debugSavedState = null;
debugTargetClock = 0;
debugClock = 0;
debugFrameStartClock = 0;
onBreakpointHit = null;
debugCondition = null;
}
this.breakpointHit = function() {
debugBreakState = self.saveState();
console.log("Breakpoint at clk", debugClock, "PC", debugBreakState.c.PC.toString(16));
this.pause();
if (onBreakpointHit) {
onBreakpointHit(debugBreakState);
}
}
this.step = function() {
var previousPC = -1;
self.setDebugCondition(function() {
if (debugClock++ >= debugTargetClock) {
var thisState = cpu.saveState();
if (previousPC < 0) {
previousPC = thisState.PC;
} else {
if (thisState.PC != previousPC && thisState.T == 0) {
//console.log(previousPC.toString(16), thisPC.toString(16));
debugTargetClock = debugClock-1;
self.breakpointHit();
return true;
}
}
}
return false;
});
}
this.runToPC = function(targetPC) {
var self = this;
self.setDebugCondition(function() {
if (debugClock++ >= debugTargetClock) {
var thisPC = cpu.saveState().PC;
if (thisPC == targetPC) {
self.breakpointHit();
debugTargetClock = debugClock;
return true;
} else {
return false;
}
}
});
}
this.loadState = function(state) {
cpu.loadState(state.c);
cpuram.mem.set(state.cb);
dvgram.mem.set(state.db);
nmicount = state.nmic;
}
this.saveState = function() {
return {
c:cpu.saveState(),
cb:cpuram.mem.slice(0),
db:dvgram.mem.slice(0),
nmic:nmicount
}
}
this.getRAMForState = function(state) {
return state.cb;
}
}
DVGStateMachine = function(bus, video) {
var self = this;
var pc = 0;
var x = 0;
var y = 0;
var gsc = 0;
var bofs = 0x4000;
var pcstack = [];
var running = false;
function readWord(a) {
a &= 0xfff;
return bus.read(a*2+bofs) + (bus.read(a*2+bofs+1) << 8);
}
function decodeSigned(w, o2) {
s = w & (1<<o2);
w = w & ((1<<o2)-1);
if (s)
return -w;
else
return w;
}
this.go = function() {
pc = 0;
gsc = 7;
running = true;
}
this.runUntilHalt = function() {
this.go();
for (var i=0; i<10000; i++) { // TODO
if (!running) break;
this.nextInstruction();
}
//console.log('DVG',i);
}
GSCALES = [7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8];
this.nextInstruction = function() {
if (!running) return;
var w = readWord(pc);
var op = w >> 12;
//console.log(hex(pc), hex(w));
pc++;
switch (op) {
// VEC
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9: { // VCTR
var sc = gsc + 9 - op;
var w2 = readWord(pc++);
var z = w2 >> 12;
var x2 = x + ((decodeSigned(w2, 10) << 7) >> sc);
var y2 = y + ((decodeSigned(w, 10) << 7) >> sc);
video.drawLine(x, y, x2, y2, z);
//console.log(pc.toString(16), w.toString(16), w2.toString(16), gsc, sc, x, y, x2, y2);
x = x2;
y = y2;
break;
}
case 10: { // LABS
var w2 = readWord(pc++);
gsc = GSCALES[w2 >> 12];
x = w2 & 0x3ff;
y = w & 0x3ff;
break;
}
case 11: // HALT
running = false;
break;
case 13: // RTSL
pc = pcstack.pop();
break;
case 12: // JSRL
pcstack.push(pc);
case 14: // JMPL
pc = w & 0xfff;
break;
case 15: { // SVEC
var sc = ((w>>11)&1) + ((w>>2)&2);
sc = gsc - sc - 1;
var x2 = x + ((decodeSigned(w, 2) << 7) >> sc);
var y2 = y + ((decodeSigned(w>>8, 2) << 7) >> sc);
var z = (w >> 4) & 0xf;
video.drawLine(x, y, x2, y2, z);
x = x2;
y = y2;
break;
}
}
}
}

View File

@ -29,6 +29,7 @@ var PRESETS = [
// {id:'examples/fullgame', name:'Thru Hike: The Game', title:'Thru Hike'},
];
Javatari.AUTO_START = false;
Javatari.SHOW_ERRORS = false;
Javatari.CARTRIDGE_CHANGE_DISABLED = true;
Javatari.DEBUG_SCANLINE_OVERFLOW = false; // TODO: make a switch
@ -37,6 +38,10 @@ Javatari.AUDIO_BUFFER_SIZE = 256;
VCSPlatform = function() {
var self = this;
this.start = function() {
Javatari.start();
}
this.loadROM = function(title, data) {
Javatari.loadROM(title, data);
this.current_output = data; // TODO: use bus
@ -74,11 +79,12 @@ VCSPlatform = function() {
Javatari.room.console.powerOn();
}
this.getOriginPC = function() {
return 0xf000; // TODO: read from vector
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
}
this.readAddress = function(addr) {
return current_output[addr - 0xf000]; // TODO: use bus to read
}
this.getRAMForState = function(state) {
return jt.Util.byteStringToUInt8Array(atob(state.r.b));
}
};
platform = new VCSPlatform();

View File

@ -1,4 +1,6 @@
// 8bitworkshop IDE user interface
var worker = new Worker("./src/worker/workermain.js");
var current_output = null;
var current_preset_idx = -1; // TODO: use URL
@ -125,10 +127,6 @@ function _shareFile(e) {
alert("Please fix errors before sharing.");
return true;
}
if (current_preset_idx < 0) {
alert("Can only reset built-in file examples.")
return true;
}
var text = editor.getValue();
console.log("POST",text.length,'bytes');
$.post({
@ -152,8 +150,9 @@ function _shareFile(e) {
}
function _resetPreset(e) {
if (current_preset_idx >= 0
&& confirm("Reset '" + PRESETS[current_preset_idx].name + "' to default?")) {
if (current_preset_idx < 0) {
alert("Can only reset built-in file examples.")
} else if (confirm("Reset '" + PRESETS[current_preset_idx].name + "' to default?")) {
qs['reset'] = '1';
window.location = "?" + $.param(qs);
}
@ -232,7 +231,7 @@ worker.onmessage = function(e) {
editor.clearGutter("gutter-info");
for (info of e.data.listing.errors) {
var div = document.createElement("div");
div.setAttribute("class", "tooltip tooltiperror");
div.setAttribute("class", "tooltipbox tooltiperror");
div.style.color = '#ff3333'; // TODO
div.appendChild(document.createTextNode("\u24cd"));
var tooltip = document.createElement("span");
@ -377,8 +376,8 @@ function showMemory(state) {
if (state) {
s = cpuStateToLongString(state.c);
s += "\n";
var ram = jt.Util.byteStringToUInt8Array(atob(state.r.b));
// TODO: show entire RAM for other platforms
var ram = platform.getRAMForState(state);
// TODO: show scrollable RAM for other platforms
for (var ofs=0; ofs<0x80; ofs+=0x10) {
s += '$' + hex(ofs+0x80) + ':';
for (var i=0; i<0x10; i++) {
@ -636,15 +635,11 @@ function showLoopTimingForCurrentLine() {
}
*/
function reset() {
platform.reset();
}
function resetAndDebug() {
reset();
platform.reset();
runToCursor();
}
}
function setupDebugControls(){
$("#dbg_reset").click(resetAndDebug);
$("#dbg_pause").click(pause);
@ -718,8 +713,10 @@ var qs = (function (a) {
return b;
})(window.location.search.substr(1).split('&'));
// start
setupDebugControls();
showWelcomeMessage();
// parse query string
try {
// is this a share URL?
if (qs['sharekey']) {
@ -753,6 +750,19 @@ try {
var lastid = localStorage.getItem("__lastid");
gotoPresetNamed(lastid || PRESETS[0].id);
}
// load and start platform object
if (qs['platform'] == 'vcs') {
platform = new VCSPlatform();
platform.start();
} else if (qs['platform'] == 'apple2') {
platform = new Apple2Platform($("#emulator")[0]);
platform.start();
} else if (qs['platform'] == 'atarivec') {
platform = new AtariVectorPlatform($("#emulator")[0]);
platform.start();
} else {
alert("Platform " + qs['platform'] + " not recognized");
}
}
} catch (e) {
alert(e+""); // TODO?

207
src/util.js Normal file
View File

@ -0,0 +1,207 @@
function lzgmini() {
// Constants
var LZG_HEADER_SIZE = 16;
var LZG_METHOD_COPY = 0;
var LZG_METHOD_LZG1 = 1;
// LUT for decoding the copy length parameter
var LZG_LENGTH_DECODE_LUT = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
20,21,22,23,24,25,26,27,28,29,35,48,72,128];
// Decoded data (produced by the decode() method)
var outdata = null;
// Calculate the checksum
var calcChecksum = function(data) {
var a = 1;
var b = 0;
var i = LZG_HEADER_SIZE;
while (i < data.length)
{
a = (a + (data[i] & 0xff)) & 0xffff;
b = (b + a) & 0xffff;
i++;
}
return (b << 16) | a;
}
// Decode LZG coded data. The function returns the size of the decoded data.
// Use any of the get* methods to retrieve the decoded data.
this.decode = function(data) {
// Start by clearing the decompressed array in this object
outdata = null;
// Check magic ID
if ((data.length < LZG_HEADER_SIZE) || (data[0] != 76) ||
(data[1] != 90) || (data[2] != 71))
{
return 0;
}
// Calculate & check the checksum
var checksum = ((data[11] & 0xff) << 24) |
((data[12] & 0xff) << 16) |
((data[13] & 0xff) << 8) |
(data[14] & 0xff);
if (calcChecksum(data) != checksum)
{
return 0;
}
// Check which method to use
var method = data[15] & 0xff;
if (method == LZG_METHOD_LZG1)
{
// Get marker symbols
var m1 = data[16] & 0xff;
var m2 = data[17] & 0xff;
var m3 = data[18] & 0xff;
var m4 = data[19] & 0xff;
// Main decompression loop
var symbol, b, b2, b3, len, offset;
var dst = new Array();
var dstlen = 0;
var k = LZG_HEADER_SIZE + 4;
var datalen = data.length;
while (k <= datalen)
{
symbol = data[k++] & 0xff;
if ((symbol != m1) && (symbol != m2) && (symbol != m3) && (symbol != m4))
{
// Literal copy
dst[dstlen++] = symbol;
}
else
{
b = data[k++] & 0xff;
if (b != 0)
{
// Decode offset / length parameters
if (symbol == m1)
{
// marker1 - "Distant copy"
len = LZG_LENGTH_DECODE_LUT[b & 0x1f];
b2 = data[k++] & 0xff;
b3 = data[k++] & 0xff;
offset = (((b & 0xe0) << 11) | (b2 << 8) | b3) + 2056;
}
else if (symbol == m2)
{
// marker2 - "Medium copy"
len = LZG_LENGTH_DECODE_LUT[b & 0x1f];
b2 = data[k++] & 0xff;
offset = (((b & 0xe0) << 3) | b2) + 8;
}
else if (symbol == m3)
{
// marker3 - "Short copy"
len = (b >> 6) + 3;
offset = (b & 63) + 8;
}
else
{
// marker4 - "Near copy (incl. RLE)"
len = LZG_LENGTH_DECODE_LUT[b & 0x1f];
offset = (b >> 5) + 1;
}
// Copy the corresponding data from the history window
for (i = 0; i < len; i++)
{
dst[dstlen] = dst[dstlen-offset];
dstlen++;
}
}
else
{
// Literal copy (single occurance of a marker symbol)
dst[dstlen++] = symbol;
}
}
}
// Store the decompressed data in the lzgmini object for later retrieval
outdata = dst;
return outdata;
}
else if (method == LZG_METHOD_COPY)
{
// Plain copy
var dst = new Array();
var dstlen = 0;
var datalen = data.length;
for (var i = LZG_HEADER_SIZE; i < datalen; i++)
{
dst[dstlen++] = data[i] & 0xff;
}
outdata = dst;
return outdata;
}
else
{
// Unknown method
return null;
}
}
// Get the decoded byte array
this.getByteArray = function()
{
return outdata;
}
// Get the decoded string from a Latin 1 (or ASCII) encoded array
this.getStringLatin1 = function()
{
var str = "";
if (outdata != null)
{
var charLUT = new Array();
for (var i = 0; i < 256; ++i)
charLUT[i] = String.fromCharCode(i);
var outlen = outdata.length;
for (var i = 0; i < outlen; i++)
str += charLUT[outdata[i]];
}
return str;
}
// Get the decoded string from an UTF-8 encoded array
this.getStringUTF8 = function()
{
var str = "";
if (outdata != null)
{
var charLUT = new Array();
for (var i = 0; i < 128; ++i)
charLUT[i] = String.fromCharCode(i);
var c;
var outlen = outdata.length;
for (var i = 0; i < outlen;)
{
c = outdata[i++];
if (c < 128)
{
str += charLUT[c];
}
else
{
if ((c > 191) && (c < 224))
{
c = ((c & 31) << 6) | (outdata[i++] & 63);
}
else
{
c = ((c & 15) << 12) | ((outdata[i] & 63) << 6) | (outdata[i+1] & 63);
i += 2;
}
str += String.fromCharCode(c);
}
}
}
return str;
}
}