mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-29 14:51:17 +00:00
bring in the logic probe; fixed z80 debugger; moved apple2 to BasicScanlinePlatform
This commit is contained in:
parent
d4873545b2
commit
37c7ba8eb2
@ -121,6 +121,9 @@ export interface Platform {
|
|||||||
getCPUState?() : CpuState;
|
getCPUState?() : CpuState;
|
||||||
|
|
||||||
debugSymbols? : DebugSymbols;
|
debugSymbols? : DebugSymbols;
|
||||||
|
|
||||||
|
startProbing?() : ProbeRecorder;
|
||||||
|
stopProbing?() : void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Preset {
|
export interface Preset {
|
||||||
@ -1070,6 +1073,7 @@ export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
|
|||||||
var sym = addr2symbol[addr];
|
var sym = addr2symbol[addr];
|
||||||
return extra ? (sym + " + $" + hex(start-addr)) : sym;
|
return extra ? (sym + " + $" + hex(start-addr)) : sym;
|
||||||
}
|
}
|
||||||
|
if (!extra) break;
|
||||||
addr--;
|
addr--;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
@ -1198,8 +1202,9 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
|||||||
/// new style
|
/// new style
|
||||||
|
|
||||||
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsKeyInput, SavesState, SavesInputState, HasCPU } from "./devices";
|
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 { SampledAudio } from "./audio";
|
||||||
|
import { ProbeRecorder } from "./recorder";
|
||||||
|
|
||||||
interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState>, SavesInputState<any> {
|
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 {
|
function isRaster(arg:any): arg is RasterFrameBased {
|
||||||
return typeof arg.getRasterY === 'function';
|
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 {
|
export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPlatform implements Platform {
|
||||||
machine : T;
|
machine : T;
|
||||||
@ -1224,6 +1232,9 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
|
|||||||
video : RasterVideo;
|
video : RasterVideo;
|
||||||
audio : SampledAudio;
|
audio : SampledAudio;
|
||||||
poller : ControllerPoller;
|
poller : ControllerPoller;
|
||||||
|
probeRecorder : ProbeRecorder;
|
||||||
|
startProbing;
|
||||||
|
stopProbing;
|
||||||
|
|
||||||
abstract newMachine() : T;
|
abstract newMachine() : T;
|
||||||
abstract getToolForFilename(s:string) : string;
|
abstract getToolForFilename(s:string) : string;
|
||||||
@ -1246,7 +1257,7 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
|
|||||||
saveControlsState() { return this.machine.saveControlsState(); }
|
saveControlsState() { return this.machine.saveControlsState(); }
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
var m = this.machine;
|
const m = this.machine;
|
||||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||||
if (hasVideo(m)) {
|
if (hasVideo(m)) {
|
||||||
var vp = m.getVideoParams();
|
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.video.setKeyboardEvents(m.setKeyInput.bind(m));
|
||||||
this.poller = new ControllerPoller(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) {
|
loadROM(title, data) {
|
||||||
|
@ -124,12 +124,12 @@ export class BusHook implements Hook<Bus> {
|
|||||||
var oldread = bus.read.bind(bus);
|
var oldread = bus.read.bind(bus);
|
||||||
var oldwrite = bus.write.bind(bus);
|
var oldwrite = bus.write.bind(bus);
|
||||||
bus.read = (a:number):number => {
|
bus.read = (a:number):number => {
|
||||||
profiler.logRead(a);
|
|
||||||
var val = oldread(a);
|
var val = oldread(a);
|
||||||
|
profiler.logRead(a,val);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
bus.write = (a:number,v:number) => {
|
bus.write = (a:number,v:number) => {
|
||||||
profiler.logWrite(a);
|
profiler.logWrite(a,v);
|
||||||
oldwrite(a,v);
|
oldwrite(a,v);
|
||||||
}
|
}
|
||||||
this.unhook = () => {
|
this.unhook = () => {
|
||||||
@ -174,25 +174,34 @@ export class CPUInsnHook implements Hook<CPU&InstructionBased> {
|
|||||||
|
|
||||||
/// PROFILER
|
/// PROFILER
|
||||||
|
|
||||||
|
export interface ProbeTime {
|
||||||
|
logClocks(clocks:number);
|
||||||
|
logNewScanline();
|
||||||
|
logNewFrame();
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProbeCPU {
|
export interface ProbeCPU {
|
||||||
logExecute(address:number);
|
logExecute(address:number);
|
||||||
logInterrupt(type:number);
|
logInterrupt(type:number);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProbeBus {
|
export interface ProbeBus {
|
||||||
logRead(address:number);
|
logRead(address:number, value:number);
|
||||||
logWrite(address:number);
|
logWrite(address:number, value:number);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProbeIO {
|
export interface ProbeIO {
|
||||||
logIORead(address:number);
|
logIORead(address:number, value:number);
|
||||||
logIOWrite(address: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 {
|
export class NullProbe implements ProbeAll {
|
||||||
|
logClocks() {}
|
||||||
|
logNewScanline() {}
|
||||||
|
logNewFrame() {}
|
||||||
logExecute() {}
|
logExecute() {}
|
||||||
logInterrupt() {}
|
logInterrupt() {}
|
||||||
logRead() {}
|
logRead() {}
|
||||||
@ -204,15 +213,15 @@ export class NullProbe implements ProbeAll {
|
|||||||
/// CONVENIENCE
|
/// CONVENIENCE
|
||||||
|
|
||||||
export interface BasicMachineControlsState {
|
export interface BasicMachineControlsState {
|
||||||
in: Uint8Array;
|
inputs: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BasicMachineState extends BasicMachineControlsState {
|
export interface BasicMachineState extends BasicMachineControlsState {
|
||||||
c: any; // TODO
|
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> {
|
SavesState<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
|
||||||
|
|
||||||
abstract cpuFrequency : number;
|
abstract cpuFrequency : number;
|
||||||
@ -234,6 +243,9 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
|
|||||||
scanline : number;
|
scanline : number;
|
||||||
frameCycles : number;
|
frameCycles : number;
|
||||||
|
|
||||||
|
nullProbe = new NullProbe();
|
||||||
|
probe : ProbeAll = this.nullProbe;
|
||||||
|
|
||||||
abstract read(a:number) : number;
|
abstract read(a:number) : number;
|
||||||
abstract write(a:number, v:number) : void;
|
abstract write(a:number, v:number) : void;
|
||||||
abstract startScanline() : void;
|
abstract startScanline() : void;
|
||||||
@ -251,6 +263,9 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
|
|||||||
connectVideo(pixels:Uint32Array) : void {
|
connectVideo(pixels:Uint32Array) : void {
|
||||||
this.pixels = pixels;
|
this.pixels = pixels;
|
||||||
}
|
}
|
||||||
|
connectProbe(probe: ProbeAll) : void {
|
||||||
|
this.probe = probe || this.nullProbe;
|
||||||
|
}
|
||||||
reset() {
|
reset() {
|
||||||
this.cpu.reset();
|
this.cpu.reset();
|
||||||
}
|
}
|
||||||
@ -260,33 +275,63 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
|
|||||||
}
|
}
|
||||||
loadState(state) {
|
loadState(state) {
|
||||||
this.cpu.loadState(state.c);
|
this.cpu.loadState(state.c);
|
||||||
this.ram.set(state.b);
|
this.ram.set(state.ram);
|
||||||
this.inputs.set(state.in);
|
this.inputs.set(state.inputs);
|
||||||
}
|
}
|
||||||
saveState() {
|
saveState() {
|
||||||
return {
|
return {
|
||||||
c:this.cpu.saveState(),
|
c:this.cpu.saveState(),
|
||||||
b:this.ram.slice(0),
|
ram:this.ram.slice(0),
|
||||||
in:this.inputs.slice(0),
|
inputs:this.inputs.slice(0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
loadControlsState(state) {
|
loadControlsState(state) {
|
||||||
this.inputs.set(state.in);
|
this.inputs.set(state.inputs);
|
||||||
}
|
}
|
||||||
saveControlsState() {
|
saveControlsState() {
|
||||||
return {
|
return {
|
||||||
in:this.inputs.slice(0)
|
inputs:this.inputs.slice(0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
advance(cycles : number) : number {
|
advanceMultiple(cycles : number) : number {
|
||||||
for (var i=0; i<cycles; i+=this.advanceCPU())
|
for (var i=0; i<cycles; i+=this.advance())
|
||||||
;
|
;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
advanceCPU() {
|
advance() {
|
||||||
var c = this.cpu as any;
|
var c = this.cpu as any;
|
||||||
if (c.advanceClock) return c.advanceClock();
|
var n = 1;
|
||||||
else if (c.advanceInsn) return c.advanceInsn(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 {
|
advanceFrame(maxClocks:number, trap) : number {
|
||||||
var clock = 0;
|
var clock = 0;
|
||||||
var endLineClock = 0;
|
var endLineClock = 0;
|
||||||
|
this.probe.logNewFrame();
|
||||||
for (var sl=0; sl<this.numTotalScanlines; sl++) {
|
for (var sl=0; sl<this.numTotalScanlines; sl++) {
|
||||||
endLineClock += this.cpuCyclesPerLine;
|
endLineClock += this.cpuCyclesPerLine;
|
||||||
this.scanline = sl;
|
this.scanline = sl;
|
||||||
@ -308,9 +354,10 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
|
|||||||
sl = 999;
|
sl = 999;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
clock += this.advance(endLineClock - clock);
|
clock += this.advance();
|
||||||
}
|
}
|
||||||
this.drawScanline();
|
this.drawScanline();
|
||||||
|
this.probe.logNewScanline();
|
||||||
}
|
}
|
||||||
return clock;
|
return clock;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { MOS6502, MOS6502State } from "../cpu/MOS6502";
|
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 { KeyFlags } from "../emu"; // TODO
|
||||||
import { lzgmini } from "../util";
|
import { lzgmini } from "../util";
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ interface AppleIIStateBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AppleIIControlsState {
|
interface AppleIIControlsState {
|
||||||
|
inputs : Uint8Array; // unused?
|
||||||
kbdlatch : number;
|
kbdlatch : number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,18 +30,23 @@ interface AppleIIState extends AppleIIStateBase, AppleIIControlsState {
|
|||||||
grswitch : number;
|
grswitch : number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSource, AcceptsROM, AcceptsKeyInput,
|
export class AppleII extends BasicScanlineMachine {
|
||||||
AppleIIStateBase, SavesState<AppleIIState>, SavesInputState<AppleIIControlsState> {
|
|
||||||
|
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
|
ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM
|
||||||
rom : Uint8Array;
|
bios : Uint8Array;
|
||||||
cpu = new MOS6502();
|
cpu = new MOS6502();
|
||||||
audio : SampledAudioSink;
|
|
||||||
pixels : Uint32Array;
|
|
||||||
grdirty = new Array(0xc000 >> 7);
|
grdirty = new Array(0xc000 >> 7);
|
||||||
grparams = {dirty:this.grdirty, grswitch:GR_TXMODE, mem:this.ram};
|
grparams = {dirty:this.grdirty, grswitch:GR_TXMODE, mem:this.ram};
|
||||||
ap2disp;
|
ap2disp;
|
||||||
pgmbin : Uint8Array;
|
|
||||||
rnd = 1;
|
rnd = 1;
|
||||||
kbdlatch = 0;
|
kbdlatch = 0;
|
||||||
soundstate = 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
|
// bank 1 is E000-FFFF, bank 2 is D000-DFFF
|
||||||
bank2rdoffset=0;
|
bank2rdoffset=0;
|
||||||
bank2wroffset=0;
|
bank2wroffset=0;
|
||||||
lastFrameCycles=0;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.rom = new lzgmini().decode(APPLEIIGO_LZG);
|
super();
|
||||||
this.ram.set(this.rom, 0xd000);
|
this.bios = new lzgmini().decode(APPLEIIGO_LZG);
|
||||||
|
this.ram.set(this.bios, 0xd000);
|
||||||
this.ram[0xbf00] = 0x4c; // fake DOS detect for C
|
this.ram[0xbf00] = 0x4c; // fake DOS detect for C
|
||||||
this.ram[0xbf6f] = 0x01; // fake DOS detect for C
|
this.ram[0xbf6f] = 0x01; // fake DOS detect for C
|
||||||
this.cpu.connectMemoryBus(this);
|
this.connectCPUMemoryBus(this);
|
||||||
}
|
}
|
||||||
saveState() : AppleIIState {
|
saveState() : AppleIIState {
|
||||||
// TODO: automagic
|
// TODO: automagic
|
||||||
@ -73,6 +79,7 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
|||||||
auxRAMselected: this.auxRAMselected,
|
auxRAMselected: this.auxRAMselected,
|
||||||
auxRAMbank: this.auxRAMbank,
|
auxRAMbank: this.auxRAMbank,
|
||||||
writeinhibit: this.writeinhibit,
|
writeinhibit: this.writeinhibit,
|
||||||
|
inputs: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
loadState(s:AppleIIState) {
|
loadState(s:AppleIIState) {
|
||||||
@ -89,13 +96,13 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
|||||||
this.ap2disp.invalidate(); // repaint entire screen
|
this.ap2disp.invalidate(); // repaint entire screen
|
||||||
}
|
}
|
||||||
saveControlsState() : AppleIIControlsState {
|
saveControlsState() : AppleIIControlsState {
|
||||||
return {kbdlatch:this.kbdlatch};
|
return {inputs:null,kbdlatch:this.kbdlatch};
|
||||||
}
|
}
|
||||||
loadControlsState(s:AppleIIControlsState) {
|
loadControlsState(s:AppleIIControlsState) {
|
||||||
this.kbdlatch = s.kbdlatch;
|
this.kbdlatch = s.kbdlatch;
|
||||||
}
|
}
|
||||||
reset() {
|
reset() {
|
||||||
this.cpu.reset();
|
super.reset();
|
||||||
this.rnd = 1;
|
this.rnd = 1;
|
||||||
// execute until $c600 boot
|
// execute until $c600 boot
|
||||||
for (var i=0; i<2000000; i++) {
|
for (var i=0; i<2000000; i++) {
|
||||||
@ -113,7 +120,7 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
|||||||
return this.ram[address];
|
return this.ram[address];
|
||||||
} else if (address >= 0xd000) {
|
} else if (address >= 0xd000) {
|
||||||
if (!this.auxRAMselected)
|
if (!this.auxRAMselected)
|
||||||
return this.rom[address - 0xd000];
|
return this.bios[address - 0xd000];
|
||||||
else if (address >= 0xe000)
|
else if (address >= 0xe000)
|
||||||
return this.ram[address];
|
return this.ram[address];
|
||||||
else
|
else
|
||||||
@ -175,8 +182,8 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
|||||||
// JMP VM_BASE
|
// JMP VM_BASE
|
||||||
case 0xc600: {
|
case 0xc600: {
|
||||||
// load program into RAM
|
// load program into RAM
|
||||||
if (this.pgmbin)
|
if (this.rom)
|
||||||
this.ram.set(this.pgmbin.slice(HDR_SIZE), PGM_BASE);
|
this.ram.set(this.rom.slice(HDR_SIZE), PGM_BASE);
|
||||||
return 0x4c;
|
return 0x4c;
|
||||||
}
|
}
|
||||||
case 0xc601: return VM_BASE&0xff;
|
case 0xc601: return VM_BASE&0xff;
|
||||||
@ -201,35 +208,25 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
|||||||
this.ram[address + this.bank2wroffset] = val;
|
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);
|
|
||||||
}
|
|
||||||
connectAudio(audio:SampledAudioSink) {
|
|
||||||
this.audio = audio;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
this.ap2disp && this.ap2disp.updateScreen();
|
|
||||||
return (this.lastFrameCycles = i);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRasterX() { return this.lastFrameCycles % cpuCyclesPerLine; }
|
connectVideo(pixels:Uint32Array) {
|
||||||
getRasterY() { return Math.floor(this.lastFrameCycles / cpuCyclesPerLine); }
|
super.connectVideo(pixels);
|
||||||
|
this.ap2disp = this.pixels && new Apple2Display(this.pixels, this.grparams);
|
||||||
|
}
|
||||||
|
startScanline() {
|
||||||
|
}
|
||||||
|
drawScanline() {
|
||||||
|
// TODO: draw scanline via ap2disp
|
||||||
|
}
|
||||||
|
advanceFrame(maxClocks:number, trap) : number {
|
||||||
|
var clocks = super.advanceFrame(maxClocks, trap);
|
||||||
|
this.ap2disp && this.ap2disp.updateScreen();
|
||||||
|
return clocks;
|
||||||
|
}
|
||||||
|
advance() {
|
||||||
|
this.audio.feedSample(this.soundstate, 1);
|
||||||
|
return super.advance();
|
||||||
|
}
|
||||||
|
|
||||||
setKeyInput(key:number, code:number, flags:number) : void {
|
setKeyInput(key:number, code:number, flags:number) : void {
|
||||||
if (flags & KeyFlags.KeyPress) {
|
if (flags & KeyFlags.KeyPress) {
|
||||||
|
@ -50,8 +50,8 @@ export class VicDual extends BasicScanlineMachine {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.cpu.connectMemoryBus(this);
|
this.connectCPUMemoryBus(this);
|
||||||
this.cpu.connectIOBus(this.newIOBus());
|
this.connectCPUIOBus(this.newIOBus());
|
||||||
this.inputs.set([0xff, 0xff, 0xff, 0xff ^ 0x8]); // most things active low
|
this.inputs.set([0xff, 0xff, 0xff, 0xff ^ 0x8]); // most things active low
|
||||||
this.display = new VicDualDisplay();
|
this.display = new VicDualDisplay();
|
||||||
this.handler = newKeyboardHandler(this.inputs, CARNIVAL_KEYCODE_MAP, this.getKeyboardFunction());
|
this.handler = newKeyboardHandler(this.inputs, CARNIVAL_KEYCODE_MAP, this.getKeyboardFunction());
|
||||||
|
@ -177,3 +177,86 @@ export class EmuProfilerImpl implements EmuProfiler {
|
|||||||
this.log(a | PROFOP_INTERRUPT);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
10
src/ui.ts
10
src/ui.ts
@ -254,6 +254,14 @@ function refreshWindowList() {
|
|||||||
return new Views.ProfileView();
|
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', () => {
|
addWindowItem('#asseteditor', 'Asset Editor', () => {
|
||||||
return new Views.AssetEditorView();
|
return new Views.AssetEditorView();
|
||||||
});
|
});
|
||||||
@ -1251,7 +1259,7 @@ function updateDebugWindows() {
|
|||||||
projectWindows.tick();
|
projectWindows.tick();
|
||||||
debugTickPaused = true;
|
debugTickPaused = true;
|
||||||
}
|
}
|
||||||
setTimeout(updateDebugWindows, 200);
|
setTimeout(updateDebugWindows, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWaitDialog(b : boolean) {
|
function setWaitDialog(b : boolean) {
|
||||||
|
174
src/views.ts
174
src/views.ts
@ -7,7 +7,7 @@ import { Platform, EmuState, ProfilerOutput, lookupSymbol, BaseDebugPlatform } f
|
|||||||
import { hex, lpad, rpad, safeident, rgb2bgr } from "./util";
|
import { hex, lpad, rpad, safeident, rgb2bgr } from "./util";
|
||||||
import { CodeAnalyzer } from "./analysis";
|
import { CodeAnalyzer } from "./analysis";
|
||||||
import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui";
|
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";
|
import * as pixed from "./pixed/pixeleditor";
|
||||||
declare var Mousetrap;
|
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 {
|
export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||||
maindiv : JQuery;
|
maindiv : JQuery;
|
||||||
cureditordiv : JQuery;
|
cureditordiv : JQuery;
|
||||||
|
@ -125,6 +125,7 @@ function testPlatform(platid, romname, maxframes, callback) {
|
|||||||
platform.reset(); // reset again
|
platform.reset(); // reset again
|
||||||
var state0b = platform.saveState();
|
var state0b = platform.saveState();
|
||||||
//TODO: vcs fails assert.deepEqual(state0a, state0b);
|
//TODO: vcs fails assert.deepEqual(state0a, state0b);
|
||||||
|
//if (platform.startProbing) platform.startProbing();
|
||||||
platform.resume(); // so that recorder works
|
platform.resume(); // so that recorder works
|
||||||
platform.setRecorder(rec);
|
platform.setRecorder(rec);
|
||||||
for (var i=0; i<maxframes; i++) {
|
for (var i=0; i<maxframes; i++) {
|
||||||
|
Loading…
Reference in New Issue
Block a user