1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2025-02-23 18:29:02 +00:00

nes updates, runToFrameClock()

This commit is contained in:
Steven Hugg 2019-04-06 21:47:42 -04:00
parent 810d0af58b
commit 0a9fffee73
8 changed files with 140 additions and 103 deletions

View File

@ -116,6 +116,8 @@ TODO:
- debug highlight doesn't go away when debugging -> running - debug highlight doesn't go away when debugging -> running
- replay doesn't work for nes (force background tile redraw) - replay doesn't work for nes (force background tile redraw)
- running profiler while replaying? grand unified replay? - running profiler while replaying? grand unified replay?
- click on profiler to step to position
- breakpoints stop profiler from running
WEB WORKER FORMAT WEB WORKER FORMAT

View File

@ -9,9 +9,10 @@
;;;;; OTHER VARIABLES ;;;;; OTHER VARIABLES
seg.u RAM seg.u RAM
; page-align to prevent messing up the timing
org $300 org $300
LineXLo ds 224 LineXLo ds 224
align $100
LineXHi ds 224 LineXHi ds 224
;;;;; NES CARTRIDGE HEADER ;;;;; NES CARTRIDGE HEADER
@ -74,11 +75,11 @@ SetPalette: subroutine
; set sprite 0 ; set sprite 0
SetSprite0: subroutine SetSprite0: subroutine
sta $200 ;y sta $200 ;y
lda #1 ;code lda #$01 ;code
sta $201 sta $201
lda #0 ;flags lda #$20 ;flags
sta $202 sta $202
lda #8 ;xpos lda #$fe ;xpos
sta $203 sta $203
rts rts
@ -90,7 +91,10 @@ SetSprite0: subroutine
NMIHandler: subroutine NMIHandler: subroutine
SAVE_REGS SAVE_REGS
lda #112 lda #0
sta PPU_SCROLL
sta PPU_SCROLL
lda #111
jsr SetSprite0 jsr SetSprite0
; load sprites ; load sprites
lda #$02 lda #$02

View File

