bring in the logic probe; fixed z80 debugger; moved apple2 to BasicScanlinePlatform

This commit is contained in:
Steven Hugg 2019-08-24 11:28:04 -04:00
parent d4873545b2
commit 37c7ba8eb2
8 changed files with 398 additions and 69 deletions

View File

@ -121,6 +121,9 @@ export interface Platform {
getCPUState?() : CpuState;
debugSymbols? : DebugSymbols;
startProbing?() : ProbeRecorder;
stopProbing?() : void;
}
export interface Preset {
@ -1070,6 +1073,7 @@ export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
var sym = addr2symbol[addr];
return extra ? (sym + " + $" + hex(start-addr)) : sym;
}
if (!extra) break;
addr--;
}
return "";
@ -1198,8 +1202,9 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
/// new style
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsKeyInput, SavesState, SavesInputState, HasCPU } from "./devices";
import { CPUClockHook, RasterFrameBased } from "./devices";
import { Probeable, RasterFrameBased } from "./devices";
import { SampledAudio } from "./audio";
import { ProbeRecorder } from "./recorder";
interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState>, SavesInputState<any> {
}
@ -1216,6 +1221,9 @@ function hasKeyInput(arg:any): arg is AcceptsKeyInput {
function isRaster(arg:any): arg is RasterFrameBased {
return typeof arg.getRasterY === 'function';
}
function hasProbe(arg:any): arg is Probeable {
return typeof arg.connectProbe == 'function';
}
export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPlatform implements Platform {
machine : T;
@ -1224,6 +1232,9 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
video : RasterVideo;
audio : SampledAudio;
poller : ControllerPoller;
probeRecorder : ProbeRecorder;
startProbing;
stopProbing;
abstract newMachine() : T;
abstract getToolForFilename(s:string) : string;
@ -1246,7 +1257,7 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
saveControlsState() { return this.machine.saveControlsState(); }
start() {
var m = this.machine;
const m = this.machine;
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
if (hasVideo(m)) {
var vp = m.getVideoParams();
@ -1264,6 +1275,16 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
this.video.setKeyboardEvents(m.setKeyInput.bind(m));
this.poller = new ControllerPoller(m.setKeyInput.bind(m));
}
if (hasProbe(m)) {
this.probeRecorder = new ProbeRecorder(m);
this.startProbing = () => {
m.connectProbe(this.probeRecorder);
return this.probeRecorder;
};
this.stopProbing = () => {
m.connectProbe(null);
};
}
}
loadROM(title, data) {

View File

@ -124,12 +124,12 @@ export class BusHook implements Hook<Bus> {
var oldread = bus.read.bind(bus);
var oldwrite = bus.write.bind(bus);
bus.read = (a:number):number => {
profiler.logRead(a);
var val = oldread(a);
profiler.logRead(a,val);
return val;
}
bus.write = (a:number,v:number) => {
profiler.logWrite(a);
profiler.logWrite(a,v);
oldwrite(a,v);
}
this.unhook = () => {
@ -174,25 +174,34 @@ export class CPUInsnHook implements Hook<CPU&InstructionBased> {
/// PROFILER
export interface ProbeTime {
logClocks(clocks:number);
logNewScanline();
logNewFrame();
}
export interface ProbeCPU {
logExecute(address:number);
logInterrupt(type:number);
}
export interface ProbeBus {
logRead(address:number);
logWrite(address:number);
logRead(address:number, value:number);
logWrite(address:number, value:number);
}
export interface ProbeIO {
logIORead(address:number);
logIOWrite(address:number);
logIORead(address:number, value:number);
logIOWrite(address:number, value:number);
}
export interface ProbeAll extends ProbeCPU, ProbeBus, ProbeIO {
export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO {
}
export class NullProbe implements ProbeAll {
logClocks() {}
logNewScanline() {}
logNewFrame() {}
logExecute() {}
logInterrupt() {}
logRead() {}
@ -204,15 +213,15 @@ export class NullProbe implements ProbeAll {
/// CONVENIENCE
export interface BasicMachineControlsState {
in: Uint8Array;
inputs: Uint8Array;
}
export interface BasicMachineState extends BasicMachineControlsState {
c: any; // TODO
b: Uint8Array;
ram: Uint8Array;
}
export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, AcceptsROM,
export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, AcceptsROM, Probeable,
SavesState<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
abstract cpuFrequency : number;
@ -234,6 +243,9 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
scanline : number;
frameCycles : number;
nullProbe = new NullProbe();
probe : ProbeAll = this.nullProbe;
abstract read(a:number) : number;
abstract write(a:number, v:number) : void;
abstract startScanline() : void;
@ -251,6 +263,9 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
connectVideo(pixels:Uint32Array) : void {
this.pixels = pixels;
}
connectProbe(probe: ProbeAll) : void {
this.probe = probe || this.nullProbe;
}
reset() {
this.cpu.reset();
}
@ -260,33 +275,63 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
}
loadState(state) {
this.cpu.loadState(state.c);
this.ram.set(state.b);
this.inputs.set(state.in);
this.ram.set(state.ram);
this.inputs.set(state.inputs);
}
saveState() {
return {
c:this.cpu.saveState(),
b:this.ram.slice(0),
in:this.inputs.slice(0),
ram:this.ram.slice(0),
inputs:this.inputs.slice(0),
};
}
loadControlsState(state) {
this.inputs.set(state.in);
this.inputs.set(state.inputs);
}
saveControlsState() {
return {
in:this.inputs.slice(0)
inputs:this.inputs.slice(0)
};
}
advance(cycles : number) : number {
for (var i=0; i<cycles; i+=this.advanceCPU())
advanceMultiple(cycles : number) : number {
for (var i=0; i<cycles; i+=this.advance())
;
return i;
}
advanceCPU() {
advance() {
var c = this.cpu as any;
if (c.advanceClock) return c.advanceClock();
else if (c.advanceInsn) return c.advanceInsn(1);
var n = 1;
this.probe.logExecute(this.cpu.getPC());
if (c.advanceClock) c.advanceClock();
else if (c.advanceInsn) n = c.advanceInsn(1);
this.probe.logClocks(n);
return n;
}
connectCPUMemoryBus(membus:Bus) : void {
this.cpu.connectMemoryBus({
read: (a) => {
let val = membus.read(a);
this.probe.logRead(a,val);
return val;
},
write: (a,v) => {
this.probe.logWrite(a,v);
membus.write(a,v);
}
});
}
connectCPUIOBus(iobus:Bus) : void {
this.cpu['connectIOBus']({
read: (a) => {
let val = iobus.read(a);
this.probe.logIORead(a,val);
return val;
},
write: (a,v) => {
this.probe.logIOWrite(a,v);
iobus.write(a,v);
}
});
}
}
@ -298,6 +343,7 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
advanceFrame(maxClocks:number, trap) : number {
var clock = 0;
var endLineClock = 0;
this.probe.logNewFrame();
for (var sl=0; sl<this.numTotalScanlines; sl++) {
endLineClock += this.cpuCyclesPerLine;
this.scanline = sl;
@ -308,9 +354,10 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
sl = 999;
break;
}
clock += this.advance(endLineClock - clock);
clock += this.advance();
}
this.drawScanline();
this.probe.logNewScanline();
}
return clock;
}

View File

@ -1,6 +1,6 @@
import { MOS6502, MOS6502State } from "../cpu/MOS6502";
import { Bus, RasterFrameBased, SavesState, SavesInputState, AcceptsROM, AcceptsKeyInput, noise, Resettable, SampledAudioSource, SampledAudioSink, HasCPU } from "../devices";
import { BasicScanlineMachine, noise } from "../devices";
import { KeyFlags } from "../emu"; // TODO
import { lzgmini } from "../util";
@ -21,6 +21,7 @@ interface AppleIIStateBase {
}
interface AppleIIControlsState {
inputs : Uint8Array; // unused?
kbdlatch : number;
}
@ -29,18 +30,23 @@ interface AppleIIState extends AppleIIStateBase, AppleIIControlsState {
grswitch : number;
}
export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSource, AcceptsROM, AcceptsKeyInput,
AppleIIStateBase, SavesState<AppleIIState>, SavesInputState<AppleIIControlsState> {
export class AppleII extends BasicScanlineMachine {
cpuFrequency = 1023000;
sampleRate = this.cpuFrequency;
cpuCyclesPerLine = 65; // approx: http://www.cs.columbia.edu/~sedwards/apple2fpga/
cpuCyclesPerFrame = 65*262;
canvasWidth = 280;
numVisibleScanlines = 192;
numTotalScanlines = 262;
defaultROMSize = 0xbf00-0x803; // TODO
ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM
rom : Uint8Array;
bios : Uint8Array;
cpu = new MOS6502();
audio : SampledAudioSink;
pixels : Uint32Array;
grdirty = new Array(0xc000 >> 7);
grparams = {dirty:this.grdirty, grswitch:GR_TXMODE, mem:this.ram};
ap2disp;
pgmbin : Uint8Array;
rnd = 1;
kbdlatch = 0;
soundstate = 0;
@ -52,14 +58,14 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
// bank 1 is E000-FFFF, bank 2 is D000-DFFF
bank2rdoffset=0;
bank2wroffset=0;
lastFrameCycles=0;
constructor() {
this.rom = new lzgmini().decode(APPLEIIGO_LZG);
this.ram.set(this.rom, 0xd000);
super();
this.bios = new lzgmini().decode(APPLEIIGO_LZG);
this.ram.set(this.bios, 0xd000);
this.ram[0xbf00] = 0x4c; // fake DOS detect for C
this.ram[0xbf6f] = 0x01; // fake DOS detect for C
this.cpu.connectMemoryBus(this);
this.connectCPUMemoryBus(this);
}
saveState() : AppleIIState {
// TODO: automagic
@ -73,6 +79,7 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
auxRAMselected: this.auxRAMselected,
auxRAMbank: this.auxRAMbank,
writeinhibit: this.writeinhibit,
inputs: null
};
}
loadState(s:AppleIIState) {
@ -89,13 +96,13 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
this.ap2disp.invalidate(); // repaint entire screen
}
saveControlsState() : AppleIIControlsState {
return {kbdlatch:this.kbdlatch};
return {inputs:null,kbdlatch:this.kbdlatch};
}
loadControlsState(s:AppleIIControlsState) {
this.kbdlatch = s.kbdlatch;
}
reset() {
this.cpu.reset();
super.reset();
this.rnd = 1;
// execute until $c600 boot
for (var i=0; i<2000000; i++) {
@ -113,7 +120,7 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
return this.ram[address];
} else if (address >= 0xd000) {
if (!this.auxRAMselected)
return this.rom[address - 0xd000];
return this.bios[address - 0xd000];
else if (address >= 0xe000)
return this.ram[address];
else
@ -175,8 +182,8 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
// JMP VM_BASE
case 0xc600: {
// load program into RAM
if (this.pgmbin)
this.ram.set(this.pgmbin.slice(HDR_SIZE), PGM_BASE);
if (this.rom)
this.ram.set(this.rom.slice(HDR_SIZE), PGM_BASE);
return 0x4c;
}
case 0xc601: return VM_BASE&0xff;
@ -201,36 +208,26 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
this.ram[address + this.bank2wroffset] = val;
}
}
loadROM(data:Uint8Array) {
this.pgmbin = data.slice();
}
getVideoParams() {
return {width:280, height:192};
}
getAudioParams() {
return {sampleRate:cpuFrequency, stereo:false};
}
connectVideo(pixels:Uint32Array) {
this.pixels = pixels;
this.ap2disp = pixels && new Apple2Display(this.pixels, this.grparams);
super.connectVideo(pixels);
this.ap2disp = this.pixels && new Apple2Display(this.pixels, this.grparams);
}
connectAudio(audio:SampledAudioSink) {
this.audio = audio;
startScanline() {
}
advanceFrame(maxCycles, trap) : number {
maxCycles = Math.min(maxCycles, cpuCyclesPerFrame);
for (var i=0; i<maxCycles; i++) {
if (trap && (this.lastFrameCycles=i)>=0 && trap()) break;
this.cpu.advanceClock();
this.audio.feedSample(this.soundstate, 1);
}
drawScanline() {
// TODO: draw scanline via ap2disp
}
advanceFrame(maxClocks:number, trap) : number {
var clocks = super.advanceFrame(maxClocks, trap);
this.ap2disp && this.ap2disp.updateScreen();
return (this.lastFrameCycles = i);
return clocks;
}
getRasterX() { return this.lastFrameCycles % cpuCyclesPerLine; }
getRasterY() { return Math.floor(this.lastFrameCycles / cpuCyclesPerLine); }
advance() {
this.audio.feedSample(this.soundstate, 1);
return super.advance();
}
setKeyInput(key:number, code:number, flags:number) : void {
if (flags & KeyFlags.KeyPress) {
// convert to uppercase for Apple ][

View File

@ -50,8 +50,8 @@ export class VicDual extends BasicScanlineMachine {
constructor() {
super();
this.cpu.connectMemoryBus(this);
this.cpu.connectIOBus(this.newIOBus());
this.connectCPUMemoryBus(this);
this.connectCPUIOBus(this.newIOBus());
this.inputs.set([0xff, 0xff, 0xff, 0xff ^ 0x8]); // most things active low
this.display = new VicDualDisplay();
this.handler = newKeyboardHandler(this.inputs, CARNIVAL_KEYCODE_MAP, this.getKeyboardFunction());

View File

@ -177,3 +177,86 @@ export class EmuProfilerImpl implements EmuProfiler {
this.log(a | PROFOP_INTERRUPT);
}
}
/////
import { Probeable, ProbeAll } from "./devices";
export enum ProbeFlags {
CLOCKS = 0x00000000,
EXECUTE = 0x01000000,
MEM_READ = 0x02000000,
MEM_WRITE = 0x04000000,
IO_READ = 0x08000000,
IO_WRITE = 0x10000000,
INTERRUPT = 0x20000000,
SCANLINE = 0x7e000000,
FRAME = 0x7f000000,
}
class ProbeFrame {
data : Uint32Array;
len : number;
}
export class ProbeRecorder implements ProbeAll {
buf = new Uint32Array(0x100000);
idx = 0;
fclk = 0;
sl = 0;
m : Probeable;
constructor(m:Probeable) {
this.m = m;
}
start() {
this.m.connectProbe(this);
this.reset();
}
stop() {
this.m.connectProbe(null);
}
reset() {
this.idx = 0;
}
log(a:number) {
// TODO: coalesce READ and EXECUTE
if (this.idx >= this.buf.length) return;
this.buf[this.idx++] = a;
}
logClocks(clocks:number) {
if (clocks) {
this.fclk += clocks;
this.log(clocks | ProbeFlags.CLOCKS);
}
}
logNewScanline() {
this.log(ProbeFlags.SCANLINE);
this.sl++;
}
logNewFrame() {
this.log(ProbeFlags.FRAME);
this.sl = 0;
}
logExecute(address:number) {
this.log(address | ProbeFlags.EXECUTE);
}
logInterrupt(type:number) {
this.log(type | ProbeFlags.INTERRUPT);
}
logRead(address:number, value:number) {
this.log(address | ProbeFlags.MEM_READ);
}
logWrite(address:number, value:number) {
this.log(address | ProbeFlags.MEM_WRITE);
}
logIORead(address:number, value:number) {
this.log(address | ProbeFlags.IO_READ);
}
logIOWrite(address:number, value:number) {
this.log(address | ProbeFlags.IO_WRITE);
}
}

View File

@ -254,6 +254,14 @@ function refreshWindowList() {
return new Views.ProfileView();
});
}
if (platform.startProbing) {
addWindowItem("#eventprobe", "Event Probe", () => {
return new Views.EventProbeView();
});
addWindowItem("#heatmap", "Heat Map", () => {
return new Views.HeatMapView();
});
}
addWindowItem('#asseteditor', 'Asset Editor', () => {
return new Views.AssetEditorView();
});
@ -1251,7 +1259,7 @@ function updateDebugWindows() {
projectWindows.tick();
debugTickPaused = true;
}
setTimeout(updateDebugWindows, 200);
setTimeout(updateDebugWindows, 100);
}
function setWaitDialog(b : boolean) {

View File

@ -7,7 +7,7 @@ import { Platform, EmuState, ProfilerOutput, lookupSymbol, BaseDebugPlatform } f
import { hex, lpad, rpad, safeident, rgb2bgr } from "./util";
import { CodeAnalyzer } from "./analysis";
import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui";
import { EmuProfilerImpl } from "./recorder";
import { EmuProfilerImpl, ProbeRecorder, ProbeFlags } from "./recorder";
import * as pixed from "./pixed/pixeleditor";
declare var Mousetrap;
@ -922,6 +922,178 @@ export class ProfileView implements ProjectView {
///
// TODO: clear buffer when scrubbing
abstract class ProbeViewBase {
probe : ProbeRecorder;
maindiv : HTMLElement;
canvas : HTMLCanvasElement;
ctx : CanvasRenderingContext2D;
recreateOnResize = true;
createCanvas(parent:HTMLElement, width:number, height:number) {
var div = document.createElement('div');
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.width = '100%';
canvas.style.height = '100%';
parent.appendChild(div);
div.appendChild(canvas);
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.initCanvas();
return this.maindiv = div;
}
initCanvas() {
}
setVisible(showing : boolean) : void {
if (showing) {
this.probe = platform.startProbing();
} else {
platform.stopProbing();
this.probe = null;
}
}
clear() {
var ctx = this.ctx;
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 0.5;
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'lighter';
}
tick() {
var p = this.probe;
if (!p || !p.idx) return; // if no probe, or if empty
var row=0;
var col=0;
var ctx = this.ctx;
this.clear();
for (var i=0; i<p.idx; i++) {
var word = p.buf[i];
var addr = word & 0xffffff;
var op = word & 0xff000000;
switch (op) {
case ProbeFlags.SCANLINE: row++; col=0; break;
case ProbeFlags.FRAME: row=0; col=0; break;
case ProbeFlags.CLOCKS: col += addr; break;
default:
this.drawEvent(op, addr, col, row);
break;
}
}
p.reset();
}
setContextForOp(op) {
var ctx = this.ctx;
switch (op) {
//case ProbeFlags.EXECUTE: ctx.fillStyle = "green"; break;
case ProbeFlags.MEM_READ: ctx.fillStyle = "white"; break;
case ProbeFlags.MEM_WRITE: ctx.fillStyle = "red"; break;
case ProbeFlags.IO_READ: ctx.fillStyle = "green"; break;
case ProbeFlags.IO_WRITE: ctx.fillStyle = "magenta"; break;
case ProbeFlags.INTERRUPT: ctx.fillStyle = "yellow"; break;
default: ctx.fillStyle = "blue"; break;
}
}
abstract drawEvent(op, addr, col, row);
}
abstract class ProbeBitmapViewBase extends ProbeViewBase {
imageData : ImageData;
datau32 : Uint32Array;
recreateOnResize = false;
initCanvas() {
this.imageData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
this.datau32 = new Uint32Array(this.imageData.data.buffer);
}
refresh() {
this.tick();
this.datau32.fill(0xff000000);
}
tick() {
super.tick();
this.ctx.putImageData(this.imageData, 0, 0);
}
clear() {
this.datau32.fill(0xff000000);
}
}
export class HeatMapView extends ProbeBitmapViewBase implements ProjectView {
createDiv(parent : HTMLElement) {
return this.createCanvas(parent, 256, 256);
}
drawEvent(op, addr, col, row) {
var x = addr & 0xff;
var y = (addr >> 8) & 0xff;
var col;
switch (op) {
case ProbeFlags.EXECUTE: col = 0x0f3f0f; break;
case ProbeFlags.MEM_READ: col = 0x3f0101; break;
case ProbeFlags.MEM_WRITE: col = 0x000f3f; break;
case ProbeFlags.IO_READ: col = 0x001f01; break;
case ProbeFlags.IO_WRITE: col = 0x003f3f; break;
case ProbeFlags.INTERRUPT: col = 0x3f3f00; break;
default: col = 0x1f1f1f; break;
}
var data = this.datau32[addr & 0xffff];
data = (data & 0x7f7f7f) << 1;
data = data | col | 0xff000000;
this.datau32[addr & 0xffff] = data;
}
}
export class EventProbeView extends ProbeViewBase implements ProjectView {
symcache : Map<number,symbol> = new Map();
createDiv(parent : HTMLElement) {
return this.createCanvas( parent, $(parent).width(), $(parent).height() );
}
drawEvent(op, addr, col, row) {
var ctx = this.ctx;
var xscale = this.canvas.width / 128; // TODO: pixels
var yscale = this.canvas.height / 262; // TODO: lines
var x = col * xscale;
var y = row * yscale;
var sym = this.getSymbol(addr);
if (!sym && op == ProbeFlags.IO_WRITE) sym = hex(addr,4);
//if (!sym && op == ProbeFlags.IO_READ) sym = hex(addr,4);
if (sym) {
this.setContextForOp(op);
ctx.fillText(sym, x, y);
}
}
getSymbol(addr:number) : string {
var sym = this.symcache[addr];
if (!sym) {
sym = lookupSymbol(platform, addr, false);
this.symcache[addr] = sym;
}
return sym;
}
refresh() {
this.tick();
this.symcache.clear();
}
}
///
export class AssetEditorView implements ProjectView, pixed.EditorContext {
maindiv : JQuery;
cureditordiv : JQuery;

View File

@ -125,6 +125,7 @@ function testPlatform(platid, romname, maxframes, callback) {
platform.reset(); // reset again
var state0b = platform.saveState();
//TODO: vcs fails assert.deepEqual(state0a, state0b);
//if (platform.startProbing) platform.startProbing();
platform.resume(); // so that recorder works
platform.setRecorder(rec);
for (var i=0; i<maxframes; i++) {