vicdual platform

This commit is contained in:
Steven Hugg 2017-01-17 20:15:46 -05:00
parent dfbd584207
commit cc9872e007
4 changed files with 299 additions and 204 deletions

View File

@ -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;

View File

@ -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
View 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
View 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);
});
});