debugging refactoring; fixed up embed.html; started DASM macro parse; Z80 stack view; replay wraps buffer > 120 secs; verilog edits

This commit is contained in:
Steven Hugg 2018-08-24 22:55:16 -04:00
parent cb22adeb62
commit 5b3c415c6f
17 changed files with 158 additions and 119 deletions

View File

@ -34,7 +34,6 @@ TODO:
- facade/kbd shortcuts for emulators, focus
- update Javatari version? (and others?)
- unify versioning
- more UI tests
- disassembler for uploaded ROMs
- show tool-specific (readonly) include files
- verilog debugging/reloading makes it slow
@ -45,14 +44,13 @@ TODO:
- Verilog compile spins forever?
- go to error in include files
- BOM in upload/download?
- stack view for Z80 platforms using memory map
- online tools for music etc
- tools (memory, disasm) use debugging state
- text log debugging script
- NES crt should mark raster pos when debugging
- intro/help text for each platform
- vscode/atom extension?
- navigator.getGamepads
- VCS library
FYI: Image links for the books on http://8bitworkshop.com/ are broken
On the website the additional grey spacing next to the program line numbers is not dynamically resized when the web browser window size is changed. Intentional?

View File

@ -79,7 +79,7 @@ window.Javatari.AUTO_START = false;
var PLATFORMS = exports.PLATFORMS;
var platform, platform_id;
var qs = (function (a) {
var _qs = (function (a) {
if (!a || a.length == 0)
return {};
var b = {};
@ -135,7 +135,7 @@ function addPageFocusHandlers() {
});
}
function startPlatform() {
function startPlatform(qs) {
if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'.");
platform = new PLATFORMS[platform_id]($("#emulator")[0]);
platform.start();
@ -148,6 +148,17 @@ function startPlatform() {
return true;
}
function loadPlatform(qs) {
if (qs.data) qs = qs.data;
platform_id = qs['p'];
if (!platform_id) throw('No platform variable!');
var scriptfn = 'gen/platform/' + platform_id.split(/[.-]/)[0] + '.js';
loadScript(scriptfn, () => {
console.log("loaded platform", platform_id);
startPlatform(qs);
});
}
function loadScript(scriptfn, onload) {
var script = document.createElement('script');
script.onload = onload;
@ -158,14 +169,8 @@ function loadScript(scriptfn, onload) {
// start
function startEmbed() {
installErrorHandler();
// add default platform?
platform_id = qs['p'];
if (!platform_id) throw('No platform variable!');
var scriptfn = 'gen/platform/' + platform_id.split(/[.-]/)[0] + '.js';
loadScript(scriptfn, () => {
console.log("loaded platform", platform_id);
startPlatform();
});
window.addEventListener("message", loadPlatform, false);
if (_qs['p']) loadPlatform(_qs);
}
startEmbed();

View File

@ -234,6 +234,8 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
<p>You can also embed it into an IFRAME:</p>
<textarea rows="4" cols="80" id="embedIframeTextarea" class="cliptext"></textarea>
<button type="button" class="btn btn-primary" data-clipboard-target="#embedIframeTextarea">Copy IFRAME Tag</button>
<p id="embedAdviceWarnIE">Note: These links may be too long for IE/Edge browsers.</p>
<p id="embedAdviceWarnAll">Note: These links may be too long for some browsers.</p>
</div>
<div class="modal-footer">
Choose one (or none) then

View File

@ -13,7 +13,7 @@ module RAM_sync(clk, addr, din, dout, we);
output [D-1:0] dout; // data output
input we; // write enable
reg [D-1:0] mem [0:(1<<A)-1]; // (1<<A)xD bit memory
reg [D-1:0] mem [1<<A]; // (1<<A)xD bit memory
always @(posedge clk) begin
if (we) // if write enabled
@ -34,7 +34,7 @@ module RAM_async(clk, addr, din, dout, we);
output [D-1:0] dout; // data output
input we; // write enable
reg [D-1:0] mem [0:(1<<A)-1]; // (1<<A)xD bit memory
reg [D-1:0] mem [1<<A]; // (1<<A)xD bit memory
always @(posedge clk) begin
if (we) // if write enabled
@ -55,7 +55,7 @@ module RAM_async_tristate(clk, addr, data, we);
inout [D-1:0] data; // data in/out
input we; // write enable
reg [D-1:0] mem [0:(1<<A)-1]; // (1<<A)xD bit memory
reg [D-1:0] mem [1<<A]; // (1<<A)xD bit memory
always @(posedge clk) begin
if (we) // if write enabled

View File

@ -1,5 +1,5 @@
import { RAM, RasterVideo, dumpRAM, dumpStackToString } from "./emu";
import { RAM, RasterVideo, dumpRAM, lookupSymbol } from "./emu";
import { hex } from "./util";
import { CodeAnalyzer } from "./analysis";
@ -291,7 +291,7 @@ export abstract class Base6502Platform extends BaseFrameBasedPlatform {
switch (category) {
case 'CPU': return cpuStateToLongString_6502(state.c);
case 'ZPRAM': return dumpRAM(state.b, 0x0, 0x100);
case 'Stack': return dumpStackToString(state.b, 0x100, 0x1ff, 0x100+state.c.SP);
case 'Stack': return dumpStackToString(<Platform><any>this, state.b, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
}
}
}
@ -502,11 +502,18 @@ export abstract class BaseZ80Platform extends BaseDebugPlatform {
//this.getOpcodeMetadata = function() { }
getDebugCategories() {
return ['CPU'];
return ['CPU','Stack'];
}
getDebugInfo(category:string, state:EmuState) : string {
switch (category) {
case 'CPU': return cpuStateToLongString_Z80(state.c);
case 'Stack': {
var sp = state.c.SP;
var start = ((sp-1) & 0xff00);
var end = start + 0xff;
if (sp == 0) sp = 0x10000;
return dumpStackToString(<Platform><any>this, [], start, end, sp, 0xcd);
}
}
}
}
@ -852,3 +859,32 @@ export abstract class BaseMAMEPlatform {
}
}
export function dumpStackToString(platform:Platform, mem:number[], start:number, end:number, sp:number, jsrop:number) : string {
var s = "";
var nraw = 0;
//s = dumpRAM(mem.slice(start,start+end+1), start, end-start+1);
function read(addr) {
if (addr < mem.length) return mem[addr];
else return platform.readAddress(addr);
}
while (sp < end) {
sp++;
// see if there's a JSR on the stack here
// TODO: make work with roms and memory maps
var addr = read(sp) + read(sp+1)*256;
var jsrofs = (jsrop == 0xcd) ? -3 : -2;
var opcode = read(addr + jsrofs); // might be out of bounds
if (opcode == jsrop) { // JSR
s += "\n$" + hex(sp) + ": ";
s += hex(addr,4) + " " + lookupSymbol(addr);
sp++;
nraw = 0;
} else {
if (nraw == 0)
s += "\n$" + hex(sp) + ": ";
s += hex(read(sp+1)) + " ";
if (++nraw == 8) nraw = 0;
}
}
return s+"\n";
}

View File

@ -430,12 +430,12 @@ export function newAddressDecoder(table : AddressDecoderEntry[], options?:Addres
// STACK DUMP
declare var addr2symbol; // address to symbol name map (TODO: import)
var addr2symbol = {}; // address to symbol name map (TODO: import)
function lookupSymbol(addr) {
export function lookupSymbol(addr) {
var start = addr;
var foundsym;
while (addr >= 0) {
while (addr2symbol && addr >= 0) {
var sym = addr2symbol[addr];
if (sym && sym.startsWith('_')) { // return first C symbol we find
return addr2symbol[addr] + " + " + (start-addr);
@ -447,27 +447,3 @@ function lookupSymbol(addr) {
return foundsym || "";
}
export function dumpStackToString(mem:number[], start:number, end:number, sp:number) : string {
var s = "";
var nraw = 0;
//s = dumpRAM(mem.slice(start,start+end+1), start, end-start+1);
while (sp < end) {
sp++;
// see if there's a JSR on the stack here
// TODO: make work with roms and memory maps
var addr = mem[sp] + mem[sp+1]*256;
var opcode = mem[addr-2]; // might be out of bounds
if (opcode == 0x20) { // JSR
s += "\n$" + hex(sp) + ": ";
s += hex(addr,4) + " " + lookupSymbol(addr);
sp++;
nraw = 0;
} else {
if (nraw == 0)
s += "\n$" + hex(sp) + ": ";
s += hex(mem[sp+1]) + " ";
if (++nraw == 8) nraw = 0;
}
}
return s+"\n";
}

View File

@ -196,15 +196,12 @@ const _Apple2Platform = function(mainElement) {
// 262.5 scanlines per frame
var clock = 0;
var debugCond = this.getDebugCallback();
var rendered = false;
for (var sl=0; sl<262; sl++) {
for (var i=0; i<cpuCyclesPerLine; i++) {
if (debugCond && debugCond()) {
grparams.dirty = grdirty;
grparams.grswitch = grswitch;
ap2disp.updateScreen();
debugCond = null;
rendered = true;
sl = 999;
break;
}
clock++;
cpu.clockPulse();
@ -212,11 +209,9 @@ const _Apple2Platform = function(mainElement) {
}
}
if (!novideo) {
if (!rendered) {
grparams.dirty = grdirty;
grparams.grswitch = grswitch;
ap2disp.updateScreen();
}
grparams.dirty = grdirty;
grparams.grswitch = grswitch;
ap2disp.updateScreen();
video.updateFrame();
}
}

View File

@ -304,21 +304,11 @@ const _GalaxianPlatform = function(mainElement, options) {
}
advance(novideo : boolean) {
var debugCond = this.getDebugCallback();
var targetTstates = cpu.getTstates();
for (var sl=0; sl<scanlinesPerFrame; sl++) {
if (!novideo) {
drawScanline(pixels, sl);
}
targetTstates += cpuCyclesPerLine;
if (debugCond) {
while (cpu.getTstates() < targetTstates) {
if (debugCond && debugCond()) { debugCond = null; }
cpu.runFrame(cpu.getTstates() + 1);
}
} else {
cpu.runFrame(targetTstates);
}
this.runCPU(cpu, cpuCyclesPerLine);
}
// visible area is 256x224 (before rotation)
if (!novideo) {

View File

@ -120,23 +120,8 @@ const _Midway8080BWPlatform = function(mainElement) {
}
advance(novideo : boolean) {
var debugCond = this.getDebugCallback();
var targetTstates = cpu.getTstates();
for (var sl=0; sl<224; sl++) {
targetTstates += cpuCyclesPerLine;
if (debugCond) {
while (cpu.getTstates() < targetTstates) {
if (debugCond && debugCond()) {
debugCond = null;
break;
}
cpu.runFrame(cpu.getTstates() + 1);
}
if (!debugCond)
break;
} else {
cpu.runFrame(targetTstates);
}
this.runCPU(cpu, cpuCyclesPerLine);
if (sl == 95)
cpu.requestInterrupt(0x8); // RST $8
else if (sl == 223)

View File

@ -1,7 +1,7 @@
"use strict";
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502 } from "../baseplatform";
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, dumpStackToString } from "../emu";
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502, dumpStackToString } from "../baseplatform";
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM } from "../emu";
import { hex, lpad, lzgmini } from "../util";
import { CodeAnalyzer_nes } from "../analysis";
import { SampleAudio } from "../audio";
@ -114,6 +114,7 @@ const _JSNESPlatform = function(mainElement) {
var cycles = nes.cpu._emulate();
//if (self.debugCondition && !self.debugBreakState && self.debugClock < 100) console.log(self.debugClock, nes.cpu.REG_PC);
self.evalDebugCondition();
// TODO: doesn't stop on breakpoint
return cycles;
}
timer = new AnimationTimer(60, this.nextFrame.bind(this));
@ -230,7 +231,7 @@ const _JSNESPlatform = function(mainElement) {
switch (category) {
case 'CPU': return cpuStateToLongString_6502(state.c);
case 'ZPRAM': return dumpRAM(state.b, 0x0, 0x100);
case 'Stack': return dumpStackToString(state.b, 0x100, 0x1ff, 0x100+state.c.SP);
case 'Stack': return dumpStackToString(this, state.b, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
case 'PPU': return this.ppuStateToLongString(state.ppu, state.b);
}
}

View File

@ -115,7 +115,10 @@ var AtariVectorPlatform = function(mainElement) {
var debugCond = self.getDebugCallback();
clock = 0;
for (var i=0; i<cpuCyclesPerFrame; i++) {
if (debugCond && debugCond()) { debugCond = null; }
if (debugCond && debugCond()) {
debugCond = null;
break;
}
clock++;
if (--nmicount == 0) {
//console.log("NMI", cpu.saveState());
@ -267,7 +270,10 @@ var AtariColorVectorPlatform = function(mainElement) {
var debugCond = self.getDebugCallback();
clock = 0;
for (var i=0; i<cpuCyclesPerFrame; i++) {
if (debugCond && debugCond()) { debugCond = null; }
if (debugCond && debugCond()) {
debugCond = null;
break;
}
clock++;
if (--nmicount == 0) {
//console.log("NMI", cpu.saveState());

View File

@ -13,9 +13,10 @@ var VERILOG_PRESETS = [
{id:'scoreboard.v', name:'Scoreboard'},
{id:'ball_absolute.v', name:'Ball Motion (absolute position)'},
{id:'ball_slip_counter.v', name:'Ball Motion (slipping counter)'},
{id:'paddles.v', name:'Paddle Inputs'},
{id:'ball_paddle.v', name:'Brick Smash Game'},
{id:'ram1.v', name:'RAM Text Display'},
{id:'switches.v', name:'Switch Inputs'},
{id:'paddles.v', name:'Paddle Inputs'},
{id:'sprite_bitmap.v', name:'Sprite Bitmaps'},
{id:'sprite_renderer.v', name:'Sprite Rendering'},
{id:'racing_game.v', name:'Racing Game'},

View File

@ -13,7 +13,7 @@ export class StateRecorderImpl implements EmuRecorder {
checkpoints : EmuState[];
framerecs : FrameRec[];
frameCount : number;
lastSeekFrame : number = -1;
lastSeekFrame : number;
constructor(platform : Platform) {
this.reset();
@ -29,10 +29,6 @@ export class StateRecorderImpl implements EmuRecorder {
}
frameRequested() : boolean {
// checkpoints full?
if (this.checkpoints.length >= this.maxCheckpoints) {
return false;
}
var controls = {
controls:this.platform.saveControlsState(),
seed:getNoiseSeed()
@ -64,6 +60,14 @@ export class StateRecorderImpl implements EmuRecorder {
recordFrame(state : EmuState) {
this.checkpoints.push(state);
// checkpoints full?
if (this.checkpoints.length > this.maxCheckpoints) {
this.checkpoints.shift(); // remove 1st checkpoint
this.framerecs = this.framerecs.slice(this.checkpointInterval);
this.lastSeekFrame -= this.checkpointInterval;
this.frameCount -= this.checkpointInterval;
if (this.callbackStateChanged) this.callbackStateChanged();
}
}
getStateAtOrBefore(frame : number) : {frame : number, state : EmuState} {

View File

@ -57,7 +57,7 @@ var current_output; // current ROM
var current_preset_entry : Preset; // current preset object (if selected)
var main_file_id : string; // main file ID
var symbolmap; // symbol map
var addr2symbol; // address to symbol name map
declare var addr2symbol; // address to symbol name map
var compparams; // received build params from worker
var store; // persistent store
@ -344,7 +344,10 @@ function _shareEmbedLink(e) {
$("#embedLinkTextarea").text(fulllink);
$("#embedIframeTextarea").text(iframelink);
$("#embedLinkModal").modal('show');
//document.execCommand("copy");
$("#embedAdviceWarnAll").hide();
$("#embedAdviceWarnIE").hide();
if (fulllink.length >= 65536) $("#embedAdviceWarnAll").show();
else if (fulllink.length >= 5120) $("#embedAdviceWarnIE").show();
});
return true;
}

View File

@ -491,11 +491,14 @@ function parseSourceLines(code, lineMatch, offsetMatch, origin) {
function parseDASMListing(code, unresolved, mainFilename) {
// 4 08ee a9 00 start lda #01workermain.js:23:5
var lineMatch = /\s*(\d+)\s+(\S+)\s+([0-9a-f]+)\s+([0-9a-f][0-9a-f ]+)?\s+(.+)?/;
var equMatch = /\bequ\b/;
var lineMatch = /\s*(\d+)\s+(\S+)\s+([0-9a-f]+)\s+([?0-9a-f][?0-9a-f ]+)?\s+(.+)?/i;
var equMatch = /\bequ\b/i;
var macroMatch = /\bMAC\s+(.+)?/i;
var errors = [];
var lines = [];
var macrolines = [];
var lastline = 0;
var macros = {};
for (var line of code.split(/\r?\n/)) {
var linem = lineMatch.exec(line);
if (linem && linem[1]) {
@ -504,9 +507,15 @@ function parseDASMListing(code, unresolved, mainFilename) {
var offset = parseInt(linem[3], 16);
var insns = linem[4];
var restline = linem[5];
if (insns && insns.startsWith('?')) insns = null;
// inside of main file?
if (filename == mainFilename) {
if (insns && !restline.match(equMatch)) {
// look for MAC statement
var macmatch = macroMatch.exec(restline);
if (macmatch) {
macros[macmatch[1]] = {line:parseInt(linem[1]), file:linem[2].toLowerCase()};
}
else if (insns && !restline.match(equMatch)) {
lines.push({
line:linenum,
offset:offset,
@ -524,6 +533,16 @@ function parseDASMListing(code, unresolved, mainFilename) {
insns:null
});
}
// inside of macro?
var mac = macros[filename.toLowerCase()];
if (insns && mac) {
macrolines.push({
filename:mac.file,
line:mac.line+linenum,
offset:offset,
insns:insns
});
}
}
// TODO: check filename too
// TODO: better symbol test (word boundaries)
@ -546,7 +565,8 @@ function parseDASMListing(code, unresolved, mainFilename) {
})
}
}
return {lines:lines, errors:errors};
// TODO: use macrolines
return {lines:lines, macrolines:macrolines, errors:errors};
}
function assembleDASM(step) {
@ -582,7 +602,7 @@ function assembleDASM(step) {
return {errors:errors};
}
var listings = {};
listings[lstpath] = {lines:listing.lines};
listings[lstpath] = listing;
// parse include files
// TODO: kinda wasted effort
for (var fn of step.files) {

View File

@ -99,33 +99,50 @@ function testPlatform(platid, romname, maxframes, callback) {
platform.nextFrame();
if (i==10) {
for (var j=0; j<0x10000; j++)
platform.readAddress(j);
platform.readAddress(j); // make sure readAddress() doesn't leave side effects
}
}
// test replay feature
platform.pause();
if (maxframes > 120*60)
maxframes = 120*60;
assert.equal(maxframes, rec.numFrames());
var state1 = platform.saveState();
assert.equal(1, rec.loadFrame(1));
assert.equal(maxframes, rec.loadFrame(maxframes));
var state2 = platform.saveState();
assert.deepEqual(state1, state2);
// test debug info
var debugs = platform.getDebugCategories();
for (var dcat of debugs) {
var dinfo = platform.getDebugInfo(dcat, state2);
console.log(dcat, dinfo);
assert.ok(dinfo && dinfo.length > 0, dcat + " empty");
assert.ok(dinfo.length < 80*24, dcat + " too long");
assert.ok(dinfo.indexOf('undefined') < 0, dcat + " undefined");
}
return platform;
}
describe('Platform Replay', () => {
it('Should run apple2', () => {
var platform = testPlatform('apple2', 'cosmic.c.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('apple2', 'cosmic.c.rom', 72, (platform, frameno) => {
if (frameno == 62) {
keycallback(32, 32, 1); // space bar
}
});
assert.equal(platform.saveState().kbd, 0x20); // strobe cleared
});
it('Should run > 120 secs', () => {
var platform = testPlatform('apple2', 'mandel.c.rom', 122*60, (platform, frameno) => {
});
});
it('Should run vcs', () => {
var platform = testPlatform('vcs', 'brickgame.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('vcs', 'brickgame.rom', 72, (platform, frameno) => {
if (frameno == 62) {
var cstate = platform.saveControlsState();
cstate.SA = 0xff ^ 0x40; // stick left
platform.loadControlsState(cstate);
@ -136,8 +153,8 @@ describe('Platform Replay', () => {
});
it('Should run nes', () => {
var platform = testPlatform('nes', 'shoot2.c.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('nes', 'shoot2.c.rom', 72, (platform, frameno) => {
if (frameno == 62) {
keycallback(Keys.VK_LEFT.c, Keys.VK_LEFT.c, 1);
}
});
@ -145,16 +162,16 @@ describe('Platform Replay', () => {
});
it('Should run vicdual', () => {
var platform = testPlatform('vicdual', 'snake1.c.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('vicdual', 'snake1.c.rom', 72, (platform, frameno) => {
if (frameno == 62) {
keycallback(Keys.VK_DOWN.c, Keys.VK_DOWN.c, 1);
}
});
});
it('Should run mw8080bw', () => {
var platform = testPlatform('mw8080bw', 'game2.c.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('mw8080bw', 'game2.c.rom', 72, (platform, frameno) => {
if (frameno == 62) {
keycallback(Keys.VK_LEFT.c, Keys.VK_LEFT.c, 1);
}
});
@ -162,8 +179,8 @@ describe('Platform Replay', () => {
});
it('Should run galaxian', () => {
var platform = testPlatform('galaxian-scramble', 'shoot2.c.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('galaxian-scramble', 'shoot2.c.rom', 72, (platform, frameno) => {
if (frameno == 62) {
keycallback(Keys.VK_LEFT.c, Keys.VK_LEFT.c, 1);
}
});
@ -171,23 +188,23 @@ describe('Platform Replay', () => {
});
it('Should run vector', () => {
var platform = testPlatform('vector-z80color', 'game.c.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('vector-z80color', 'game.c.rom', 72, (platform, frameno) => {
if (frameno == 62) {
keycallback(Keys.VK_UP.c, Keys.VK_UP.c, 1);
}
});
});
it('Should run williams', () => {
var platform = testPlatform('williams-z80', 'game1.c.rom', 70, (platform, frameno) => {
if (frameno == 60) {
var platform = testPlatform('williams-z80', 'game1.c.rom', 72, (platform, frameno) => {
if (frameno == 62) {
keycallback(Keys.VK_LEFT.c, Keys.VK_LEFT.c, 1);
}
});
});
/*
it('Should run sound_williams', () => {
var platform = testPlatform('sound_williams-z80', 'swave.c.rom', 70, (platform, frameno) => {
var platform = testPlatform('sound_williams-z80', 'swave.c.rom', 72, (platform, frameno) => {
if (frameno == 60) {
keycallback(Keys.VK_2.c, Keys.VK_2.c, 1);
}

Binary file not shown.