added js coleco platform

This commit is contained in:
Steven Hugg 2018-11-19 09:10:13 -05:00
parent 30ac9a3509
commit 43ac950a52
6 changed files with 278 additions and 35 deletions

View File

@ -320,6 +320,7 @@ function require(modname) {
<script src="tss/js/tss/AudioLooper.js"></script>
<script src="tss/js/Log.js"></script>
<script src="gen/video/tms9918a.js"></script>
<script src="gen/util.js"></script>
<script src="gen/store.js"></script>
<script src="src/vlist.js"></script>

View File

@ -1,4 +1,4 @@

include "nesdefs.asm"
;;;;; VARIABLES
@ -26,9 +26,9 @@ Start:
sta PPU_ADDR ; PPU addr = $0000
sta PPU_SCROLL
sta PPU_SCROLL ; scroll = $0000
lda #$90
lda #CTRL_NMI
sta PPU_CTRL ; enable NMI
lda #$1e
lda #MASK_SPR|MASK_BG|MASK_SPR_CLIP|MASK_BG_CLIP
sta PPU_MASK ; enable rendering
.endless
jmp .endless ; endless loop
@ -38,15 +38,15 @@ FillVRAM: subroutine
txa
ldy #$20
sty PPU_ADDR
sta PPU_ADDR
ldy #$10
sta PPU_ADDR ; PPU addr = $2000
ldy #$10 ; $10 (16) 256-byte pages
.loop:
sta PPU_DATA
adc #7
sta PPU_DATA ; write to VRAM
adc #7 ; add 7 to make randomish pattern
inx
bne .loop
dey
bne .loop
dey ; next page
bne .loop ; out of pages?
rts
; set palette colors
@ -54,14 +54,14 @@ SetPalette: subroutine
ldy #$00
lda #$3f
sta PPU_ADDR
sty PPU_ADDR
ldx #32
sty PPU_ADDR ; PPU addr = 0x3f00
ldx #32 ; 32 palette colors
.loop:
lda Palette,y
sta PPU_DATA
lda Palette,y ; load from ROM
sta PPU_DATA ; store to palette
iny
dex
bne .loop
bne .loop ; loop until 32 colors stored
rts
@ -77,14 +77,9 @@ NMIHandler:
; update scroll position (must be done after VRAM updates)
inc ScrollPos
lda ScrollPos
sta PPU_SCROLL
sta PPU_SCROLL ; X scroll position
lda #0
sta PPU_SCROLL
; TODO: write high bits to PPUCTRL
lda ScrollPos
and #0
ora #$90 ; enable NMI
sta PPU_CTRL
sta PPU_SCROLL ; Y scroll position
; reload registers
pla ; reload A
rti

View File

@ -19,7 +19,7 @@ export class AY38910_Audio {
master : MasterAudio;
psg = new PsgDeviceChannel();
curreg = 0;
constructor(master : MasterAudio) {
this.master = master;
this.psg.setMode(PsgDeviceChannel.MODE_SIGNED);
@ -41,6 +41,21 @@ export class AY38910_Audio {
}
}
export class SN76489_Audio {
master : MasterAudio;
psg = new PsgDeviceChannel();
constructor(master : MasterAudio) {
this.master = master;
this.psg.setMode(PsgDeviceChannel.MODE_SIGNED);
this.psg.setDevice(PsgDeviceChannel.DEVICE_SN76489);
master.master.addChannel(this.psg);
}
setData(val : number) {
this.psg.writeRegisterSN(0, val & 0xff);
}
}
// https://en.wikipedia.org/wiki/POKEY
// https://user.xmission.com/~trevin/atari/pokey_regs.html
// http://krap.pl/mirrorz/atari/homepage.ntlworld.com/kryten_droid/Atari/800XL/atari_hw/pokey.htm

View File

@ -1,7 +1,10 @@
"use strict";
import { Platform, BaseMAMEPlatform, getToolForFilename_z80 } from "../baseplatform";
import { PLATFORMS } from "../emu";
import { Platform, BaseMAMEPlatform, BaseZ80Platform, getToolForFilename_z80 } from "../baseplatform";
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
import { hex, lzgmini, stringToByteArray } from "../util";
import { MasterAudio, SN76489_Audio } from "../audio";
import { TMS9918A } from "../video/tms9918a";
// http://www.colecovision.eu/ColecoVision/development/tutorial1.shtml
// http://www.colecovision.eu/ColecoVision/development/libcv.shtml
@ -31,6 +34,228 @@ var ColecoVision_PRESETS = [
{id:'platform.c', name:'Platform Game'},
];
var COLECOVISION_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_UP, 0, 0x1],
[Keys.VK_DOWN, 0, 0x4],
[Keys.VK_LEFT, 0, 0x8],
[Keys.VK_RIGHT, 0, 0x2],
[Keys.VK_SPACE, 0, 0x40],
[Keys.VK_CONTROL, 1, 0x40],
[Keys.VK_W, 2, 0x1],
[Keys.VK_S, 2, 0x4],
[Keys.VK_A, 2, 0x8],
[Keys.VK_D, 2, 0x2],
[Keys.VK_Z, 2, 0x40],
[Keys.VK_X, 3, 0x40],
]);
/// standard emulator
const _ColecoVisionPlatform = function(mainElement) {
const cpuFrequency = 3579545; // MHz
const canvasWidth = 304;
const numTotalScanlines = 262;
const numVisibleScanlines = 240;
const cpuCyclesPerLine = Math.round(cpuFrequency / 60 / numTotalScanlines);
var cpu, ram, membus, iobus, rom, bios;
var video, vdp, timer;
var audio, psg;
var inputs = new Uint8Array(4);
var keypadMode = false;
class ColecoVisionPlatform extends BaseZ80Platform implements Platform {
getPresets() { return ColecoVision_PRESETS; }
getToolForFilename = getToolForFilename_z80;
getDefaultExtension() { return ".c"; };
start() {
ram = new RAM(1024);
bios = new lzgmini().decode(stringToByteArray(atob(COLECO_BIOS_LZG)));
membus = {
read: newAddressDecoder([
[0x0000, 0x1fff, 0x1fff, function(a) { return bios ? bios[a] : 0; }],
[0x6000, 0x7fff, 0x3ff, function(a) { return ram.mem[a]; }],
[0x8000, 0xffff, 0x7fff, function(a) { return rom ? rom[a] : 0; }],
]),
write: newAddressDecoder([
[0x6000, 0x7fff, 0x3ff, function(a,v) { ram.mem[a] = v; }],
]),
isContended: function() { return false; },
};
iobus = {
read: function(addr) {
addr &= 0xff;
//console.log('IO read', hex(addr,4));
switch (addr) {
case 0xfc: return inputs[keypadMode?1:0] ^ 0xff;
case 0xff: return inputs[keypadMode?3:2] ^ 0xff;
}
if (addr >= 0xa0 && addr <= 0xbf) {
if (addr & 1)
return vdp.readStatus();
else
return vdp.readData();
}
return 0;
},
write: function(addr, val) {
addr &= 0xff;
val &= 0xff;
//console.log('IO write', hex(addr,4), hex(val,2));
switch (addr >> 4) {
case 0x8: case 0x9: keypadMode = true; break;
case 0xc: case 0xd: keypadMode = false; break;
case 0xa: case 0xb:
if (addr & 1)
return vdp.writeAddress(val);
else
return vdp.writeData(val);
case 0xf: psg.setData(val); break;
}
}
};
cpu = this.newCPU(membus, iobus);
video = new RasterVideo(mainElement,canvasWidth,numVisibleScanlines);
video.create();
audio = new MasterAudio();
psg = new SN76489_Audio(audio);
var cru = {
setVDPInterrupt: (b) => {
if (b) {
cpu.nonMaskableInterrupt();
} else {
// TODO: reset interrupt?
}
}
};
vdp = new TMS9918A(video.canvas, cru, false);
setKeyboardFromMap(video, inputs, COLECOVISION_KEYCODE_MAP);
timer = new AnimationTimer(60, this.nextFrame.bind(this));
}
readAddress(addr) {
return membus.read(addr);
}
advance(novideo : boolean) {
for (var sl=0; sl<numTotalScanlines; sl++) {
this.runCPU(cpu, cpuCyclesPerLine);
if (sl < numVisibleScanlines)
vdp.drawScanline(sl);
}
vdp.updateCanvas();
}
loadROM(title, data) {
rom = padBytes(data, 0x8000);
this.reset();
}
loadState(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
vdp.restoreState(state.vdp);
keypadMode = state.kpm;
inputs.set(state.in);
}
saveState() {
return {
c:this.getCPUState(),
b:ram.mem.slice(0),
vdp:vdp.getState(),
kpm:keypadMode,
in:inputs.slice(0),
};
}
loadControlsState(state) {
inputs[0] = state.in0;
inputs[1] = state.in1;
inputs[2] = state.in2;
}
saveControlsState() {
return {
in0:inputs[0],
in1:inputs[1],
in2:inputs[2],
};
}
getCPUState() {
return cpu.saveState();
}
isRunning() {
return timer && timer.isRunning();
}
pause() {
timer.stop();
audio.stop();
}
resume() {
timer.start();
audio.start();
}
reset() {
cpu.reset();
cpu.setTstates(0);
}
getDebugCategories() {
return super.getDebugCategories().concat(['VDP']);
}
getDebugInfo(category, state) {
switch (category) {
case 'VDP': return this.vdpStateToLongString(state.vdp);
default: return super.getDebugInfo(category, state);
}
}
vdpStateToLongString(ppu) {
return vdp.getRegsString();
}
}
return new ColecoVisionPlatform();
}
var COLECO_BIOS_LZG = `
TFpHAAAgAAAAB7djQcnHAQEDBgcx/3MYawAAAMMMgAehB+EPB+USB+UVB+UYB+UbB+UeB+QHHAZm
IYA8igUCBYIAKgCAff5VIAl8/qogBCoKgOnHAwkfgICAAAMFT6CgB4LgByEH4WDAYMBABlggQIAg
B+HAwOCgYAMGKweBQAYxBphAQEAG+KBABnAGEuAGUAabB+QA4AflBkggIAYyB+FgoKCgwAZdwAY5
B+HABhAGYAfhIAZQoKDgIAMCcOCAwAaIYIDgoAZY4AMFOAYGBsgGIAZYAwJWBlAAQAMEcAYfQAZ4
BhoDA3gGBgaQBjgGGwYfoOCAAwLIB+EDA/DAoAchAwNggAaQwAMFmOCAByEH5QZ4AwKABligAwUw
4EBAQAZYICAgoAMCUAYuBpAGPwawoOAG4AfhAyNQQKCgBqDAoMADJECgoOADBGjgwAZYYIADBPgD
AlADBEigAwVooAMFCAMEQAYHAwRQBg8GUAMiEAMEoAMEcAMD6gaQICAGyAADJZgDJ4gDI7gAwGAD
I0CAwAME6AMC+QMCaCBgAwRwAGCgwAaIQOADA2AGJiAH4gZoAwPgA0M6ACAAAwX4gKDAAwPAAyLP
AwNgAwXwAyNJAwR+AwX4oMADRQBgAyOgAyJxB+PAYANDEOBAQAMDeAMF+AfhA0RQAyJBB+NAAwPI
BncGkOBgwAZQYECABrADQs8DAkjAQCBAA0QYAAMDUAcLOERsRFREOAA4fFR8RHw4AAAofHx8OBAA
ABA4B+MQODgQfHwDBQgQBhgAADAwB4H8/PzMzAeBAAB4SEh4BkiEtLSEBggcDDRISDAAOEREOBAG
eBgUEDBwYAAMNCw0LGxgAABUOGw4VAAAIDA4PDgwIAAIGDh4OBgIAwJXEAMCaCgHAgAoADxUVDQU
FBQAOEQwKBhEAwNwAAB4eAMGIAchfBAHAQAHggMEqBh8GAfBABAwfDAH4gAAQEBAfAAAKCh8KCgG
DxA4OHwGB3x8ODgGLwcHBg8GfgBsbEgGyQZnfCgAIDhAMAhwEABkZAgQIExMACBQUCBUSDQAMDAD
ZEsQIAcCEAAgAwJ5EAYMKDh8OCgGURB8AwdYBuV8AwdlAyIyBAgQAyKmOERMVGREOAAQMAZ4OAYI
BBggQHwH4jgEBhAIGChIfAgIAHxAQHgGSAYVeEQH4XwGeCAgAyNYBkgHgTwECAMiijAwAySSB+Mg
Bh5AIBAIAwR3B0EGBAZyAwNgAwLoOERcVFxABrhEfEREAHhERAdCBghAQEBEOAZIBwF4AwN4AyJA
B+RABlhcREQ8AAbvRAA4AwP4OAAEBwEDAohESFBgUEhEAyJ1AyN4RGxUBh9EAERkVEwGqAZGAwRY
AwVIREQDIlAGSEgGmEADA/gDI9YQEAMCYAaoB8MoBkhUBwEoB+EoECgGKAaOBiB4AwLpQHgAOAMj
kDgAAAMC9gQAADgIBwI4AAYlA0rv/DADRR0GGgQ8RDwABjQDA+gGCEQDAvgEBg4DAuAGSHhAAyJ4
IHgDInAAAAYQPAQ4QEBwSAcBAANEehgACAAYCAgISDBAQAMD+QaOBlAAAGhUVEQDAnQG6AMCSAMC
yAADI1p4QAMDSEQ8BAAAWCQgIHAGWEA4BAYYAwJnKAMCnAYuWANCUQME+AfiVHwGSEhIMAMDSAYY
OBBgAAB4CDBAeAMCoGAgIBgDAngHYzAICAwICDAAKFADRuhsRER8AwPeQEQ4EDBIAwVYDAMF4Ach
AyMIKAflMAflOCgH5AMkFxAwBiAGqAYgB+MGIAMFCAME8BAGgRgAIAMFCAYHKER8RAMCQGwH4gwA
fEB4A2KYAHgUfFA8ADxQUHxQUFwAOAAwSAOCOCgH5WAH5TgDBaBgB+UoAwXwAwL9BugH5AAQOEBA
OBAAGCQgeCAkXABEKBB8ByEAYFBQaFxISAAIFBA4EBBQIBgDBdADIyIQGAfhAwRgGAMGWFADI6kH
4khoWEgDBSY8AwUeeAMi8DADQiAAA2ISA6N2/AQEB6FASFA4RAgcB+IsVBwEBmADglsGEiRIJAZf
AEgkAyKuVACoB2SoBySo/FT8B2IDRDAHA/AHpQfjUFBQ0AdhAyK+8AfjAwYQ0BAGkAcGBhAGyNAQ
8AYkAAYCB+MGJgfjBngDBVgcBpAQEPwDZgwDBhAHggZIAwYYAwUOBpBQUFBcAwNYXAMjtQMC9wbI
3AboB6HcAwUYBpAGCAaQBhgGkBADBghQUAMGKAMDWAZCAwW7AwNQAwJoBpgDBXAGAnwG11ADB0AD
JiADBbgGoPwHBQZJB+LgBwUcBwUDBhQAADRISDQDY49wSEhwQHhIA4OpAAB8A8R6eEggECADwtEA
PANDTwNjYHAGWShQAwKHOBA4A8LZA0IIeAaZOEREKChsADBAIBA4BqkoA4KfA0KIVFQDIvg4QANi
FwAGaQNjr3gHIwZYEAAGEEAwCDBABgkHoTAIBlEIFANIAlAgBiAAfAdhAwJ3B0IGfwMDjgPmiAcB
A8SRHAYnBihQAwK9BwFgEAODTQPjNXgDAmgHHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8H
HwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBwM=`;
/// MAME support
class ColecoVisionMAMEPlatform extends BaseMAMEPlatform implements Platform {
@ -61,6 +286,8 @@ class ColecoVisionMAMEPlatform extends BaseMAMEPlatform implements Platform {
getDefaultExtension() { return ".c"; };
}
///
PLATFORMS['coleco'] = ColecoVisionMAMEPlatform;
PLATFORMS['coleco.mame'] = ColecoVisionMAMEPlatform;
PLATFORMS['coleco'] = _ColecoVisionPlatform;

View File

@ -1,3 +1,4 @@
'use strict';
/*
* js99'er - TI-99/4A emulator written in JavaScript
*
@ -9,7 +10,7 @@
* GNU General Public License v2.0
*/
'use strict';
import { hex } from "../util";
enum TMS9918A_Mode {
GRAPHICS = 0,
@ -32,6 +33,7 @@ export function TMS9918A(canvas, cru, enableFlicker) {
this.ram = new Uint8Array(16384); // VDP RAM
this.registers = new Uint8Array(8);
this.spriteBuffer = new Uint8Array(256);
this.addressRegister = null;
this.statusRegister = null;
@ -178,7 +180,8 @@ TMS9918A.prototype = {
var y1 = y - vBorder;
// Pre-process sprites
if (!textMode) {
var spriteBuffer = new Uint8Array(drawWidth);
var spriteBuffer = this.spriteBuffer;
spriteBuffer.fill(0);
var spritesOnLine = 0;
var endMarkerFound = false;
var spriteAttributeAddr = spriteAttributeTable;
@ -550,11 +553,11 @@ TMS9918A.prototype = {
getRegsString: function () {
var s = "";
for (var i = 0; i < this.registers.length; i++) {
s += "VR" + i + ":" + this.registers[i].toHexByte() + " ";
s += "VR" + i + ":" + hex(this.registers[i],2) + " ";
}
s += "\nSIT:" + this.nameTable.toHexWord() + " PDT:" + this.charPatternTable.toHexWord() + " (" + this.patternTableSize().toHexWord() + ")" +
" CT:" + this.colorTable.toHexWord() + " (" + this.colorTableSize().toHexWord() + ") SDT:" + this.spritePatternTable.toHexWord() +
" SAL:" + this.spriteAttributeTable.toHexWord() + "\nVDP: " + this.addressRegister.toHexWord();
s += "\nSIT:" + hex(this.nameTable,4) + " PDT:" + hex(this.charPatternTable,4) + " (" + hex(this.patternTableSize(),4) + ")" +
" CT:" + hex(this.colorTable,4) + " (" + hex(this.colorTableSize(),4) + ") SDT:" + hex(this.spritePatternTable,4) +
" SAL:" + hex(this.spriteAttributeTable,4) + "\nVDP: " + hex(this.addressRegister,4);
return s;
},

View File

@ -111,7 +111,7 @@ var PLATFORM_PARAMS = {
'nes': { //TODO
define: '__NES__',
cfgfile: 'neslib.cfg',
libargs: ['crt0.o', 'nes.lib',
libargs: ['crt0.o', 'nes.lib',
'-D', 'NES_MAPPER=0',
'-D', 'NES_PRG_BANKS=2',
'-D', 'NES_CHR_BANKS=0', // TODO: >0 doesn't seem to work
@ -182,6 +182,8 @@ var PLATFORM_PARAMS = {
},
};
PLATFORM_PARAMS['coleco.mame'] = PLATFORM_PARAMS['coleco'];
var _t1;
function starttime() { _t1 = new Date(); }
function endtime(msg) { var _t2 = new Date(); console.log(msg, _t2.getTime() - _t1.getTime(), "ms"); }
@ -1025,7 +1027,7 @@ function linkSDLDZ80(step:BuildStep)
setupFS(FS, 'sdcc');
populateFiles(step, FS);
// TODO: coleco hack so that -u flag works
if (step.platform == "coleco") {
if (step.platform.startsWith("coleco")) {
FS.writeFile('crt0.rel', FS.readFile('/share/lib/coleco/crt0.rel', {encoding:'utf8'}));
FS.writeFile('crt0.lst', '\n'); // TODO: needed so -u flag works
}
@ -1046,7 +1048,7 @@ function linkSDLDZ80(step:BuildStep)
// return unchanged if no files changed
if (!anyTargetChanged(step, ["main.ihx", "main.noi"]))
return;
var listings = {};
for (var fn of step.files) {
if (fn.endsWith('.lst')) {
@ -1161,7 +1163,7 @@ function preprocessMCPP(step:BuildStep) {
// TODO: make configurable by other compilers
var args = [
"-D", "__8BITWORKSHOP__",
"-D", platform.toUpperCase().replace('-','_'),
"-D", platform.toUpperCase().replace(/[-.]/g,'_'),
"-D", "__SDCC_z80",
"-I", "/share/include",
"-Q",