sms: made into classes, more vdp fixed (still need to pass vdptest); debug fn bound to platform

This commit is contained in:
Steven Hugg 2018-12-01 09:48:30 -05:00
parent 23f32b2dd3
commit 2dd5d88d55
4 changed files with 305 additions and 177 deletions

View File

@ -83,7 +83,7 @@ export interface Platform {
runToPC?(pc:number) : void;
runUntilReturn?() : void;
stepBack?() : void;
runEval?(evalfunc/* : DebugEvalCondition*/) : void;
runEval?(evalfunc : DebugEvalCondition) : void;
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
getSP?() : number;
@ -113,9 +113,9 @@ export interface MemoryBus {
write : (address:number, value:number) => void;
}
type DebugCondition = () => boolean;
type DebugEvalCondition = (c:CpuState) => boolean;
type BreakpointCallback = (EmuState) => void;
export type DebugCondition = () => boolean;
export type DebugEvalCondition = (c:CpuState) => boolean;
export type BreakpointCallback = (s:EmuState) => void;
export interface EmuRecorder {
frameRequested() : boolean;

View File

@ -47,182 +47,294 @@ var SG1000_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_1, 1, 0x10],
]);
/// standard emulator
class SG1000Platform extends BaseZ80Platform {
const _SG1000Platform = function(mainElement, isSMS:boolean) {
cpuFrequency = 3579545; // MHz
canvasWidth = 304;
numTotalScanlines = 262;
numVisibleScanlines = 240;
cpuCyclesPerLine;
const cpuFrequency = 3579545; // MHz
const canvasWidth = 304;
const numTotalScanlines = 262;
const numVisibleScanlines = 240;
const cpuCyclesPerLine = Math.round(cpuFrequency / 60 / numTotalScanlines);
cpu;
ram : RAM;
membus;
iobus;
rom = new Uint8Array(0);
video;
vdp;
timer;
audio;
psg;
inputs = new Uint8Array(4);
mainElement : HTMLElement;
var cpu, ram, membus, iobus, rom;
var video, vdp, timer;
var audio, psg;
var inputs = new Uint8Array(4);
class SG1000Platform extends BaseZ80Platform implements Platform {
isSMS = false; // TODO: remove
currentScanline : number;
currentScanline;
constructor(mainElement : HTMLElement) {
super();
this.mainElement = mainElement;
}
getPresets() { return SG1000_PRESETS; }
start() {
var ramSize = isSMS ? 0x2000 : 0x400;
ram = new RAM(ramSize);
membus = {
read: newAddressDecoder([
[0xc000, 0xffff, ramSize-1, function(a) { return ram.mem[a]; }],
[0x0000, 0xbfff, 0xffff, function(a) { return rom ? rom[a] : 0; }],
]),
write: newAddressDecoder([
[0xc000, 0xffff, ramSize-1, function(a,v) { ram.mem[a] = v; }],
]),
isContended: function() { return false; },
};
iobus = {
read: (addr:number) => {
addr &= 0xff;
//console.log('IO read', hex(addr,4));
switch (addr & 0xc1) {
case 0x40: return isSMS ? this.currentScanline : 0;
case 0x80: return vdp.readData();
case 0x81: return vdp.readStatus();
case 0xc0: return inputs[0] ^ 0xff;
case 0xc1: return inputs[1] ^ 0xff;
}
return 0;
},
write: (addr:number, val:number) => {
addr &= 0xff;
val &= 0xff;
//console.log('IO write', hex(addr,4), hex(val,2));
switch (addr & 0xc1) {
case 0x80: return vdp.writeData(val);
case 0x81: return vdp.writeAddress(val);
case 0x40:
case 0x41: return psg.setData(val);
}
}
};
cpu = this.newCPU(membus, iobus);
video = new RasterVideo(mainElement,canvasWidth,numVisibleScanlines,{overscan:true});
video.create();
audio = new MasterAudio();
psg = new SN76489_Audio(audio);
var cru = {
setVDPInterrupt: (b) => {
if (b) {
cpu.nonMaskableInterrupt();
} else {
// TODO: reset interrupt?
}
getPresets() { return SG1000_PRESETS; }
newRAM() {
return new RAM(0x400);
}
newMembus() {
var ramSize = this.ram.mem.length;
return {
read: newAddressDecoder([
[0xc000, 0xffff, ramSize-1, (a) => { return this.ram.mem[a]; }],
[0x0000, 0xbfff, 0xffff, (a) => { return this.rom[a]; }],
]),
write: newAddressDecoder([
[0xc000, 0xffff, ramSize-1, (a,v) => { this.ram.mem[a] = v; }],
]),
isContended: () => { return false; },
};
}
newIOBus() {
return {
read: (addr:number) => {
addr &= 0xff;
//console.log('IO read', hex(addr,4));
switch (addr & 0xc1) {
case 0x40: return this.isSMS ? this.currentScanline : 0;
case 0x80: return this.vdp.readData();
case 0x81: return this.vdp.readStatus();
case 0xc0: return this.inputs[0] ^ 0xff;
case 0xc1: return this.inputs[1] ^ 0xff;
}
};
var vdpclass = isSMS ? SMSVDP : TMS9918A;
vdp = new vdpclass(video.getFrameData(), cru, true); // true = 4 sprites/line
setKeyboardFromMap(video, inputs, SG1000_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.currentScanline = sl;
this.runCPU(cpu, cpuCyclesPerLine);
vdp.drawScanline(sl);
}
video.updateFrame();
}
loadROM(title, data) {
if (data.length < 0xc000) {
rom = padBytes(data, 0xc000);
} else {
switch (data.length) {
case 0x10000:
case 0x20000:
case 0x40000:
rom = data;
break;
default:
throw "Unknown rom size: $" + hex(data.length);
return 0;
},
write: (addr:number, val:number) => {
addr &= 0xff;
val &= 0xff;
//console.log('IO write', hex(addr,4), hex(val,2));
switch (addr & 0xc1) {
case 0x80: return this.vdp.writeData(val);
case 0x81: return this.vdp.writeAddress(val);
case 0x40:
case 0x41: return this.psg.setData(val);
}
}
this.reset();
}
loadState(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
vdp.restoreState(state.vdp);
inputs.set(state.in);
}
saveState() {
return {
c:this.getCPUState(),
b:ram.mem.slice(0),
vdp:vdp.getState(),
in:inputs.slice(0),
};
}
loadControlsState(state) {
inputs.set(state.in);
}
saveControlsState() {
return {
in:inputs.slice(0)
};
}
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);
vdp.reset();
psg.reset();
}
getDebugCategories() {
return super.getDebugCategories().concat(['VDP']);
}
getDebugInfo(category, state) {
switch (category) {
case 'VDP': return this.vdpStateToLongString(state.vdp);
default: return super.getDebugInfo(category, state);
};
}
newVDP(frameData, cru, flicker) {
return new TMS9918A(frameData, cru, flicker);
}
start() {
this.cpuCyclesPerLine = Math.round(this.cpuFrequency / 60 / this.numTotalScanlines);
this.ram = this.newRAM();
this.membus = this.newMembus();
this.iobus = this.newIOBus();
this.cpu = this.newCPU(this.membus, this.iobus);
this.video = new RasterVideo(this.mainElement, this.canvasWidth, this.numVisibleScanlines, {overscan:true});
this.video.create();
this.audio = new MasterAudio();
this.psg = new SN76489_Audio(this.audio);
var cru = {
setVDPInterrupt: (b) => {
if (b) {
this.cpu.nonMaskableInterrupt();
} else {
// TODO: reset interrupt?
}
}
};
this.vdp = this.newVDP(this.video.getFrameData(), cru, true); // true = 4 sprites/line
setKeyboardFromMap(this.video, this.inputs, SG1000_KEYCODE_MAP); // TODO
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
}
readAddress(addr) {
return this.membus.read(addr);
}
advance(novideo : boolean) {
for (var sl=0; sl<this.numTotalScanlines; sl++) {
this.currentScanline = sl;
this.runCPU(this.cpu, this.cpuCyclesPerLine);
this.vdp.drawScanline(sl);
}
vdpStateToLongString(ppu) {
return vdp.getRegsString();
this.video.updateFrame();
}
loadROM(title, data) {
this.rom = padBytes(data, 0xc000);
this.reset();
}
loadState(state) {
this.cpu.loadState(state.c);
this.ram.mem.set(state.b);
this.vdp.restoreState(state.vdp);
this.inputs.set(state.in);
}
saveState() {
return {
c:this.getCPUState(),
b:this.ram.mem.slice(0),
vdp:this.vdp.getState(),
in:this.inputs.slice(0),
};
}
loadControlsState(state) {
this.inputs.set(state.in);
}
saveControlsState() {
return {
in:this.inputs.slice(0)
};
}
getCPUState() {
return this.cpu.saveState();
}
isRunning() {
return this.timer && this.timer.isRunning();
}
pause() {
this.timer.stop();
this.audio.stop();
}
resume() {
this.timer.start();
this.audio.start();
}
reset() {
this.cpu.reset();
this.cpu.setTstates(0);
this.vdp.reset();
this.psg.reset();
}
getDebugCategories() {
return super.getDebugCategories().concat(['VDP']);
}
getDebugInfo(category, state) {
switch (category) {
case 'VDP': return this.vdpStateToLongString(state.vdp);
default: return super.getDebugInfo(category, state);
}
}
return new SG1000Platform();
vdpStateToLongString(ppu) {
return this.vdp.getRegsString();
}
}
///
const _SMSPlatform = function(mainElement) {
this.__proto__ = new (_SG1000Platform as any)(mainElement, true);
class SMSPlatform extends SG1000Platform {
isSMS = true;
cartram : RAM = new RAM(0);
pagingRegisters = new Uint8Array(4);
romPageMask : number;
// TODO: hide bottom scanlines
reset() {
super.reset();
this.pagingRegisters.set([0,0,1,2]);
}
newVDP(frameData, cru, flicker) {
return new SMSVDP(frameData, cru, flicker);
}
newRAM() { return new RAM(0x2000); }
getPagedROM(a:number, reg:number) {
//if (!(a&0xff)) console.log(hex(a), reg, this.pagingRegisters[reg], this.romPageMask);
return this.rom[a + ((this.pagingRegisters[reg] & this.romPageMask) << 14)]; // * $4000
}
newMembus() {
return {
read: newAddressDecoder([
[0xc000, 0xffff, 0x1fff, (a) => { return this.ram.mem[a]; }],
[0x0000, 0x03ff, 0x3ff, (a) => { return this.rom[a]; }],
[0x0400, 0x3fff, 0x3fff, (a) => { return this.getPagedROM(a,1); }],
[0x4000, 0x7fff, 0x3fff, (a) => { return this.getPagedROM(a,2); }],
[0x8000, 0xbfff, 0x3fff, (a) => {
var reg0 = this.pagingRegisters[0]; // RAM select?
if (reg0 & 0x8) {
return this.cartram.mem[(reg0 & 0x4) ? a+0x4000 : a];
} else {
return this.getPagedROM(a,3);
}
}],
]),
write: newAddressDecoder([
[0xc000, 0xfffb, 0x1fff, (a,v) => {
this.ram.mem[a] = v;
}],
[0xfffc, 0xffff, 0x3, (a,v) => {
this.pagingRegisters[a] = v;
this.ram.mem[a+0x1ffc] = v;
}],
[0x8000, 0xbfff, 0x3fff, (a,v) => {
var reg0 = this.pagingRegisters[0]; // RAM select?
if (reg0 & 0x8) {
if (this.cartram.mem.length == 0)
this.cartram = new RAM(0x8000); // create cartridge RAM lazily
this.cartram.mem[(reg0 & 0x4) ? a+0x4000 : a] = v;
}
}],
]),
isContended: () => { return false; },
};
}
loadROM(title, data) {
if (data.length <= 0xc000) {
this.rom = padBytes(data, 0xc000);
this.romPageMask = 3; // only pages 0, 1, 2
} else {
switch (data.length) {
case 0x10000:
case 0x20000:
case 0x40000:
case 0x80000:
this.rom = data;
this.romPageMask = (data.length >> 14) - 1; // div $4000
break;
default:
throw "Unknown rom size: $" + hex(data.length);
}
}
//console.log("romPageMask: " + hex(this.romPageMask));
this.reset();
}
loadState(state) {
super.loadState(state);
this.pagingRegisters.set(state.pr);
this.cartram.mem.set(state.cr);
}
saveState() {
var state = super.saveState();
state['pr'] = this.pagingRegisters.slice(0);
state['cr'] = this.cartram.mem.slice(0);
return state;
}
getDebugInfo(category, state) {
switch (category) {
case 'CPU':
return super.getDebugInfo(category, state) +
"\nBank Regs: " + this.pagingRegisters + "\n";
default: return super.getDebugInfo(category, state);
}
}
}
///
PLATFORMS['sms-sg1000-libcv'] = _SG1000Platform;
PLATFORMS['sms-sms-libcv'] = _SMSPlatform;
PLATFORMS['sms-sg1000-libcv'] = SG1000Platform;
PLATFORMS['sms-sms-libcv'] = SMSPlatform;

View File

@ -7,7 +7,7 @@ import * as bootstrap from "bootstrap";
import { CodeProject } from "./project";
import { WorkerResult, WorkerOutput, VerilogOutput, SourceFile, WorkerError, FileData } from "./workertypes";
import { ProjectWindows } from "./windows";
import { Platform, Preset, DebugSymbols } from "./baseplatform";
import { Platform, Preset, DebugSymbols, DebugEvalCondition } from "./baseplatform";
import { PLATFORMS } from "./emu";
import * as Views from "./views";
import { createNewPersistentStore } from "./store";
@ -716,7 +716,7 @@ function runToCursor() {
if (platform.runToPC) {
platform.runToPC(pc);
} else {
platform.runEval(function(c) {
platform.runEval((c) => {
return c.PC == pc;
});
}
@ -748,7 +748,7 @@ function resetAndDebug() {
platform.reset();
setupBreakpoint("reset");
if (platform.runEval)
platform.runEval(function(c) { return true; }); // break immediately
platform.runEval((c) => { return true; }); // break immediately
else
; // TODO???
} else {
@ -760,9 +760,9 @@ var lastBreakExpr = "c.PC == 0x6000";
function _breakExpression() {
var exprs = window.prompt("Enter break expression", lastBreakExpr);
if (exprs) {
var fn = new Function('c', 'return (' + exprs + ');');
var fn = new Function('c', 'return (' + exprs + ');').bind(platform);
setupBreakpoint();
platform.runEval(fn);
platform.runEval(fn as DebugEvalCondition);
lastBreakExpr = exprs;
}
}

View File

@ -91,19 +91,14 @@ export class TMS9918A {
RGBA(204, 204, 204),
RGBA(255, 255, 255)
];
this.reset();
}
reset() {
var i;
for (i = 0; i < this.ram.length; i++) {
this.ram[i] = 0;
}
for (i = 0; i < this.registers.length; i++) {
this.registers[i] = 0;
}
this.ram.fill(0);
this.registers.fill(0);
this.addressRegister = 0;
this.statusRegister = 0;
@ -499,6 +494,7 @@ export class TMS9918A {
writeData(i:number) {
this.ram[this.addressRegister++] = i;
this.addressRegister &= this.ramMask;
this.latch = false;
this.redrawRequired = true;
}
@ -516,6 +512,7 @@ export class TMS9918A {
var i = this.prefetchByte;
this.prefetchByte = this.ram[this.addressRegister++];
this.addressRegister &= this.ramMask;
this.latch = false;
return i;
}
@ -678,6 +675,13 @@ export class SMSVDP extends TMS9918A {
registers = new Uint8Array(16); // 8 more registers (actually only 5)
vramUntwiddled = new Uint8Array(0x8000);
reset() {
super.reset();
this.writeToCRAM = false;
this.cram.fill(0);
this.cpalette.fill(0);
this.vramUntwiddled.fill(0);
}
updateMode(reg0:number, reg1:number) {
if (reg0 & 0x04) {
this.screenMode = TMS9918A_Mode.MODE4;
@ -697,11 +701,12 @@ export class SMSVDP extends TMS9918A {
}
setVDPWriteRegister(i:number) {
super.setVDPWriteRegister(i);
this.writeToCRAM = false;
//this.writeToCRAM = false; // TODO?
this.ramMask = 0x3fff;
}
setVDPWriteCommand3(i:number) {
this.writeToCRAM = true;
//this.addressRegister &= 0x1f; // TODO?
}
writeData(i:number) {
if (this.writeToCRAM) {
@ -715,7 +720,18 @@ export class SMSVDP extends TMS9918A {
super.writeData(i);
this.writeTwiddled(oldAddress, i);
}
this.latch = false;
}
readData() : number {
if (this.writeToCRAM) {
var palindex = this.addressRegister++ & (this.cram.length-1);
this.addressRegister &= this.ramMask;
return this.cram[palindex];
} else {
return super.readData();
}
}
writeTwiddled(vdp_addr:number, val:number) {
var planarBase = vdp_addr & 0x3ffc;
var twiddledBase = planarBase * 2;