AY-3-8910 example (konamisound) using tss library

This commit is contained in:
Steven Hugg 2017-01-18 12:08:15 -05:00
parent cc9872e007
commit c5e4622480
6 changed files with 196 additions and 120 deletions

View File

@ -273,6 +273,12 @@ canvas.pixelated {
<script src="javatari.js/release/javatari/javatari.js"></script>
<script src="src/cpu/z80.js"></script>
<script src="tss/js/tss/PsgDeviceChannel.js"></script>
<script src="tss/js/tss/MasterChannel.js"></script>
<script src="tss/js/tss/AudioLooper.js"></script>
<script src="tss/js/Log.js"></script>
<script src="src/emu.js"></script>
<script src="src/util.js"></script>
<script src="src/disasm.js"></script>

View File

@ -12,7 +12,7 @@ var GalaxianPlatform = function(mainElement) {
this.__proto__ = new BaseZ80Platform();
var cpu, ram, vram, oram, membus, iobus, rom, palette, outlatches;
var video, audio, timer, pixels, displayPCs;
var video, audio, timer, pixels;
var inputs = [0xe,0x8,0x0];
var interruptEnabled = 0;
var starsEnabled = 0;

153
src/platform/konamisound.js Normal file
View File

@ -0,0 +1,153 @@
"use strict";
var KONAMISOUND_PRESETS = [
];
// TODO: global???
window.buildZ80({
applyContention: false
});
var KonamiSoundPlatform = function(mainElement) {
var self = this;
this.__proto__ = new BaseZ80Platform();
var cpu, ram, rom, membus, iobus;
var audio, master;
var video, timer;
var interruptPending = 0;
var psgRegister = 0;
var psg;
var cpuFrequency = 14318000/8;
var ioTimerFrequency = 1789750 / 1280;
var cpuCyclesPerFrame = cpuFrequency/60;
var cpuCyclesPerTimer = cpuFrequency/ioTimerFrequency;
this.getPresets = function() {
return KONAMISOUND_PRESETS;
}
this.start = function() {
ram = new RAM(0x400);
membus = {
read: new AddressDecoder([
[0x0000, 0x3fff, 0x1fff, function(a) { return rom ? rom[a] : null; }],
[0x4000, 0x5fff, 0x3ff, function(a) { return ram.mem[a]; }]
]),
write: new AddressDecoder([
[0x4000, 0x5fff, 0x3ff, function(a,v) { ram.mem[a] = v; }],
[0x6000, 0x6fff, 0, function(a,v) { }],
]),
isContended: function() { return false; },
};
iobus = {
read: function(addr) {
if (addr & 0x40) {
if (psgRegister == 0xf) { // timer
var bit = (cpu.getTstates() / cpuCyclesPerTimer) & 1;
return bit << 3;
}
return psg.readRegister(psgRegister) & 0xff;
}
return 0;
},
write: function(addr, val) {
if (addr & 0x80) {
psgRegister = val & 0xf;
//console.log('PSG reg', psgRegister);
}
if (addr & 0x40) {
psg.writeRegisterAY(psgRegister, val & 0xff);
//console.log('PSG write', psgRegister, val);
}
}
};
cpu = window.Z80({
display: {},
memory: membus,
ioBus: iobus
});
psg = new PsgDeviceChannel();
master = new MasterChannel();
psg.setMode(PsgDeviceChannel.MODE_SIGNED);
psg.setDevice(PsgDeviceChannel.DEVICE_AY_3_8910);
master.addChannel(psg);
audio = new AudioLooper(512);
audio.setChannel(master);
//audio = new SampleAudio(psg.sampleRate);
video = new RasterVideo(mainElement,256,256);
video.create();
video.setKeyboardEvents(function(key,code,flags) {
var intr = (key-49);
if (intr >= 0 && (flags & 1)) {
psg.writeRegister(14, intr);
psg.writeRegister(15, 0x80);
cpu.setIFF1(1);
cpu.requestInterrupt(0x38);
/*
console.log(cpu.saveState());
console.log(hex(intr * 8), cpu.getIFF1(), cpu.getIM());
cpu.runFrame(cpu.getTstates() + 1);
console.log(cpu.saveState());
*/
}
});
timer = new AnimationTimer(60, function() {
if (!self.isRunning())
return;
var debugCond = self.getDebugCallback();
var targetTstates = cpu.getTstates() + cpuCyclesPerFrame;
if (debugCond) {
while (cpu.getTstates() < targetTstates) {
if (debugCond && debugCond()) { debugCond = null; }
cpu.runFrame(cpu.getTstates() + 1);
}
} else {
cpu.runFrame(targetTstates);
}
});
}
this.loadROM = function(title, data) {
rom = padBytes(data, 0x4000);
cpu.reset();
}
this.loadState = function(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
}
this.saveState = function() {
return {
c:self.getCPUState(),
b:ram.mem.slice(0),
};
}
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.activate();
}
this.reset = function() {
cpu.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
}
this.readAddress = function(addr) {
return membus.read(addr); // TODO?
}
}
PLATFORMS['konamisound'] = KonamiSoundPlatform;

View File

@ -15,7 +15,7 @@ function assertEquals(a,b) {
assert(a==b, a + " != " + b);
}
describe('Test address decoder', function() {
describe('Address decoder', function() {
it('Should work empty', function() {
var decoder = new AddressDecoder([]);
assertEquals(0, decoder(0x1234));

View File

@ -1,107 +0,0 @@
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);
});
});

View File

@ -1,9 +1,10 @@
"use strict";
global.window = global;
require('../../../src/cpu/z80.js');
var _global = window;
_global.buildZ80({
applyContention: true
});
@ -200,13 +201,36 @@ var testsIn = fs.readFileSync('test/cli/z80/tests.in', {encoding:'utf8'}).split(
var testsExpected = fs.readFileSync('test/cli/z80/tests.expected', {encoding:'utf8'}).split('\n\n');
assert(testsIn.length == testsExpected.length);
describe('Z80 CPU', function() {
it('should execute test cases', function() {
for (var iter=0; iter<testsIn.length; iter++) {
var fn = function(index, input, expected) {
var output = runTest(input);
assert.equal(output.trim(), expected.trim());
}.call(this, iter, testsIn[iter], testsExpected[iter]);
}
});
});
function benchmark(cycles) {
var memory = Memory(function() { });
var ioBus = IOBus();
var z80 = _global.Z80({
display: {},
memory: memory,
ioBus: ioBus
});
memory.clear();
for (var i=0; i<0x10000; i++)
memory.write(i, i&0xff);
z80.setTstates(0);
z80.runFrame(cycles);
console.log(z80.saveState());
}
if (global.describe) {
describe('Z80 CPU', function() {
it('should execute test cases', function() {
for (var iter=0; iter<testsIn.length; iter++) {
var fn = function(index, input, expected) {
var output = runTest(input);
assert.equal(output.trim(), expected.trim());
}.call(this, iter, testsIn[iter], testsExpected[iter]);
}
});
it('should run 1M cycles', function() {
benchmark(1164770);
});
});
} else {
benchmark(100*1000000);
}