2017-01-03 15:43:40 +00:00
|
|
|
"use strict";
|
2017-01-03 01:42:15 +00:00
|
|
|
|
2019-10-26 01:55:50 +00:00
|
|
|
import { Platform, BaseZ80Platform, Base6502Platform } from "../common/baseplatform";
|
|
|
|
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, VectorVideo, Keys, makeKeycodeMap } from "../common/emu";
|
|
|
|
import { hex } from "../common/util";
|
|
|
|
import { MasterAudio, POKEYDeviceChannel, newPOKEYAudio } from "../common/audio";
|
2018-08-16 23:19:20 +00:00
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
// http://www.computerarcheology.com/Arcade/Asteroids/DVG.html
|
|
|
|
// http://arcarc.xmission.com/Tech/neilw_xy.txt
|
|
|
|
|
2017-02-01 18:21:17 +00:00
|
|
|
var VECTOR_PRESETS = [
|
2017-03-24 22:10:35 +00:00
|
|
|
{id:'font.c', name:'Vector Fonts'},
|
|
|
|
{id:'threed.c', name:'3D Transformations'},
|
2017-03-27 15:21:17 +00:00
|
|
|
{id:'game.c', name:'Space Game'},
|
2017-04-20 13:45:35 +00:00
|
|
|
{id:'music.c', name:'POKEY Music'},
|
2017-01-03 01:42:15 +00:00
|
|
|
]
|
|
|
|
|
2017-02-01 18:21:17 +00:00
|
|
|
var ASTEROIDS_KEYCODE_MAP = makeKeycodeMap([
|
2019-06-07 13:45:30 +00:00
|
|
|
[Keys.B, 3, 0xff],
|
|
|
|
[Keys.A, 4, 0xff],
|
|
|
|
[Keys.SELECT, 8, 0xff],
|
|
|
|
[Keys.VK_6, 9, 0xff],
|
|
|
|
[Keys.VK_7, 10, 0xff],
|
|
|
|
[Keys.START, 11, 0xff],
|
|
|
|
[Keys.P2_START, 12, 0xff],
|
|
|
|
[Keys.UP, 13, 0xff],
|
|
|
|
[Keys.RIGHT, 14, 0xff],
|
|
|
|
[Keys.LEFT, 15, 0xff],
|
2017-02-01 18:21:17 +00:00
|
|
|
]);
|
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
var GRAVITAR_KEYCODE_MAP = makeKeycodeMap([
|
2019-06-07 13:45:30 +00:00
|
|
|
[Keys.B, 1, -0x1],
|
|
|
|
[Keys.A, 1, -0x2],
|
|
|
|
[Keys.VK_5, 0, 0x2],
|
|
|
|
[Keys.VK_6, 0, 0x1],
|
|
|
|
[Keys.START, 2, 0x20],
|
|
|
|
[Keys.P2_START, 2, 0x40],
|
|
|
|
[Keys.UP, 1, -0x10],
|
|
|
|
[Keys.DOWN, 1, -0x20],
|
|
|
|
[Keys.RIGHT, 1, -0x4],
|
|
|
|
[Keys.LEFT, 1, -0x8],
|
2017-02-05 04:19:54 +00:00
|
|
|
]);
|
2017-02-01 18:21:17 +00:00
|
|
|
|
2017-01-03 15:43:40 +00:00
|
|
|
var AtariVectorPlatform = function(mainElement) {
|
2018-08-18 13:52:17 +00:00
|
|
|
var XTAL = 12096000;
|
|
|
|
var cpuFrequency = XTAL/8;
|
|
|
|
var cpuCyclesPer3khz = Math.round(cpuFrequency/(XTAL/4096)); // ~3 Khz
|
|
|
|
var cpuCyclesPerNMI = Math.round(cpuFrequency*12/(XTAL/4096)); // ~250 Hz
|
2017-01-03 01:42:15 +00:00
|
|
|
var cpuCyclesPerFrame = Math.round(cpuFrequency/60);
|
|
|
|
var cpu, cpuram, dvgram, rom, vecrom, bus, dvg;
|
|
|
|
var video, audio, timer;
|
|
|
|
var clock;
|
2018-08-18 13:52:17 +00:00
|
|
|
var watchdog = 0;
|
2017-01-03 01:42:15 +00:00
|
|
|
var switches = new RAM(16).mem;
|
|
|
|
var nmicount = cpuCyclesPerNMI;
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
this.__proto__ = new (Base6502Platform as any)();
|
2017-01-07 18:05:02 +00:00
|
|
|
|
2017-01-03 15:43:40 +00:00
|
|
|
this.getPresets = function() {
|
2017-02-01 18:21:17 +00:00
|
|
|
return VECTOR_PRESETS;
|
2017-01-03 15:43:40 +00:00
|
|
|
}
|
|
|
|
|
2017-01-03 01:42:15 +00:00
|
|
|
this.start = function() {
|
|
|
|
cpuram = new RAM(0x400);
|
|
|
|
dvgram = new RAM(0x2000);
|
2018-08-18 13:52:17 +00:00
|
|
|
//switches[5] = 0xff;
|
|
|
|
//switches[7] = 0xff;
|
2017-01-03 01:42:15 +00:00
|
|
|
// bus
|
|
|
|
bus = {
|
2017-01-21 13:13:36 +00:00
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
read: newAddressDecoder([
|
2017-01-21 13:13:36 +00:00
|
|
|
[0x0, 0x3ff, 0x3ff, function(a) { return cpuram.mem[a]; }],
|
2018-08-18 13:52:17 +00:00
|
|
|
[0x2001, 0x2001, 0, function(a) { return ((clock / cpuCyclesPer3khz) & 1) ? 0xff : 0x00; }],
|
2017-01-21 13:13:36 +00:00
|
|
|
[0x2000, 0x2007, 0x7, function(a) { return switches[a]; }],
|
|
|
|
[0x2400, 0x2407, 0x7, function(a) { return switches[a+8]; }],
|
|
|
|
[0x4000, 0x4fff, 0xfff, function(a) { return dvgram.mem[a]; }],
|
|
|
|
[0x5000, 0x5fff, 0xfff, function(a) { return vecrom[a]; }],
|
2020-01-28 15:50:38 +00:00
|
|
|
[0x6800, 0x7fff, 0, function(a) { return rom ? rom[a - 0x6800] : 0; }],
|
2017-01-21 13:13:36 +00:00
|
|
|
], {gmask:0x7fff}),
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
write: newAddressDecoder([
|
2017-01-21 13:13:36 +00:00
|
|
|
[0x0, 0x3ff, 0x3ff, function(a,v) { cpuram.mem[a] = v; }],
|
2017-02-05 04:19:54 +00:00
|
|
|
[0x3000, 0x3000, 0, function(a,v) { dvg.runUntilHalt(0); }],
|
2018-08-18 13:52:17 +00:00
|
|
|
[0x3400, 0x3400, 0, function(a,v) { watchdog = 0; }],
|
2017-01-21 13:13:36 +00:00
|
|
|
// TODO: draw asynchronous or allow poll of HALT ($2002)
|
|
|
|
[0x4000, 0x5fff, 0x1fff, function(a,v) { dvgram.mem[a] = v; }],
|
|
|
|
], {gmask:0x7fff})
|
|
|
|
|
2017-01-03 01:42:15 +00:00
|
|
|
};
|
2017-04-22 01:56:49 +00:00
|
|
|
this.readAddress = bus.read;
|
2018-11-22 12:39:06 +00:00
|
|
|
cpu = this.newCPU(bus);
|
2017-01-03 01:42:15 +00:00
|
|
|
// create video/audio
|
|
|
|
video = new VectorVideo(mainElement,1024,1024);
|
2017-02-05 04:19:54 +00:00
|
|
|
dvg = new DVGBWStateMachine(bus, video, 0x4000);
|
2019-05-26 23:22:51 +00:00
|
|
|
audio = newPOKEYAudio(2);
|
2017-01-14 02:31:04 +00:00
|
|
|
video.create();
|
2018-08-23 23:45:36 +00:00
|
|
|
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
|
|
|
setKeyboardFromMap(video, switches, ASTEROIDS_KEYCODE_MAP);
|
|
|
|
}
|
2018-11-22 12:39:06 +00:00
|
|
|
|
|
|
|
this.advance = (novideo) => {
|
2018-08-23 23:45:36 +00:00
|
|
|
if (!novideo) video.clear();
|
2018-11-22 12:39:06 +00:00
|
|
|
var debugCond = this.getDebugCallback();
|
2017-01-03 01:42:15 +00:00
|
|
|
clock = 0;
|
|
|
|
for (var i=0; i<cpuCyclesPerFrame; i++) {
|
2018-08-25 02:55:16 +00:00
|
|
|
if (debugCond && debugCond()) {
|
|
|
|
debugCond = null;
|
|
|
|
break;
|
|
|
|
}
|
2017-01-03 01:42:15 +00:00
|
|
|
clock++;
|
2017-02-01 18:21:17 +00:00
|
|
|
if (--nmicount == 0) {
|
2017-01-03 01:42:15 +00:00
|
|
|
//console.log("NMI", cpu.saveState());
|
|
|
|
var n = cpu.setNMIAndWait();
|
|
|
|
clock += n;
|
|
|
|
nmicount = cpuCyclesPerNMI - n;
|
|
|
|
}
|
|
|
|
cpu.clockPulse();
|
2017-01-03 15:43:40 +00:00
|
|
|
//cpu.executeInstruction();
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
2018-08-18 13:52:17 +00:00
|
|
|
//if (++watchdog == 256) { watchdog = 0; cpu.reset(); }
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.loadROM = function(title, data) {
|
2017-01-03 15:43:40 +00:00
|
|
|
if(data.length != 0x2000) {
|
2019-11-13 20:45:18 +00:00
|
|
|
throw Error("ROM length must be == 0x2000");
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
2017-01-03 15:43:40 +00:00
|
|
|
rom = data.slice(0,0x1800);
|
|
|
|
vecrom = data.slice(0x1800,0x2000);
|
2017-01-03 01:42:15 +00:00
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isRunning = function() {
|
2017-04-19 01:18:53 +00:00
|
|
|
return timer && timer.isRunning();
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
|
|
|
this.pause = function() {
|
|
|
|
timer.stop();
|
2017-02-12 06:07:35 +00:00
|
|
|
audio.stop();
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
|
|
|
this.resume = function() {
|
|
|
|
timer.start();
|
2017-02-12 06:07:35 +00:00
|
|
|
audio.start();
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
|
|
|
this.reset = function() {
|
|
|
|
this.clearDebug();
|
|
|
|
cpu.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadState = function(state) {
|
|
|
|
cpu.loadState(state.c);
|
2018-08-18 13:52:17 +00:00
|
|
|
cpuram.mem.set(state.b);
|
2017-01-03 01:42:15 +00:00
|
|
|
dvgram.mem.set(state.db);
|
2017-03-28 16:22:33 +00:00
|
|
|
switches.set(state.sw);
|
2017-01-03 01:42:15 +00:00
|
|
|
nmicount = state.nmic;
|
|
|
|
}
|
|
|
|
this.saveState = function() {
|
|
|
|
return {
|
|
|
|
c:cpu.saveState(),
|
2018-08-18 13:52:17 +00:00
|
|
|
b:cpuram.mem.slice(0),
|
2017-01-03 01:42:15 +00:00
|
|
|
db:dvgram.mem.slice(0),
|
2017-03-28 16:22:33 +00:00
|
|
|
sw:switches.slice(0),
|
2017-01-03 01:42:15 +00:00
|
|
|
nmic:nmicount
|
|
|
|
}
|
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
this.loadControlsState = function(state) {
|
|
|
|
switches.set(state.sw);
|
|
|
|
}
|
|
|
|
this.saveControlsState = function() {
|
|
|
|
return {
|
|
|
|
sw:switches.slice(0),
|
|
|
|
}
|
|
|
|
}
|
2017-01-07 18:05:02 +00:00
|
|
|
this.getCPUState = function() {
|
|
|
|
return cpu.saveState();
|
|
|
|
}
|
2019-08-27 16:12:56 +00:00
|
|
|
this.getMemoryMap = function() { return { main:[
|
|
|
|
{name:'Switches/POKEY I/O',start:0x7800,size:0x1000,type:'io'},
|
|
|
|
{name:'DVG I/O',start:0x8800,size:0x100,type:'io'},
|
|
|
|
{name:'EAROM',start:0x8900,size:0x100,type:'ram'},
|
|
|
|
] } };
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
var AtariColorVectorPlatform = function(mainElement) {
|
|
|
|
var masterFrequency = 12096000.0;
|
|
|
|
var cpuFrequency = masterFrequency / 8;
|
|
|
|
var nmiFrequency = masterFrequency / 4096 / 12;
|
|
|
|
var cpuCyclesPerNMI = Math.round(cpuFrequency / nmiFrequency);
|
|
|
|
var cpuCyclesPerFrame = Math.round(cpuFrequency / 60);
|
|
|
|
var cpu, cpuram, dvgram, rom, vecrom, bus, dvg, earom;
|
|
|
|
var video, audio, timer;
|
|
|
|
var clock;
|
|
|
|
var switches = new RAM(16).mem;
|
|
|
|
var nmicount = cpuCyclesPerNMI;
|
|
|
|
var earom_offset, earom_data;
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
this.__proto__ = new (Base6502Platform as any)();
|
2019-04-24 20:56:53 +00:00
|
|
|
//this.debugPCDelta = 0;
|
2017-02-05 04:19:54 +00:00
|
|
|
|
|
|
|
this.getPresets = function() {
|
|
|
|
return VECTOR_PRESETS;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.start = function() {
|
|
|
|
cpuram = new RAM(0x800);
|
|
|
|
dvgram = new RAM(0x2000);
|
|
|
|
earom = new RAM(0x40);
|
2017-04-23 13:03:05 +00:00
|
|
|
//rom = padBytes(new lzgmini().decode(GRAVITAR_ROM).slice(0), 0x7000+1);
|
|
|
|
//vecrom = padBytes(new lzgmini().decode(GRAVITAR_VECROM).slice(0), 0x6000-0x2800+1);
|
2017-03-27 15:21:17 +00:00
|
|
|
switches[0] = 0x0;
|
2017-02-05 04:19:54 +00:00
|
|
|
switches[1] = 0xff;
|
2017-03-27 15:21:17 +00:00
|
|
|
switches[2] = 0x0;
|
2017-02-05 04:19:54 +00:00
|
|
|
// bus
|
|
|
|
bus = {
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
read: newAddressDecoder([
|
2017-02-05 04:19:54 +00:00
|
|
|
[0x0, 0x7ff, 0x7ff, function(a) { return cpuram.mem[a]; }],
|
|
|
|
[0x2000, 0x27ff, 0x7ff, function(a) { return dvgram.mem[a]; }],
|
|
|
|
[0x2800, 0x5fff, 0x7fff, function(a) { return vecrom[a - 0x2800]; }],
|
|
|
|
//[0x2001, 0x2001, 0, function(a) { return ((clock/500) & 1) ? 0xff : 0x00; }],
|
|
|
|
//[0x6000, 0x67ff, 0x7ff, function(a) { /* pokey1 */ return 0; }],
|
|
|
|
//[0x6800, 0x6fff, 0x7ff, function(a) { /* pokey2 */ return 0; }],
|
|
|
|
[0x7800, 0x7800, 0, function(a) { return switches[0]; }],
|
|
|
|
[0x8000, 0x8000, 0, function(a) { return switches[1]; }],
|
|
|
|
[0x8800, 0x8800, 0, function(a) { return switches[2]; }],
|
|
|
|
//[0x7000, 0x7000, 0, function(a) { /* EAROM read */ return 0; }],
|
|
|
|
//[0x8940, 0x897f, 0x3f, function(a) { /* EAROM data */ return 0; }],
|
|
|
|
[0x8900, 0x8900, 0, function(a) { /* EAROM read */ return earom_data; }],
|
2020-01-28 15:50:38 +00:00
|
|
|
[0x9000, 0xffff, 0xffff, function(a) { return rom ? rom[a - 0x9000] : 0; }],
|
2017-02-05 04:19:54 +00:00
|
|
|
]),
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
write: newAddressDecoder([
|
2017-02-05 04:19:54 +00:00
|
|
|
[0x0, 0x7ff, 0x7ff, function(a,v) { cpuram.mem[a] = v; }],
|
|
|
|
[0x2000, 0x27ff, 0x7ff, function(a,v) { dvgram.mem[a] = v; }],
|
2019-04-24 20:56:53 +00:00
|
|
|
[0x2800, 0x5fff, 0x7fff, function(a,v) { vecrom[a - 0x2800] = v; }], // TODO: remove (it's ROM!)
|
2017-02-12 06:07:35 +00:00
|
|
|
[0x6000, 0x67ff, 0xf, function(a,v) { audio.pokey1.setRegister(a, v); }],
|
|
|
|
[0x6800, 0x6fff, 0xf, function(a,v) { audio.pokey2.setRegister(a, v); }],
|
2017-02-05 04:19:54 +00:00
|
|
|
[0x8800, 0x8800, 0, function(a,v) { /* LEDs, etc */ }],
|
|
|
|
[0x8840, 0x8840, 0, function(a,v) { dvg.runUntilHalt(0); }],
|
|
|
|
[0x8880, 0x8880, 0, function(a,v) { dvg.reset(); }],
|
|
|
|
[0x88c0, 0x88c0, 0, function(a,v) { /* IRQ ACK */ }],
|
|
|
|
[0x8900, 0x8900, 0, function(a,v) { /* EAROM ctrl */
|
|
|
|
if (v == 9) earom_data=earom.mem[earom_offset];
|
|
|
|
if (v == 12) earom.mem[earom_offset]=earom_data;
|
|
|
|
}],
|
|
|
|
[0x8940, 0x897f, 0x3f, function(a,v) { /* EAROM data */ earom_offset = a; earom_data = v; }],
|
|
|
|
[0x8980, 0x8980, 0, function(a,v) { /* TODO: watchdog */ }],
|
|
|
|
// TODO: draw asynchronous or allow poll of HALT ($2002)
|
|
|
|
//[0, 0xffff, 0, function(a,v) { console.log(hex(a,4),hex(v,2)); }],
|
|
|
|
])
|
|
|
|
|
|
|
|
};
|
2017-04-22 01:56:49 +00:00
|
|
|
this.readAddress = bus.read;
|
2018-11-22 12:39:06 +00:00
|
|
|
cpu = this.newCPU(bus);
|
2017-02-05 04:19:54 +00:00
|
|
|
// create video/audio
|
|
|
|
video = new VectorVideo(mainElement,1024,1024);
|
|
|
|
dvg = new DVGColorStateMachine(bus, video, 0x2000);
|
2019-05-26 23:22:51 +00:00
|
|
|
audio = newPOKEYAudio(2);
|
2017-02-05 04:19:54 +00:00
|
|
|
video.create();
|
2018-08-23 23:45:36 +00:00
|
|
|
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
|
|
|
setKeyboardFromMap(video, switches, GRAVITAR_KEYCODE_MAP);
|
|
|
|
}
|
2018-11-22 12:39:06 +00:00
|
|
|
|
|
|
|
this.advance = (novideo) => {
|
2018-08-23 23:45:36 +00:00
|
|
|
if (!novideo) video.clear();
|
2018-11-22 12:39:06 +00:00
|
|
|
var debugCond = this.getDebugCallback();
|
2017-02-05 04:19:54 +00:00
|
|
|
clock = 0;
|
|
|
|
for (var i=0; i<cpuCyclesPerFrame; i++) {
|
2018-08-25 02:55:16 +00:00
|
|
|
if (debugCond && debugCond()) {
|
|
|
|
debugCond = null;
|
|
|
|
break;
|
|
|
|
}
|
2017-02-05 04:19:54 +00:00
|
|
|
clock++;
|
|
|
|
if (--nmicount == 0) {
|
|
|
|
//console.log("NMI", cpu.saveState());
|
|
|
|
var n = cpu.setIRQAndWait(); // TODO: only if I flag set
|
|
|
|
clock += n;
|
|
|
|
nmicount = cpuCyclesPerNMI - n;
|
|
|
|
//console.log(n, clock, nmicount);
|
|
|
|
}
|
|
|
|
cpu.clockPulse();
|
|
|
|
//cpu.executeInstruction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadROM = function(title, data) {
|
2019-04-24 20:56:53 +00:00
|
|
|
rom = data.slice(0, 0x7000);
|
|
|
|
vecrom = padBytes(data.slice(0x7000), 0x3800);
|
2017-02-05 04:19:54 +00:00
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isRunning = function() {
|
2017-04-19 01:18:53 +00:00
|
|
|
return timer && timer.isRunning();
|
2017-02-05 04:19:54 +00:00
|
|
|
}
|
|
|
|
this.pause = function() {
|
|
|
|
timer.stop();
|
2017-02-12 06:07:35 +00:00
|
|
|
audio.stop();
|
2017-02-05 04:19:54 +00:00
|
|
|
}
|
|
|
|
this.resume = function() {
|
|
|
|
timer.start();
|
2017-02-12 06:07:35 +00:00
|
|
|
audio.start();
|
2017-02-05 04:19:54 +00:00
|
|
|
}
|
|
|
|
this.reset = function() {
|
|
|
|
this.clearDebug();
|
|
|
|
cpu.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadState = function(state) {
|
2019-04-24 20:56:53 +00:00
|
|
|
this.unfixPC(state.c);
|
2017-02-05 04:19:54 +00:00
|
|
|
cpu.loadState(state.c);
|
2019-04-24 20:56:53 +00:00
|
|
|
this.fixPC(state.c);
|
2018-08-18 13:52:17 +00:00
|
|
|
cpuram.mem.set(state.b);
|
2017-02-05 04:19:54 +00:00
|
|
|
dvgram.mem.set(state.db);
|
2017-03-28 16:22:33 +00:00
|
|
|
switches.set(state.sw);
|
2017-02-05 04:19:54 +00:00
|
|
|
nmicount = state.nmic;
|
|
|
|
}
|
|
|
|
this.saveState = function() {
|
|
|
|
return {
|
2019-04-24 20:56:53 +00:00
|
|
|
c:this.getCPUState(),
|
2018-08-18 13:52:17 +00:00
|
|
|
b:cpuram.mem.slice(0),
|
2017-02-05 04:19:54 +00:00
|
|
|
db:dvgram.mem.slice(0),
|
2017-03-28 16:22:33 +00:00
|
|
|
sw:switches.slice(0),
|
2017-02-05 04:19:54 +00:00
|
|
|
nmic:nmicount
|
|
|
|
}
|
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
this.loadControlsState = function(state) {
|
|
|
|
switches.set(state.sw);
|
|
|
|
}
|
|
|
|
this.saveControlsState = function() {
|
|
|
|
return {
|
|
|
|
sw:switches.slice(0),
|
|
|
|
}
|
|
|
|
}
|
2017-02-05 04:19:54 +00:00
|
|
|
this.getCPUState = function() {
|
2019-04-24 20:56:53 +00:00
|
|
|
return this.fixPC(cpu.saveState());
|
2017-02-05 04:19:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-01 18:21:17 +00:00
|
|
|
//
|
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
var Z80ColorVectorPlatform = function(mainElement, proto) {
|
2017-03-28 16:22:33 +00:00
|
|
|
var cpuFrequency = 4000000.0;
|
2017-02-01 18:21:17 +00:00
|
|
|
var cpuCyclesPerFrame = Math.round(cpuFrequency/60);
|
|
|
|
var cpu, cpuram, dvgram, rom, bus, dvg;
|
|
|
|
var video, audio, timer;
|
|
|
|
var clock;
|
|
|
|
var switches = new RAM(16).mem;
|
2017-03-24 22:10:35 +00:00
|
|
|
var mathram = new RAM(16).mem;
|
2017-02-01 18:21:17 +00:00
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
this.__proto__ = new (BaseZ80Platform as any)();
|
2017-02-01 18:21:17 +00:00
|
|
|
|
|
|
|
this.getPresets = function() {
|
|
|
|
return VECTOR_PRESETS;
|
|
|
|
}
|
|
|
|
|
2017-03-24 22:10:35 +00:00
|
|
|
function do_math() {
|
|
|
|
var sum = (((mathram[0] + (mathram[1]<<8)) << 16) >> 16);
|
|
|
|
var a = (mathram[2] << 24) >> 24;
|
|
|
|
var b = (mathram[3] << 24) >> 24;
|
|
|
|
var d = a!=0 ? (sum/a) : 0;
|
|
|
|
sum += (a*b) & 0xffff;
|
|
|
|
mathram[0] = sum & 0xff;
|
|
|
|
mathram[1] = (sum >> 8) & 0xff;
|
|
|
|
mathram[4] = d & 0xff;
|
|
|
|
mathram[5] = (d >> 8) & 0xff;
|
|
|
|
}
|
|
|
|
|
2017-02-01 18:21:17 +00:00
|
|
|
this.start = function() {
|
2017-02-05 04:19:54 +00:00
|
|
|
cpuram = new RAM(0x2000);
|
|
|
|
dvgram = new RAM(0x4000);
|
2017-04-19 18:26:46 +00:00
|
|
|
switches[0] = 0x0;
|
|
|
|
switches[1] = 0xff;
|
|
|
|
switches[2] = 0x0;
|
2017-02-01 18:21:17 +00:00
|
|
|
// bus
|
|
|
|
bus = {
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
read: newAddressDecoder([
|
2020-01-28 15:50:38 +00:00
|
|
|
[0x0, 0x7fff, 0, function(a) { return rom ? rom[a] : 0; }],
|
2017-02-05 04:19:54 +00:00
|
|
|
[0x8000, 0x800f, 0xf, function(a) { return switches[a]; }],
|
2017-03-24 22:10:35 +00:00
|
|
|
[0x8100, 0x810f, 0xf, function(a) { return mathram[a]; } ],
|
2017-02-05 04:19:54 +00:00
|
|
|
[0xa000, 0xdfff, 0x3fff, function(a) { return dvgram.mem[a]; }],
|
|
|
|
[0xe000, 0xffff, 0x1fff, function(a) { return cpuram.mem[a]; }],
|
2017-02-01 18:21:17 +00:00
|
|
|
]),
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
write: newAddressDecoder([
|
2017-02-12 06:07:35 +00:00
|
|
|
[0x8000, 0x800f, 0xf, function(a,v) { audio.pokey1.setRegister(a, v); }],
|
|
|
|
[0x8010, 0x801f, 0xf, function(a,v) { audio.pokey2.setRegister(a, v); }],
|
2017-03-24 22:10:35 +00:00
|
|
|
[0x8100, 0x810e, 0xf, function(a,v) { mathram[a] = v; } ],
|
|
|
|
[0x810f, 0x810f, 0, function(a,v) { do_math(); } ],
|
2017-02-05 04:19:54 +00:00
|
|
|
[0x8840, 0x8840, 0, function(a,v) { dvg.runUntilHalt(0); }],
|
|
|
|
[0x8880, 0x8880, 0, function(a,v) { dvg.reset(); }],
|
2017-03-28 16:22:33 +00:00
|
|
|
[0x8980, 0x8980, 0, function(a,v) { switches[0xe] = 16; }],
|
2017-02-05 04:19:54 +00:00
|
|
|
[0xa000, 0xdfff, 0x3fff, function(a,v) { dvgram.mem[a] = v; }],
|
|
|
|
[0xe000, 0xffff, 0x1fff, function(a,v) { cpuram.mem[a] = v; }],
|
2017-02-01 18:21:17 +00:00
|
|
|
])
|
|
|
|
|
|
|
|
};
|
2017-04-22 01:56:49 +00:00
|
|
|
this.readAddress = bus.read;
|
2019-08-25 20:20:12 +00:00
|
|
|
cpu = this.newCPU(bus, bus);
|
2017-02-01 18:21:17 +00:00
|
|
|
// create video/audio
|
|
|
|
video = new VectorVideo(mainElement,1024,1024);
|
2017-02-05 04:19:54 +00:00
|
|
|
dvg = new DVGColorStateMachine(bus, video, 0xa000);
|
2019-05-26 23:22:51 +00:00
|
|
|
audio = newPOKEYAudio(2);
|
2017-02-01 18:21:17 +00:00
|
|
|
video.create();
|
2018-08-23 23:45:36 +00:00
|
|
|
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
|
|
|
setKeyboardFromMap(video, switches, GRAVITAR_KEYCODE_MAP);
|
|
|
|
}
|
2018-11-22 12:39:06 +00:00
|
|
|
|
|
|
|
this.advance = (novideo) => {
|
2018-08-23 23:45:36 +00:00
|
|
|
if (!novideo) video.clear();
|
2018-11-22 12:39:06 +00:00
|
|
|
this.runCPU(cpu, cpuCyclesPerFrame);
|
2019-08-25 20:20:12 +00:00
|
|
|
cpu.interrupt(0xff); // RST 0x38
|
2017-03-28 16:22:33 +00:00
|
|
|
switches[0xf] = (switches[0xf] + 1) & 0x3;
|
|
|
|
if (--switches[0xe] <= 0) {
|
|
|
|
console.log("WATCHDOG FIRED"); // TODO: alert on video
|
2018-11-22 12:39:06 +00:00
|
|
|
this.reset(); // watchdog reset
|
2017-03-28 16:22:33 +00:00
|
|
|
}
|
2017-02-01 18:21:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.loadROM = function(title, data) {
|
2017-02-05 04:19:54 +00:00
|
|
|
rom = padBytes(data, 0x8000);
|
2017-02-01 18:21:17 +00:00
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isRunning = function() {
|
2017-04-19 01:18:53 +00:00
|
|
|
return timer && timer.isRunning();
|
2017-02-01 18:21:17 +00:00
|
|
|
}
|
|
|
|
this.pause = function() {
|
|
|
|
timer.stop();
|
2017-02-12 06:07:35 +00:00
|
|
|
audio.stop();
|
2017-02-01 18:21:17 +00:00
|
|
|
}
|
|
|
|
this.resume = function() {
|
|
|
|
timer.start();
|
2017-02-12 06:07:35 +00:00
|
|
|
audio.start();
|
2017-02-01 18:21:17 +00:00
|
|
|
}
|
|
|
|
this.reset = function() {
|
2017-03-28 16:22:33 +00:00
|
|
|
switches[0xe] = 16;
|
2017-02-01 18:21:17 +00:00
|
|
|
cpu.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadState = function(state) {
|
|
|
|
cpu.loadState(state.c);
|
2018-08-18 13:52:17 +00:00
|
|
|
cpuram.mem.set(state.b);
|
2017-02-01 18:21:17 +00:00
|
|
|
dvgram.mem.set(state.db);
|
2017-03-28 16:22:33 +00:00
|
|
|
switches.set(state.sw);
|
2017-03-24 22:10:35 +00:00
|
|
|
mathram.set(state.mr);
|
2017-02-01 18:21:17 +00:00
|
|
|
}
|
|
|
|
this.saveState = function() {
|
|
|
|
return {
|
|
|
|
c:cpu.saveState(),
|
2018-08-18 13:52:17 +00:00
|
|
|
b:cpuram.mem.slice(0),
|
2017-02-01 18:21:17 +00:00
|
|
|
db:dvgram.mem.slice(0),
|
2017-03-28 16:22:33 +00:00
|
|
|
sw:switches.slice(0),
|
2017-03-24 22:10:35 +00:00
|
|
|
mr:mathram.slice(0),
|
2017-02-01 18:21:17 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
this.loadControlsState = function(state) {
|
|
|
|
switches.set(state.sw);
|
|
|
|
}
|
|
|
|
this.saveControlsState = function() {
|
|
|
|
return {
|
|
|
|
sw:switches.slice(0),
|
|
|
|
}
|
|
|
|
}
|
2017-02-01 18:21:17 +00:00
|
|
|
this.getCPUState = function() {
|
|
|
|
return cpu.saveState();
|
|
|
|
}
|
2019-08-27 16:12:56 +00:00
|
|
|
this.getMemoryMap = function() { return { main:[
|
|
|
|
{name:'Switches/POKEY I/O',start:0x8000,size:0x100,type:'io'},
|
|
|
|
{name:'Math Box I/O',start:0x8100,size:0x100,type:'io'},
|
|
|
|
{name:'DVG I/O',start:0x8800,size:0x100,type:'io'},
|
|
|
|
{name:'DVG RAM',start:0xa000,size:0x4000,type:'ram'},
|
|
|
|
] } };
|
2017-02-01 18:21:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DIGITAL VIDEO GENERATOR
|
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
var DVGBWStateMachine = function(bus, video, bofs) {
|
2017-01-03 01:42:15 +00:00
|
|
|
var pc = 0;
|
|
|
|
var x = 0;
|
|
|
|
var y = 0;
|
|
|
|
var gsc = 0;
|
|
|
|
var pcstack = [];
|
|
|
|
var running = false;
|
2017-02-05 04:19:54 +00:00
|
|
|
bofs &= 0xffff;
|
2017-01-03 01:42:15 +00:00
|
|
|
|
|
|
|
function readWord(a) {
|
|
|
|
a &= 0xfff;
|
2018-08-18 14:21:18 +00:00
|
|
|
var v = bus.read(a*2+bofs) + (bus.read(a*2+bofs+1) << 8);
|
|
|
|
//console.log(hex(a*2+bofs,4), hex(v,4), hex(x>>2), hex(y>>2));
|
|
|
|
return v;
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function decodeSigned(w, o2) {
|
2017-01-03 15:43:40 +00:00
|
|
|
var s = w & (1<<o2);
|
2017-01-03 01:42:15 +00:00
|
|
|
w = w & ((1<<o2)-1);
|
|
|
|
if (s)
|
|
|
|
return -w;
|
|
|
|
else
|
|
|
|
return w;
|
|
|
|
}
|
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
this.reset = function() {
|
2017-01-03 01:42:15 +00:00
|
|
|
pc = 0;
|
|
|
|
gsc = 7;
|
2017-02-05 04:19:54 +00:00
|
|
|
running = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.go = function() {
|
|
|
|
this.reset();
|
2017-01-03 01:42:15 +00:00
|
|
|
running = true;
|
|
|
|
}
|
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
this.runUntilHalt = function(startpc) {
|
2017-01-03 01:42:15 +00:00
|
|
|
this.go();
|
2017-02-05 04:19:54 +00:00
|
|
|
pc = startpc;
|
2017-02-01 18:21:17 +00:00
|
|
|
for (var i=0; i<10000; i++) { // TODO: limit execution
|
2017-01-03 01:42:15 +00:00
|
|
|
if (!running) break;
|
|
|
|
this.nextInstruction();
|
|
|
|
}
|
|
|
|
//console.log('DVG',i);
|
|
|
|
}
|
|
|
|
|
2017-01-03 15:43:40 +00:00
|
|
|
var GSCALES = [7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8];
|
2017-01-03 01:42:15 +00:00
|
|
|
|
|
|
|
this.nextInstruction = function() {
|
|
|
|
if (!running) return;
|
|
|
|
var w = readWord(pc);
|
|
|
|
var op = w >> 12;
|
2018-08-19 23:25:42 +00:00
|
|
|
//console.log(hex(pc*2+bofs), hex(w), hex(x>>2), hex(y>>2), hex(32-pcstack.length*2));
|
2017-01-03 01:42:15 +00:00
|
|
|
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);
|
2018-08-18 13:52:17 +00:00
|
|
|
video.drawLine(x, y, x2, y2, z*32, 7);
|
2017-01-03 01:42:15 +00:00
|
|
|
//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;
|
2018-08-18 13:52:17 +00:00
|
|
|
video.drawLine(x, y, x2, y2, z*32, 7);
|
2017-01-03 01:42:15 +00:00
|
|
|
x = x2;
|
|
|
|
y = y2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-14 05:47:26 +00:00
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
var DVGColorStateMachine = function(bus, video, bofs) {
|
|
|
|
var pc = 0;
|
|
|
|
var x = 0;
|
|
|
|
var y = 0;
|
|
|
|
var scale = 1.0;
|
|
|
|
var color;
|
|
|
|
var statz;
|
2017-03-24 22:10:35 +00:00
|
|
|
var sparkle;
|
2017-02-05 04:19:54 +00:00
|
|
|
var pcstack = [];
|
|
|
|
var running = false;
|
2017-03-24 22:10:35 +00:00
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
bofs &= 0xffff;
|
|
|
|
|
|
|
|
function readWord(a) {
|
|
|
|
a &= 0x1fff;
|
|
|
|
return bus.read(a*2+bofs) + (bus.read(a*2+bofs+1) << 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
// twos complement
|
|
|
|
function decodeSigned(w, o2) {
|
|
|
|
var s = w & (1<<o2);
|
|
|
|
w = w & ((1<<o2)-1);
|
|
|
|
if (s)
|
|
|
|
return w-(1<<o2);
|
|
|
|
else
|
|
|
|
return w;
|
|
|
|
}
|
|
|
|
|
2017-03-24 22:10:35 +00:00
|
|
|
function sparkle_color() {
|
|
|
|
return (Math.random() * 256) & 0x7;
|
|
|
|
}
|
|
|
|
|
2017-02-05 04:19:54 +00:00
|
|
|
this.reset = function() {
|
|
|
|
pc = 0;
|
|
|
|
scale = 1.0;
|
|
|
|
color = 7;
|
|
|
|
statz = 15;
|
|
|
|
x = 512;
|
|
|
|
y = 512;
|
2017-03-24 22:10:35 +00:00
|
|
|
sparkle = false;
|
2017-02-05 04:19:54 +00:00
|
|
|
running = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.go = function() {
|
|
|
|
this.reset();
|
|
|
|
running = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.runUntilHalt = function(startpc) {
|
|
|
|
this.go();
|
|
|
|
pc = startpc;
|
|
|
|
for (var i=0; i<10000; i++) { // TODO: limit execution
|
|
|
|
if (!running) break;
|
|
|
|
this.nextInstruction();
|
|
|
|
}
|
|
|
|
//console.log('DVG',i);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.nextInstruction = function() {
|
|
|
|
if (!running) return;
|
|
|
|
var w = readWord(pc);
|
|
|
|
var op = w >> 13;
|
|
|
|
//video.drawLine(pc, 1023, pc+1, 1023-op, 7);
|
|
|
|
//console.log(hex(pc), hex(w), op);
|
|
|
|
pc++;
|
|
|
|
switch (op) {
|
|
|
|
case 0: { // VCTR
|
|
|
|
var w2 = readWord(pc++);
|
|
|
|
var z = w2 >> 13;
|
|
|
|
if (z == 2) z = statz;
|
|
|
|
var x2 = x + Math.round(decodeSigned(w2, 12) * scale);
|
|
|
|
var y2 = y + Math.round(decodeSigned(w, 12) * scale);
|
2017-03-24 22:10:35 +00:00
|
|
|
if (sparkle) color = sparkle_color();
|
2017-02-05 04:19:54 +00:00
|
|
|
video.drawLine(x, y, x2, y2, z<<4, color);
|
|
|
|
x = x2;
|
|
|
|
y = y2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: // HALT
|
|
|
|
running = false;
|
|
|
|
break;
|
|
|
|
case 2: { // SVEC
|
|
|
|
var x2 = x + Math.round(decodeSigned(w, 4) * scale * 2);
|
|
|
|
var y2 = y + Math.round(decodeSigned(w>>8, 4) * scale * 2);
|
|
|
|
var z = (w >> 5) & 0x7;
|
|
|
|
if (z == 2) z = statz;
|
2017-03-24 22:10:35 +00:00
|
|
|
if (sparkle) color = sparkle_color();
|
2017-02-05 04:19:54 +00:00
|
|
|
video.drawLine(x, y, x2, y2, z<<4, color);
|
|
|
|
x = x2;
|
|
|
|
y = y2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 3: { // STAT/SCAL
|
2017-03-24 22:10:35 +00:00
|
|
|
if (w & 0x1000) { // SCAL
|
2017-02-05 04:19:54 +00:00
|
|
|
var b = ((w >> 8) & 0x07)+8;
|
|
|
|
var l = (~w) & 0xff;
|
|
|
|
scale = ((l << 16) >> b) / 32768.0;
|
2017-03-24 22:10:35 +00:00
|
|
|
} else { // STAT
|
2017-02-05 04:19:54 +00:00
|
|
|
color = w & 7;
|
|
|
|
statz = (w >> 4) & 0xf;
|
2017-03-24 22:10:35 +00:00
|
|
|
sparkle = (w & 0x800) != 0;
|
2017-02-05 04:19:54 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 4: // CNTR
|
|
|
|
x = 512;
|
|
|
|
y = 512;
|
|
|
|
break;
|
|
|
|
case 6: // RTSL
|
|
|
|
if (pcstack.length == 0) {
|
|
|
|
//console.log("stack underflow"); // TODO: error?
|
|
|
|
} else {
|
|
|
|
pc = pcstack.pop();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 5: // JSRL
|
|
|
|
pcstack.push(pc);
|
|
|
|
case 7: // JMPL
|
|
|
|
if (pc == 0)
|
|
|
|
running = false;
|
|
|
|
else
|
|
|
|
pc = w & 0x1fff;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-01 18:21:17 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
PLATFORMS['vector-ataribw'] = AtariVectorPlatform;
|
2017-02-05 04:19:54 +00:00
|
|
|
PLATFORMS['vector-ataricolor'] = AtariColorVectorPlatform;
|
|
|
|
PLATFORMS['vector-z80color'] = Z80ColorVectorPlatform;
|