mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-31 10:30:29 +00:00
vicdual platform
This commit is contained in:
parent
dfbd584207
commit
cc9872e007
@ -1,196 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var EXIDY_PRESETS = [
|
||||
];
|
||||
|
||||
var ExidyPlatform = function(mainElement) {
|
||||
var self = this;
|
||||
var cpuFrequency = 705562;
|
||||
var cyclesPerFrame = Math.round(cpuFrequency/60);
|
||||
var vblankCyclesPerFrame = Math.round(cpuFrequency*3/(262.5*60));
|
||||
var cpu, ram, bus, rom;
|
||||
var video, ap2disp, audio, timer;
|
||||
var port_dsw = 0;
|
||||
var PGM_BASE = 0x1000; // where to JMP after pr#6
|
||||
|
||||
this.getPresets = function() {
|
||||
return EXIDY_PRESETS;
|
||||
}
|
||||
|
||||
this.start = function() {
|
||||
cpu = new jt.M6502();
|
||||
ram = new RAM(0x200); // 64K + 16K LC RAM - 4K hardware
|
||||
rom = new RAM(0x1000);
|
||||
// bus
|
||||
bus = {
|
||||
read: function(address) {
|
||||
address &= 0xffff;
|
||||
if (address < ram.mem.length) {
|
||||
return ram.mem[address];
|
||||
} else if (address >= 0x1000 && address <= 0x1fff) {
|
||||
return rom.mem[address - 0x1000];
|
||||
} else if (address >= 0xf000) {
|
||||
return rom.mem[address - 0xf000];
|
||||
} else if (address == 0xc000) {
|
||||
return port_dsw;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: function(address, val) {
|
||||
address &= 0xffff;
|
||||
val &= 0xff;
|
||||
if (address < ram.mem.length) {
|
||||
ram.mem[address] = val;
|
||||
}
|
||||
}
|
||||
};
|
||||
cpu.connectBus(bus);
|
||||
// create video/audio
|
||||
video = new RasterVideo(mainElement,256,256);
|
||||
video.create();
|
||||
audio = new SampleAudio(cpuFrequency); // TODO
|
||||
var idata = video.getFrameData();
|
||||
timer = new AnimationTimer(60, function() {
|
||||
var clock = 0;
|
||||
breakClock = -1;
|
||||
for (var i=0; i<cyclesPerFrame; i++) {
|
||||
if (debugCondition && breakClock < 0 && debugCondition()) { breakClock = clock; }
|
||||
clock++;
|
||||
cpu.clockPulse();
|
||||
port_dsw = (i < vblankCyclesPerFrame) ? 0x80 : 0x0;
|
||||
}
|
||||
video.updateFrame();
|
||||
});
|
||||
// TODO: reset debug state
|
||||
}
|
||||
|
||||
// TODO: refactor into base
|
||||
|
||||
this.getOpcodeMetadata = function(opcode, offset) {
|
||||
return Javatari.getOpcodeMetadata(opcode, offset); // TODO
|
||||
}
|
||||
|
||||
this.loadROM = function(title, data) {
|
||||
this.reset();
|
||||
if(data.length != 0x1000) {
|
||||
throw "ROM length must be == 0x1000";
|
||||
}
|
||||
rom.mem.set(data);
|
||||
}
|
||||
|
||||
this.getRasterPosition = function() {
|
||||
return {x:0, y:0};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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();
|
||||
debugBreakState.c.PC = (debugBreakState.c.PC-1) & 0xffff;
|
||||
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.runEval = function(evalfunc) {
|
||||
var self = this;
|
||||
self.setDebugCondition(function() {
|
||||
if (debugClock++ > debugTargetClock) {
|
||||
var cpuState = cpu.saveState();
|
||||
cpuState.PC = (cpuState.PC-1)&0xffff;
|
||||
if (evalfunc(cpuState)) {
|
||||
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),
|
||||
};
|
||||
}
|
||||
this.getRAMForState = function(state) {
|
||||
return ram.mem;
|
||||
}
|
||||
};
|
||||
|
||||
PLATFORMS['exidy'] = ExidyPlatform;
|
@ -1,4 +1,3 @@
|
||||
|
||||
"use strict";
|
||||
var GALAXIAN_PRESETS = [
|
||||
];
|
||||
@ -204,6 +203,7 @@ var GalaxianPlatform = function(mainElement) {
|
||||
return;
|
||||
var debugCond = self.getDebugCallback();
|
||||
var targetTstates = cpu.getTstates();
|
||||
// TODO: get raster position
|
||||
for (var sl=0; sl<scanlinesPerFrame; sl++) {
|
||||
drawScanline(pixels, sl);
|
||||
targetTstates += cpuCyclesPerLine;
|
||||
@ -319,13 +319,7 @@ var GalaxianPlatform = function(mainElement) {
|
||||
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);
|
||||
return membus.read(addr); // TODO?
|
||||
}
|
||||
}
|
||||
|
||||
|
190
src/platform/vicdual.js
Normal file
190
src/platform/vicdual.js
Normal file
@ -0,0 +1,190 @@
|
||||
"use strict";
|
||||
var VICDUAL_PRESETS = [
|
||||
];
|
||||
|
||||
// TODO: global???
|
||||
window.buildZ80({
|
||||
applyContention: false
|
||||
});
|
||||
|
||||
var VicDualPlatform = function(mainElement) {
|
||||
var self = this;
|
||||
this.__proto__ = new BaseZ80Platform();
|
||||
|
||||
var cpu, ram, membus, iobus, rom;
|
||||
var video, audio, timer, pixels;
|
||||
var inputs = [0xff, 0xff, 0xff, 0xff]; // most things active low
|
||||
|
||||
var XTAL = 15468000.0;
|
||||
var scanlinesPerFrame = 0x106;
|
||||
var vsyncStart = 0xec;
|
||||
var vsyncEnd = 0xf0;
|
||||
var cpuFrequency = XTAL/8;
|
||||
var hsyncFrequency = XTAL/3/scanlinesPerFrame;
|
||||
var vsyncFrequency = hsyncFrequency/0x148;
|
||||
var cpuCyclesPerLine = cpuFrequency/hsyncFrequency;
|
||||
var timerFrequency = 500; // TODO
|
||||
|
||||
// TODO: programmable palette
|
||||
var palette = [
|
||||
0xffcccccc,
|
||||
0xff00ffff, // yellow
|
||||
0xff0000ff, // red
|
||||
0xff00ffff, // yellow
|
||||
0xffffff00, // cyan
|
||||
0xff00ffff, // yellow 2
|
||||
0xff00ff00, // green
|
||||
0xffffffff // white
|
||||
];
|
||||
|
||||
function drawScanline(pixels, sl) {
|
||||
if (sl >= 224) return;
|
||||
var pixofs = sl*256;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
var vramofs = (sl>>3)<<5; // offset in VRAM
|
||||
var yy = sl & 7; // y offset within tile
|
||||
for (var xx=0; xx<32; xx++) {
|
||||
var attrib = ram.mem[vramofs+xx];
|
||||
var data = ram.mem[0x800 + (attrib<<3) + yy];
|
||||
var col = (attrib>>5); // + (palbank<<3);
|
||||
var color1 = 0xff000000; // TODO
|
||||
var color2 = palette[col & 0x7]; // TODO
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
pixels[outi] = (data&bm) ? color2 : color1;
|
||||
outi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var KEYCODE_MAP = {
|
||||
37:{i:1,m:0x10,a:0}, // left arrow (P1)
|
||||
39:{i:1,m:0x20,a:0}, // right arrow (P1)
|
||||
49:{i:2,m:0x10,a:0}, // 1
|
||||
32:{i:2,m:0x20,a:0}, // space bar (P1)
|
||||
53:{i:3,m:0x08,a:1}, // 5
|
||||
50:{i:3,m:0x20,a:0}, // 2
|
||||
}
|
||||
|
||||
this.getPresets = function() {
|
||||
return VICDUAL_PRESETS;
|
||||
}
|
||||
|
||||
this.start = function() {
|
||||
ram = new RAM(0x1000);
|
||||
membus = {
|
||||
read: new AddressDecoder([
|
||||
[0x0000, 0x7fff, 0x3fff, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x8000, 0xffff, 0x0fff, function(a) { return ram.mem[a]; }],
|
||||
]),
|
||||
write: new AddressDecoder([
|
||||
[0x8000, 0xffff, 0x0fff, function(a,v) { ram.mem[a] = v; }],
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
iobus = {
|
||||
read: function(addr) { return inputs[addr&3]; },
|
||||
write: function(addr, val) {
|
||||
if (addr & 0x1) { }; // audio 1
|
||||
if (addr & 0x2) { }; // audio 2
|
||||
if (addr & 0x8) { }; // coin status
|
||||
if (addr & 0x40) { }; // palette
|
||||
}
|
||||
};
|
||||
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^o.a) & 1) {
|
||||
inputs[o.i] &= ~o.m;
|
||||
} else {
|
||||
inputs[o.i] |= o.m;
|
||||
}
|
||||
// reset CPU when coin inserted
|
||||
if (o.i==3 && o.m==0x8) cpu.reset();
|
||||
}
|
||||
});
|
||||
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 (sl == vsyncStart) inputs[1] |= 0x8;
|
||||
if (sl == vsyncEnd) inputs[1] &= ~0x8;
|
||||
if (debugCond) {
|
||||
while (cpu.getTstates() < targetTstates) {
|
||||
if (debugCond && debugCond()) { debugCond = null; }
|
||||
cpu.runFrame(cpu.getTstates() + 1);
|
||||
}
|
||||
} else {
|
||||
cpu.runFrame(targetTstates);
|
||||
}
|
||||
}
|
||||
video.updateFrame();
|
||||
self.restartDebugState();
|
||||
});
|
||||
}
|
||||
|
||||
this.loadROM = function(title, data) {
|
||||
rom = padBytes(data, 0x4000);
|
||||
self.reset();
|
||||
}
|
||||
|
||||
this.loadState = function(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
inputs[3] = state.in3;
|
||||
}
|
||||
this.saveState = function() {
|
||||
return {
|
||||
c:self.getCPUState(),
|
||||
b:ram.mem.slice(0),
|
||||
in0:inputs[0],
|
||||
in1:inputs[1],
|
||||
in2:inputs[2],
|
||||
in3:inputs[3],
|
||||
};
|
||||
}
|
||||
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();
|
||||
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
|
||||
}
|
||||
this.readAddress = function(addr) {
|
||||
return membus.read(addr); // TODO?
|
||||
}
|
||||
}
|
||||
|
||||
PLATFORMS['vicdual'] = VicDualPlatform;
|
107
test/cli/worker.js
Normal file
107
test/cli/worker.js
Normal file
@ -0,0 +1,107 @@
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var vm = require('vm');
|
||||
var worker = {};
|
||||
|
||||
var includeInThisContext = function(path) {
|
||||
var code = fs.readFileSync(path);
|
||||
vm.runInThisContext(code, path);
|
||||
};
|
||||
|
||||
global.importScripts = function(path) {
|
||||
includeInThisContext('src/worker/'+path);
|
||||
}
|
||||
|
||||
function Blob(blob) {
|
||||
this.size = blob.length;
|
||||
this.length = blob.length;
|
||||
this.slice = function(a,b) {
|
||||
var data = blob.slice(a,b);
|
||||
var b = new Blob(data);
|
||||
//console.log(a, b, data.length, data.slice(0,64));
|
||||
//console.log(new Error().stack);
|
||||
return b;
|
||||
}
|
||||
this.asArrayBuffer = function() {
|
||||
var buf = new ArrayBuffer(blob.length);
|
||||
var arr = new Uint8Array(buf);
|
||||
for (var i=0; i<blob.length; i++)
|
||||
arr[i] = blob[i].charCodeAt(0);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
global.XMLHttpRequest = function() {
|
||||
this.open = function(a,b,c) {
|
||||
if (this.responseType == 'json') {
|
||||
var txt = fs.readFileSync('src/worker/'+b);
|
||||
this.response = JSON.parse(txt);
|
||||
} else if (this.responseType == 'blob') {
|
||||
var data = fs.readFileSync('src/worker/'+b, {encoding:'binary'});
|
||||
//var buf = new ArrayBuffer(data.length);
|
||||
//var blob = new Uint8Array(buf);
|
||||
//blob.set(data);
|
||||
this.response = new Blob(data);
|
||||
}
|
||||
}
|
||||
this.send = function() { }
|
||||
}
|
||||
|
||||
global.FileReaderSync = function() {
|
||||
this.readAsArrayBuffer = function(blob) {
|
||||
return blob.asArrayBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
global.onmessage = null;
|
||||
global.postMessage = null;
|
||||
|
||||
includeInThisContext("src/worker/workermain.js");
|
||||
|
||||
function compile(tool, code, platform, callback, outlen, nlines, nerrors) {
|
||||
global.postMessage = function(msg) {
|
||||
if (msg.errors && msg.errors.length) {
|
||||
console.log(msg.errors);
|
||||
assert.equal(nerrors, msg.errors.length, "errors");
|
||||
} else {
|
||||
assert.equal(nerrors||0, 0, "errors");
|
||||
assert.equal(msg.output.length, outlen, "output binary");
|
||||
assert.equal(msg.lines.length, nlines, "listing lines");
|
||||
}
|
||||
callback(null, msg);
|
||||
};
|
||||
global.onmessage({
|
||||
data:{code:code, platform:platform, tool:tool}
|
||||
});
|
||||
}
|
||||
|
||||
describe('Worker', function() {
|
||||
it('should assemble DASM', function(done) {
|
||||
compile('dasm', '\tprocessor 6502\n\torg $f000\nfoo lda #0\n', 'vcs', done, 2, 1);
|
||||
});
|
||||
it('should assemble ACME', function(done) {
|
||||
compile('acme', 'foo: lda #0\n', 'vcs', done, 2, 0); // TODO
|
||||
});
|
||||
it('should compile PLASMA', function(done) {
|
||||
compile('plasm', 'word x = 0', 'apple2', 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}', 'apple2', done, 2947, 4);
|
||||
});
|
||||
it('should NOT assemble Z80ASM', function(done) {
|
||||
compile('z80asm', 'ddwiuweq', 'none', done, 0, 0, 1);
|
||||
});
|
||||
it('should assemble Z80ASM', function(done) {
|
||||
compile('z80asm', '\tMODULE test\n\tXREF _puts\n\tld hl,$0000\n\tret\n', 'spaceinv', done, 4, 2, 0);
|
||||
});
|
||||
it('should NOT compile SDCC', function(done) {
|
||||
compile('sdcc', 'foobar', 'spaceinv', done, 0, 0, 1);
|
||||
});
|
||||
it('should assemble SDASZ80', function(done) {
|
||||
compile('sdcc', '\tMODULE test\n\tXREF _puts\n\tld hl,$0000\n\tret\n', 'spaceinv', done, 8192, 2, 1);
|
||||
});
|
||||
it('should compile SDCC', function(done) {
|
||||
compile('sdcc', 'int foo=0;\nint main(int argc) {\nint x=1;\nint y=2+argc;\nreturn x+y+argc;\n}', 'spaceinv', done, 8192, 3, 0);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user