@ -106,12 +106,18 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
constraints = null; constraints = null;
// TODO: if jump to zero-page, maybe assume RTS? // TODO: if jump to zero-page, maybe assume RTS?
switch (meta.opcode) { switch (meta.opcode) {
/* case 0x19: case 0x1d:
case 0xb9: // TODO: hack for zero page,y case 0x39: case 0x3d:
if (addr < 0x100) case 0x59: case 0x5d:
meta.maxCycles -= 1; case 0x79: case 0x7d:
case 0x99: case 0x9d:
case 0xa9: case 0xad:
case 0xb9: case 0xbd: case 0xbc: case 0xbe:
case 0xd9: case 0xdd:
case 0xf9: case 0xfd:
if (lob == 0)
meta.maxCycles -= 1; // no page boundary crossed
break; break;
*/
// TODO: only VCS // TODO: only VCS
case 0x85: case 0x85:
if (lob == 0x2) { // STA WSYNC if (lob == 0x2) { // STA WSYNC

View File

@ -85,6 +85,7 @@ export interface Platform {
runUntilReturn?() : void; runUntilReturn?() : void;
stepBack?() : void; stepBack?() : void;
runEval?(evalfunc : DebugEvalCondition) : void; runEval?(evalfunc : DebugEvalCondition) : void;
runToFrameClock?(clock : number) : void;
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
getSP?() : number; getSP?() : number;
@ -328,6 +329,16 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
} }
}); });
} }
runToFrameClock?(clock : number) : void {
this.restartDebugging();
this.debugTargetClock = clock;
this.setDebugCondition( () => {
if (this.debugClock++ > this.debugTargetClock) {
this.breakpointHit(this.debugClock-1);
return true;
}
});
}
step() { step() {
var previousPC = -1; var previousPC = -1;
this.setDebugCondition( () => { this.setDebugCondition( () => {
@ -678,12 +689,12 @@ export abstract class Base6809Platform extends BaseZ80Platform {
return cpu; return cpu;
} }
runUntilReturn() { runUntilReturn() {
var depth = 1; var depth = 1;
this.runEval((c:CpuState) => { this.runEval((c:CpuState) => {
if (depth <= 0) if (depth <= 0)
return true; return true;
var op = this.readAddress(c.PC); var op = this.readAddress(c.PC);
// TODO: 6809 opcodes // TODO: 6809 opcodes
if (op == 0x9d || op == 0xad || op == 0xbd) // CALL if (op == 0x9d || op == 0xad || op == 0xbd) // CALL
depth++; depth++;
@ -693,7 +704,7 @@ export abstract class Base6809Platform extends BaseZ80Platform {
}); });
} }
cpuStateToLongString(c:CpuState) { cpuStateToLongString(c:CpuState) {
return cpuStateToLongString_6809(c); return cpuStateToLongString_6809(c);
} }
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine { disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {

View File

@ -535,7 +535,7 @@ export class Toolbar {
icon = '<span class="glyphicon ' + icon + '" aria-hidden="true"></span>'; icon = '<span class="glyphicon ' + icon + '" aria-hidden="true"></span>';
} }
btn.html(icon); btn.html(icon);
btn.prop("title", alttext + " (" + key + ")"); btn.prop("title", key ? (alttext+" ("+key+")") : alttext);
btn.click(fn); btn.click(fn);
this.grp.append(btn); this.grp.append(btn);
} }

View File

@ -60,103 +60,108 @@ const JSNES_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_D, 1, 7], [Keys.VK_D, 1, 7],
]); ]);
const _JSNESPlatform = function(mainElement) { class JSNESPlatform extends Base6502Platform implements Platform {
var nes; mainElement;
var rom; nes;
var video, audio, timer; video;
const audioFrequency = 44030; //44100 audio;
var frameindex = 0; timer;
var ntvideo; audioFrequency = 44030; //44100
var ntlastbuf; frameindex = 0;
ntvideo;
ntlastbuf;
class JSNESPlatform extends Base6502Platform implements Platform { constructor(mainElement) {
super();
this.mainElement = mainElement;
}
getPresets() { return JSNES_PRESETS; } getPresets() { return JSNES_PRESETS; }
start() { start() {
this.debugPCDelta = 1; this.debugPCDelta = 1;
var debugbar = $("<div>").appendTo(mainElement); var debugbar = $("<div>").appendTo(this.mainElement);
audio = new SampleAudio(audioFrequency); this.audio = new SampleAudio(this.audioFrequency);
video = new RasterVideo(mainElement,256,224,{overscan:true}); this.video = new RasterVideo(this.mainElement,256,224,{overscan:true});
video.create(); this.video.create();
// debugging view // debugging view
ntvideo = new RasterVideo(mainElement,512,480,{overscan:false}); this.ntvideo = new RasterVideo(this.mainElement,512,480,{overscan:false});
ntvideo.create(); this.ntvideo.create();
$(ntvideo.canvas).hide(); $(this.ntvideo.canvas).hide();
ntlastbuf = new Uint32Array(0x1000); this.ntlastbuf = new Uint32Array(0x1000);
// toggle buttons // toggle buttons (TODO)
$('<button>').text("Video").appendTo(debugbar).click(() => { $(video.canvas).toggle() }); $('<button>').text("Video").appendTo(debugbar).click(() => { $(this.video.canvas).toggle() });
$('<button>').text("Nametable").appendTo(debugbar).click(() => { $(ntvideo.canvas).toggle() }); $('<button>').text("Nametable").appendTo(debugbar).click(() => { $(this.ntvideo.canvas).toggle() });
var idata = video.getFrameData(); var idata = this.video.getFrameData();
nes = new jsnes.NES({ this.nes = new jsnes.NES({
onFrame: (frameBuffer : number[]) => { onFrame: (frameBuffer : number[]) => {
for (var i=0; i<frameBuffer.length; i++) for (var i=0; i<frameBuffer.length; i++)
idata[i] = frameBuffer[i] | 0xff000000; idata[i] = frameBuffer[i] | 0xff000000;
video.updateFrame(); this.video.updateFrame();
frameindex++; this.frameindex++;
this.updateDebugViews(); this.updateDebugViews();
}, },
onAudioSample: (left:number, right:number) => { onAudioSample: (left:number, right:number) => {
if (frameindex < 10) if (this.frameindex < 10)
audio.feedSample(0, 1); // avoid popping at powerup this.audio.feedSample(0, 1); // avoid popping at powerup
else else
audio.feedSample(left+right, 1); this.audio.feedSample(left+right, 1);
}, },
onStatusUpdate: function(s) { onStatusUpdate: function(s) {
console.log(s); console.log(s);
}, },
//TODO: onBatteryRamWrite //TODO: onBatteryRamWrite
}); });
//nes.ppu.clipToTvSize = false; //this.nes.ppu.clipToTvSize = false;
nes.stop = () => { this.nes.stop = () => {
// TODO: trigger breakpoint // TODO: trigger breakpoint
console.log(nes.cpu.toJSON()); console.log(this.nes.cpu.toJSON());
throw new EmuHalt("CPU STOPPED @ PC $" + hex(nes.cpu.REG_PC)); throw new EmuHalt("CPU STOPPED @ PC $" + hex(this.nes.cpu.REG_PC));
}; };
// insert debug hook // insert debug hook
nes.cpu._emulate = nes.cpu.emulate; this.nes.cpu._emulate = this.nes.cpu.emulate;
nes.cpu.emulate = () => { this.nes.cpu.emulate = () => {
var cycles = nes.cpu._emulate(); var cycles = this.nes.cpu._emulate();
this.evalDebugCondition(); this.evalDebugCondition();
return cycles; return cycles;
} }
timer = new AnimationTimer(60, this.nextFrame.bind(this)); this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
// set keyboard map // set keyboard map
setKeyboardFromMap(video, [], JSNES_KEYCODE_MAP, (o,key,code,flags) => { setKeyboardFromMap(this.video, [], JSNES_KEYCODE_MAP, (o,key,code,flags) => {
if (flags & KeyFlags.KeyDown) if (flags & KeyFlags.KeyDown)
nes.buttonDown(o.index+1, o.mask); // controller, button this.nes.buttonDown(o.index+1, o.mask); // controller, button
else if (flags & KeyFlags.KeyUp) else if (flags & KeyFlags.KeyUp)
nes.buttonUp(o.index+1, o.mask); // controller, button this.nes.buttonUp(o.index+1, o.mask); // controller, button
}); });
//var s = ''; nes.ppu.palTable.curTable.forEach((rgb) => { s += "0x"+hex(rgb,6)+", "; }); console.log(s); //var s = ''; nes.ppu.palTable.curTable.forEach((rgb) => { s += "0x"+hex(rgb,6)+", "; }); console.log(s);
} }
advance(novideo : boolean) { advance(novideo : boolean) {
nes.frame(); this.nes.frame();
} }
updateDebugViews() { updateDebugViews() {
// don't update if view is hidden // don't update if view is hidden
if (! $(ntvideo.canvas).is(":visible")) if (! $(this.ntvideo.canvas).is(":visible"))
return; return;
var a = 0; var a = 0;
var attraddr = 0; var attraddr = 0;
var idata = ntvideo.getFrameData(); var idata = this.ntvideo.getFrameData();
var baseTile = nes.ppu.regS === 0 ? 0 : 256; var baseTile = this.nes.ppu.regS === 0 ? 0 : 256;
for (var row=0; row<60; row++) { for (var row=0; row<60; row++) {
for (var col=0; col<64; col++) { for (var col=0; col<64; col++) {
a = 0x2000 + (col&31) + ((row%30)*32); a = 0x2000 + (col&31) + ((row%30)*32);
if (col >= 32) a += 0x400; if (col >= 32) a += 0x400;
if (row >= 30) a += 0x800; if (row >= 30) a += 0x800;
var name = nes.ppu.mirroredLoad(a) + baseTile; var name = this.nes.ppu.mirroredLoad(a) + baseTile;
var t = nes.ppu.ptTile[name]; var t = this.nes.ppu.ptTile[name];
attraddr = (a & 0x2c00) | 0x3c0 | (a & 0x0C00) | ((a >> 4) & 0x38) | ((a >> 2) & 0x07); attraddr = (a & 0x2c00) | 0x3c0 | (a & 0x0C00) | ((a >> 4) & 0x38) | ((a >> 2) & 0x07);
var attr = nes.ppu.mirroredLoad(attraddr); var attr = this.nes.ppu.mirroredLoad(attraddr);
var tag = name ^ (attr<<9) ^ 0x80000000; var tag = name ^ (attr<<9) ^ 0x80000000;
if (tag != ntlastbuf[a & 0xfff]) { if (tag != this.ntlastbuf[a & 0xfff]) {
ntlastbuf[a & 0xfff] = tag; this.ntlastbuf[a & 0xfff] = tag;
var i = row*64*8*8 + col*8; var i = row*64*8*8 + col*8;
var j = 0; var j = 0;
var attrshift = (col&2) + ((a&0x40)>>4); var attrshift = (col&2) + ((a&0x40)>>4);
@ -165,7 +170,7 @@ const _JSNESPlatform = function(mainElement) {
for (var x=0; x<8; x++) { for (var x=0; x<8; x++) {
var color = t.pix[j++]; var color = t.pix[j++];
if (color) color += coloradd; if (color) color += coloradd;
var rgb = nes.ppu.imgPalette[color]; var rgb = this.nes.ppu.imgPalette[color];
idata[i++] = rgb | 0xff000000; idata[i++] = rgb | 0xff000000;
} }
i += 64*8-8; i += 64*8-8;
@ -173,13 +178,13 @@ const _JSNESPlatform = function(mainElement) {
} }
} }
} }
ntvideo.updateFrame(); this.ntvideo.updateFrame();
} }
loadROM(title, data) { loadROM(title, data) {
var romstr = byteArrayToString(data); var romstr = byteArrayToString(data);
nes.loadROM(romstr); this.nes.loadROM(romstr);
frameindex = 0; this.frameindex = 0;
} }
newCodeAnalyzer() { newCodeAnalyzer() {
return new CodeAnalyzer_nes(this); return new CodeAnalyzer_nes(this);
@ -190,42 +195,42 @@ const _JSNESPlatform = function(mainElement) {
getDefaultExtension() { return ".c"; }; getDefaultExtension() { return ".c"; };
reset() { reset() {
//nes.cpu.reset(); // doesn't work right, crashes //this.nes.cpu.reset(); // doesn't work right, crashes
nes.cpu.requestIrq(nes.cpu.IRQ_RESET); this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
} }
isRunning() { isRunning() {
return timer.isRunning(); return this.timer.isRunning();
} }
pause() { pause() {
timer.stop(); this.timer.stop();
audio.stop(); this.audio.stop();
} }
resume() { resume() {
timer.start(); this.timer.start();
audio.start(); this.audio.start();
} }
runToVsync() { runToVsync() {
var frame0 = frameindex; var frame0 = this.frameindex;
this.runEval((c) => { return frameindex>frame0; }); this.runEval((c) => { return this.frameindex>frame0; });
} }
getRasterScanline() : number { getRasterScanline() : number {
return nes.ppu.scanline; return this.nes.ppu.scanline;
} }
readVRAMAddress(addr : number) : number { readVRAMAddress(addr : number) : number {
return nes.ppu.vramMem[addr & 0x7fff]; return this.nes.ppu.vramMem[addr & 0x7fff];
} }
getCPUState() { getCPUState() {
var c = nes.cpu.toJSON(); var c = this.nes.cpu.toJSON();
this.copy6502REGvars(c); this.copy6502REGvars(c);
return c; return c;
} }
// TODO don't need to save ROM? // TODO don't need to save ROM?
saveState() { saveState() {
//var s = $.extend(true, {}, nes); //var s = $.extend(true, {}, this.nes);
var s = nes.toJSON(); var s = this.nes.toJSON();
s.c = s.cpu; s.c = s.cpu;
this.copy6502REGvars(s.c); this.copy6502REGvars(s.c);
s.b = s.cpu.mem = s.cpu.mem.slice(0); s.b = s.cpu.mem = s.cpu.mem.slice(0);
@ -235,28 +240,28 @@ const _JSNESPlatform = function(mainElement) {
return s; return s;
} }
loadState(state) { loadState(state) {
nes.fromJSON(state); this.nes.fromJSON(state);
//nes.cpu.fromJSON(state.cpu); //this.nes.cpu.fromJSON(state.cpu);
//nes.mmap.fromJSON(state.mmap); //this.nes.mmap.fromJSON(state.mmap);
//nes.ppu.fromJSON(state.ppu); //this.nes.ppu.fromJSON(state.ppu);
nes.cpu.mem = state.cpu.mem.slice(0); this.nes.cpu.mem = state.cpu.mem.slice(0);
nes.ppu.vramMem = state.ppu.vramMem.slice(0); this.nes.ppu.vramMem = state.ppu.vramMem.slice(0);
nes.ppu.spriteMem = state.ppu.spriteMem.slice(0); this.nes.ppu.spriteMem = state.ppu.spriteMem.slice(0);
this.loadControlsState(state.ctrl); this.loadControlsState(state.ctrl);
//$.extend(nes, state); //$.extend(this.nes, state);
} }
saveControlsState() { saveControlsState() {
return { return {
c1: nes.controllers[1].state.slice(0), c1: this.nes.controllers[1].state.slice(0),
c2: nes.controllers[2].state.slice(0), c2: this.nes.controllers[2].state.slice(0),
}; };
} }
loadControlsState(state) { loadControlsState(state) {
nes.controllers[1].state = state.c1; this.nes.controllers[1].state = state.c1;
nes.controllers[2].state = state.c2; this.nes.controllers[2].state = state.c2;
} }
readAddress(addr) { readAddress(addr) {
return nes.cpu.mem[addr] & 0xff; return this.nes.cpu.mem[addr] & 0xff;
} }
copy6502REGvars(c) { copy6502REGvars(c) {
c.T = 0; c.T = 0;
@ -277,7 +282,7 @@ const _JSNESPlatform = function(mainElement) {
} }
getDebugCategories() { getDebugCategories() {
return super.getDebugCategories().concat(['PPU', 'Mapper']); return super.getDebugCategories().concat(['PPU','Mapper']);
} }
getDebugInfo(category, state) { getDebugInfo(category, state) {
switch (category) { switch (category) {
@ -347,6 +352,9 @@ const _JSNESPlatform = function(mainElement) {
mapperStateToLongString(mmap, mem) { mapperStateToLongString(mmap, mem) {
//console.log(mmap, mem); //console.log(mmap, mem);
var s = ""; var s = "";
if (this.nes.rom) {
s += "Mapper " + this.nes.rom.mapperType + "\n";
}
if (mmap.irqCounter !== undefined) { if (mmap.irqCounter !== undefined) {
s += "\nIRQ Counter: " + mmap.irqCounter; s += "\nIRQ Counter: " + mmap.irqCounter;
s += "\n IRQ Latch: " + mmap.irqLatchValue; s += "\n IRQ Latch: " + mmap.irqLatchValue;
@ -358,8 +366,6 @@ const _JSNESPlatform = function(mainElement) {
s += "\n"; s += "\n";
return s; return s;
} }
}
return new JSNESPlatform();
} }
/// MAME support /// MAME support
@ -384,8 +390,8 @@ class NESMAMEPlatform extends BaseMAMEPlatform implements Platform {
}); });
} else { } else {
// look at iNES header for PRG and CHR ROM lengths // look at iNES header for PRG and CHR ROM lengths
var prgromlen = data[4] * 0x2000; var prgromlen = data[4] * 0x4000;
var chrromlen = data[5] * 0x1000; var chrromlen = data[5] * 0x2000;
this.loadROMFile(data); this.loadROMFile(data);
this.loadRegion(":nes_slot:cart:prg_rom", data.slice(0x10, 0x10+prgromlen)); this.loadRegion(":nes_slot:cart:prg_rom", data.slice(0x10, 0x10+prgromlen));
this.loadRegion(":nes_slot:cart:chr_rom", data.slice(0x10+prgromlen, 0x10+prgromlen+chrromlen)); this.loadRegion(":nes_slot:cart:chr_rom", data.slice(0x10+prgromlen, 0x10+prgromlen+chrromlen));
@ -400,6 +406,6 @@ class NESMAMEPlatform extends BaseMAMEPlatform implements Platform {
/// ///
PLATFORMS['nes'] = _JSNESPlatform; PLATFORMS['nes'] = JSNESPlatform;
PLATFORMS['nes.mame'] = NESMAMEPlatform; PLATFORMS['nes.mame'] = NESMAMEPlatform;

View File

@ -742,6 +742,7 @@ function uiDebugCallback(state) {
lastDebugState = state; lastDebugState = state;
showDebugInfo(state); showDebugInfo(state);
projectWindows.refresh(true); projectWindows.refresh(true);
debugTickPaused = true;
} }
function setupDebugCallback(btnid? : string) { function setupDebugCallback(btnid? : string) {

View File

@ -861,9 +861,10 @@ export class ProfileView implements ProjectView {
var l = f.lines[row]; var l = f.lines[row];
if (!l) return; if (!l) return;
var lastsym = ''; var lastsym = '';
for (var i=l.start; i<=l.end; i++) { var canDebug = platform.runToFrameClock;
var pc = f.iptab[i]; for (let i=l.start; i<=l.end; i++) {
var sym = this.symcache[pc]; let pc = f.iptab[i];
let sym = this.symcache[pc];
if (!sym) { if (!sym) {
sym = lookupSymbol(platform, pc, false); sym = lookupSymbol(platform, pc, false);
this.symcache[pc] = sym; this.symcache[pc] = sym;
@ -873,7 +874,13 @@ export class ProfileView implements ProjectView {
if (sym.startsWith('_')) cls = "profiler-cident"; if (sym.startsWith('_')) cls = "profiler-cident";
else if (sym.startsWith('@')) cls = "profiler-local"; else if (sym.startsWith('@')) cls = "profiler-local";
else if (/^\d*[.]/.exec(sym)) cls = "profiler-local"; else if (/^\d*[.]/.exec(sym)) cls = "profiler-local";
div.appendChild(createTextSpan(' '+sym, cls)); var span = createTextSpan(' '+sym, cls);
if (canDebug) {
$(span).click(() => {
platform.runToFrameClock(i);
});
}
div.appendChild(span);
lastsym = sym; lastsym = sym;
} }
} }