2018-07-08 04:58:11 +00:00
|
|
|
|
2021-08-06 00:16:13 +00:00
|
|
|
import { RasterVideo, dumpRAM, AnimationTimer, ControllerPoller } from "./emu";
|
|
|
|
import { hex, printFlags, invertMap, byteToASCII } from "./util";
|
2018-08-17 19:13:58 +00:00
|
|
|
import { CodeAnalyzer } from "./analysis";
|
2020-07-11 14:51:26 +00:00
|
|
|
import { Segment, FileData } from "./workertypes";
|
2018-08-27 21:31:49 +00:00
|
|
|
import { disassemble6502 } from "./cpu/disasm6502";
|
|
|
|
import { disassembleZ80 } from "./cpu/disasmz80";
|
2019-08-25 20:20:12 +00:00
|
|
|
import { Z80 } from "./cpu/ZilogZ80";
|
2018-08-16 23:19:20 +00:00
|
|
|
|
2021-08-06 00:16:13 +00:00
|
|
|
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsBIOS, AcceptsKeyInput, SavesState, SavesInputState, HasCPU, HasSerialIO, SerialIOInterface, AcceptsJoyInput } from "./devices";
|
|
|
|
import { Probeable, RasterFrameBased, AcceptsPaddleInput } from "./devices";
|
|
|
|
import { SampledAudio } from "./audio";
|
2022-09-01 15:19:20 +00:00
|
|
|
import { ProbeRecorder } from "./probe";
|
2021-08-06 00:16:13 +00:00
|
|
|
import { BaseWASMMachine } from "./wasmplatform";
|
2021-08-06 02:19:43 +00:00
|
|
|
import { CPU6809 } from "./cpu/6809";
|
2022-02-21 22:54:33 +00:00
|
|
|
import { _MOS6502 } from "./cpu/MOS6502";
|
2021-08-06 00:16:13 +00:00
|
|
|
|
|
|
|
///
|
|
|
|
|
2018-07-08 04:58:11 +00:00
|
|
|
export interface OpcodeMetadata {
|
|
|
|
minCycles: number;
|
2018-07-11 15:17:37 +00:00
|
|
|
maxCycles: number;
|
2018-08-16 23:19:20 +00:00
|
|
|
insnlength: number;
|
|
|
|
opcode: number;
|
2018-07-08 04:58:11 +00:00
|
|
|
}
|
|
|
|
|
2018-08-22 03:39:34 +00:00
|
|
|
export interface CpuState {
|
2018-09-14 13:10:41 +00:00
|
|
|
PC:number;
|
|
|
|
EPC?:number; // effective PC (for bankswitching)
|
|
|
|
o?:number;/*opcode*/
|
2018-08-16 23:19:20 +00:00
|
|
|
SP?:number
|
2018-07-11 15:17:37 +00:00
|
|
|
/*
|
2018-11-22 12:39:06 +00:00
|
|
|
A:number, X:number, Y:number, SP:number, R:boolean,
|
2018-07-11 15:17:37 +00:00
|
|
|
N,V,D,Z,C:boolean*/
|
|
|
|
};
|
2018-08-22 18:50:10 +00:00
|
|
|
export interface EmuState {
|
2018-08-29 12:24:13 +00:00
|
|
|
c?:CpuState, // CPU state
|
2018-09-18 18:09:51 +00:00
|
|
|
b?:Uint8Array|number[], // RAM (TODO: not for vcs, support Uint8Array)
|
2019-08-23 14:37:30 +00:00
|
|
|
ram?:Uint8Array,
|
2018-08-29 12:24:13 +00:00
|
|
|
o?:{}, // verilog
|
2018-08-22 18:50:10 +00:00
|
|
|
};
|
|
|
|
export interface EmuControlsState {
|
|
|
|
}
|
|
|
|
export type DisasmLine = {
|
|
|
|
line:string,
|
2018-08-28 12:28:53 +00:00
|
|
|
nbytes:number,
|
|
|
|
isaddr:boolean
|
2018-08-22 18:50:10 +00:00
|
|
|
};
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2018-09-17 20:09:09 +00:00
|
|
|
export type SymbolMap = {[ident:string]:number};
|
|
|
|
export type AddrSymbolMap = {[address:number]:string};
|
|
|
|
|
|
|
|
export class DebugSymbols {
|
2019-03-08 01:00:12 +00:00
|
|
|
symbolmap : SymbolMap; // symbol -> address
|
2018-09-17 20:09:09 +00:00
|
|
|
addr2symbol : AddrSymbolMap; // address -> symbol
|
2020-07-08 01:56:44 +00:00
|
|
|
debuginfo : {}; // extra platform-specific debug info
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2020-07-08 01:56:44 +00:00
|
|
|
constructor(symbolmap : SymbolMap, debuginfo : {}) {
|
2018-09-17 20:09:09 +00:00
|
|
|
this.symbolmap = symbolmap;
|
2020-07-08 01:56:44 +00:00
|
|
|
this.debuginfo = debuginfo;
|
2018-09-17 20:09:09 +00:00
|
|
|
this.addr2symbol = invertMap(symbolmap);
|
2019-08-13 19:43:41 +00:00
|
|
|
//// TODO: shouldn't be necc.
|
2019-09-28 01:50:41 +00:00
|
|
|
if (!this.addr2symbol[0x0]) this.addr2symbol[0x0] = '$00'; // needed for ...
|
2018-09-17 20:09:09 +00:00
|
|
|
this.addr2symbol[0x10000] = '__END__'; // ... dump memory to work
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 16:12:56 +00:00
|
|
|
type MemoryMapType = "main" | "vram";
|
|
|
|
type MemoryMap = { [type:string] : Segment[] };
|
2019-08-20 15:42:39 +00:00
|
|
|
|
2019-08-23 19:05:12 +00:00
|
|
|
export function isDebuggable(arg:any): arg is Debuggable {
|
2021-08-03 21:27:20 +00:00
|
|
|
return arg && typeof arg.getDebugCategories === 'function';
|
2019-08-23 19:05:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface Debuggable {
|
|
|
|
getDebugCategories?() : string[];
|
|
|
|
getDebugInfo?(category:string, state:EmuState) : string;
|
|
|
|
}
|
|
|
|
|
2018-07-08 04:58:11 +00:00
|
|
|
export interface Platform {
|
2019-12-18 01:30:42 +00:00
|
|
|
start() : void | Promise<void>;
|
2018-07-08 04:58:11 +00:00
|
|
|
reset() : void;
|
|
|
|
isRunning() : boolean;
|
|
|
|
getToolForFilename(s:string) : string;
|
|
|
|
getDefaultExtension() : string;
|
2021-06-03 23:17:06 +00:00
|
|
|
getPresets?() : Preset[];
|
2018-07-08 04:58:11 +00:00
|
|
|
pause() : void;
|
|
|
|
resume() : void;
|
2018-07-10 01:46:45 +00:00
|
|
|
loadROM(title:string, rom:any); // TODO: Uint8Array
|
2019-05-08 02:36:06 +00:00
|
|
|
loadBIOS?(title:string, rom:Uint8Array);
|
2020-07-11 14:51:26 +00:00
|
|
|
getROMExtension?(rom:FileData) : string;
|
2018-08-26 02:00:45 +00:00
|
|
|
|
|
|
|
loadState?(state : EmuState) : void;
|
|
|
|
saveState?() : EmuState;
|
|
|
|
loadControlsState?(state : EmuControlsState) : void;
|
|
|
|
saveControlsState?() : EmuControlsState;
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-08-27 13:28:31 +00:00
|
|
|
inspect?(ident:string) : string;
|
2018-07-11 15:17:37 +00:00
|
|
|
disassemble?(addr:number, readfn:(addr:number)=>number) : DisasmLine;
|
2018-07-08 04:58:11 +00:00
|
|
|
readAddress?(addr:number) : number;
|
2019-03-15 01:55:16 +00:00
|
|
|
readVRAMAddress?(addr:number) : number;
|
2019-08-27 03:31:39 +00:00
|
|
|
|
2018-07-08 04:58:11 +00:00
|
|
|
setFrameRate?(fps:number) : void;
|
|
|
|
getFrameRate?() : number;
|
2018-08-26 02:00:45 +00:00
|
|
|
|
2019-03-08 01:00:12 +00:00
|
|
|
setupDebug?(callback : BreakpointCallback) : void;
|
2018-07-08 04:58:11 +00:00
|
|
|
clearDebug?() : void;
|
|
|
|
step?() : void;
|
|
|
|
runToVsync?() : void;
|
|
|
|
runToPC?(pc:number) : void;
|
|
|
|
runUntilReturn?() : void;
|
|
|
|
stepBack?() : void;
|
2018-12-01 14:48:30 +00:00
|
|
|
runEval?(evalfunc : DebugEvalCondition) : void;
|
2019-04-07 01:47:42 +00:00
|
|
|
runToFrameClock?(clock : number) : void;
|
2020-08-13 17:32:47 +00:00
|
|
|
stepOver?() : void;
|
2020-08-23 18:40:04 +00:00
|
|
|
restartAtPC?(pc:number) : boolean;
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-07-08 04:58:11 +00:00
|
|
|
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
|
|
|
|
getSP?() : number;
|
2019-08-23 14:37:30 +00:00
|
|
|
getPC?() : number;
|
2018-08-16 23:19:20 +00:00
|
|
|
getOriginPC?() : number;
|
2018-08-26 02:00:45 +00:00
|
|
|
newCodeAnalyzer?() : CodeAnalyzer;
|
2019-08-27 16:12:56 +00:00
|
|
|
|
|
|
|
getPlatformName?() : string;
|
|
|
|
getMemoryMap?() : MemoryMap;
|
2018-07-29 20:26:05 +00:00
|
|
|
|
2018-08-22 03:39:34 +00:00
|
|
|
setRecorder?(recorder : EmuRecorder) : void;
|
2020-07-04 14:16:45 +00:00
|
|
|
advance?(novideo? : boolean) : number;
|
|
|
|
advanceFrameClock?(trap:DebugCondition, step:number) : number;
|
2022-09-15 17:05:54 +00:00
|
|
|
showHelp?() : string;
|
2018-11-26 11:12:45 +00:00
|
|
|
resize?() : void;
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2019-03-03 16:32:25 +00:00
|
|
|
getRasterScanline?() : number;
|
2022-09-01 18:43:09 +00:00
|
|
|
getRasterLineClock?() : number;
|
2019-06-09 15:13:25 +00:00
|
|
|
setBreakpoint?(id : string, cond : DebugCondition);
|
|
|
|
clearBreakpoint?(id : string);
|
2020-07-10 21:37:04 +00:00
|
|
|
hasBreakpoint?(id : string) : boolean;
|
2019-06-09 15:13:25 +00:00
|
|
|
getCPUState?() : CpuState;
|
2019-03-03 16:32:25 +00:00
|
|
|
|
2018-09-17 20:09:09 +00:00
|
|
|
debugSymbols? : DebugSymbols;
|
2020-07-08 01:56:44 +00:00
|
|
|
getDebugTree?() : {};
|
2019-08-24 15:28:04 +00:00
|
|
|
|
|
|
|
startProbing?() : ProbeRecorder;
|
|
|
|
stopProbing?() : void;
|
2020-08-15 20:03:56 +00:00
|
|
|
|
|
|
|
isBlocked?() : boolean; // is blocked, halted, or waiting for input?
|
2020-10-17 19:34:08 +00:00
|
|
|
|
|
|
|
readFile?(path: string) : FileData;
|
|
|
|
writeFile?(path: string, data: FileData) : boolean;
|
2021-08-01 18:03:50 +00:00
|
|
|
sourceFileFetch?: (path:string) => FileData;
|
2021-06-28 20:36:47 +00:00
|
|
|
|
|
|
|
getDownloadFile?() : {extension:string, blob:Blob};
|
2022-07-29 23:22:50 +00:00
|
|
|
getDebugSymbolFile?() : {extension:string, blob:Blob};
|
2018-07-08 04:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface Preset {
|
|
|
|
id : string;
|
|
|
|
name : string;
|
|
|
|
chapter? : number;
|
|
|
|
title? : string;
|
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
export interface MemoryBus {
|
|
|
|
read : (address:number) => number;
|
|
|
|
write : (address:number, value:number) => void;
|
2019-05-27 01:54:37 +00:00
|
|
|
contend?: (address:number, cycles:number) => number;
|
|
|
|
isContended?: (address:number) => boolean;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-12-01 14:48:30 +00:00
|
|
|
export type DebugCondition = () => boolean;
|
|
|
|
export type DebugEvalCondition = (c:CpuState) => boolean;
|
2019-12-01 17:47:42 +00:00
|
|
|
export type BreakpointCallback = (s:EmuState, msg?:string) => void;
|
2019-03-08 01:00:12 +00:00
|
|
|
// for composite breakpoints w/ single debug function
|
|
|
|
export class BreakpointList {
|
|
|
|
id2bp : {[id:string] : Breakpoint} = {};
|
|
|
|
getDebugCondition() : DebugCondition {
|
|
|
|
if (Object.keys(this.id2bp).length == 0) {
|
|
|
|
return null; // no breakpoints
|
|
|
|
} else {
|
|
|
|
// evaluate all breakpoints
|
|
|
|
return () => {
|
|
|
|
var result = false;
|
|
|
|
for (var id in this.id2bp)
|
|
|
|
if (this.id2bp[id].cond())
|
|
|
|
result = true;
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-24 16:51:47 +00:00
|
|
|
export interface Breakpoint {
|
|
|
|
cond: DebugCondition;
|
|
|
|
};
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2018-08-22 03:39:34 +00:00
|
|
|
export interface EmuRecorder {
|
|
|
|
frameRequested() : boolean;
|
2018-08-22 18:50:10 +00:00
|
|
|
recordFrame(state : EmuState);
|
2018-08-22 03:39:34 +00:00
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
/////
|
|
|
|
|
2018-08-27 13:28:31 +00:00
|
|
|
export abstract class BasePlatform {
|
|
|
|
recorder : EmuRecorder = null;
|
2018-09-17 20:09:09 +00:00
|
|
|
debugSymbols : DebugSymbols;
|
2020-10-17 19:34:08 +00:00
|
|
|
internalFiles : {[path:string] : FileData} = {};
|
2018-08-27 13:28:31 +00:00
|
|
|
|
|
|
|
abstract loadState(state : EmuState) : void;
|
|
|
|
abstract saveState() : EmuState;
|
|
|
|
abstract pause() : void;
|
|
|
|
abstract resume() : void;
|
2020-07-04 14:16:45 +00:00
|
|
|
abstract advance(novideo? : boolean) : number;
|
2018-08-27 13:28:31 +00:00
|
|
|
|
|
|
|
setRecorder(recorder : EmuRecorder) : void {
|
|
|
|
this.recorder = recorder;
|
|
|
|
}
|
|
|
|
updateRecorder() {
|
|
|
|
// are we recording and do we need to save a frame?
|
|
|
|
if (this.recorder && (<Platform><any>this).isRunning() && this.recorder.frameRequested()) {
|
|
|
|
this.recorder.recordFrame(this.saveState());
|
|
|
|
}
|
|
|
|
}
|
2020-07-06 23:53:20 +00:00
|
|
|
inspect(sym: string) : string {
|
|
|
|
return inspectSymbol((this as any) as Platform, sym);
|
|
|
|
}
|
2020-08-13 17:32:47 +00:00
|
|
|
getDebugTree() : {} {
|
2022-02-24 15:47:34 +00:00
|
|
|
var o : any = { };
|
|
|
|
o.state = this.saveState();
|
2022-06-22 17:22:47 +00:00
|
|
|
if (this.debugSymbols?.debuginfo) o.debuginfo = this.debugSymbols.debuginfo;
|
2022-02-24 15:47:34 +00:00
|
|
|
return o;
|
2020-07-08 01:56:44 +00:00
|
|
|
}
|
2020-10-17 19:34:08 +00:00
|
|
|
readFile(path: string) : FileData {
|
|
|
|
return this.internalFiles[path];
|
|
|
|
}
|
|
|
|
writeFile(path: string, data: FileData) : boolean {
|
|
|
|
this.internalFiles[path] = data;
|
|
|
|
return true;
|
|
|
|
}
|
2019-03-08 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export abstract class BaseDebugPlatform extends BasePlatform {
|
|
|
|
onBreakpointHit : BreakpointCallback;
|
|
|
|
debugCallback : DebugCondition;
|
|
|
|
debugSavedState : EmuState = null;
|
|
|
|
debugBreakState : EmuState = null;
|
|
|
|
debugTargetClock : number = 0;
|
|
|
|
debugClock : number = 0;
|
|
|
|
breakpoints : BreakpointList = new BreakpointList();
|
2019-08-27 03:31:39 +00:00
|
|
|
frameCount : number = 0;
|
2019-03-08 01:00:12 +00:00
|
|
|
|
|
|
|
abstract getCPUState() : CpuState;
|
|
|
|
|
|
|
|
setBreakpoint(id : string, cond : DebugCondition) {
|
|
|
|
if (cond) {
|
|
|
|
this.breakpoints.id2bp[id] = {cond:cond};
|
|
|
|
this.restartDebugging();
|
|
|
|
} else {
|
|
|
|
this.clearBreakpoint(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
clearBreakpoint(id : string) {
|
|
|
|
delete this.breakpoints.id2bp[id];
|
|
|
|
}
|
2020-07-10 21:37:04 +00:00
|
|
|
hasBreakpoint(id : string) {
|
|
|
|
return this.breakpoints.id2bp[id] != null;
|
|
|
|
}
|
2019-03-08 01:00:12 +00:00
|
|
|
getDebugCallback() : DebugCondition {
|
|
|
|
return this.breakpoints.getDebugCondition();
|
2018-11-22 12:39:06 +00:00
|
|
|
}
|
2019-03-08 01:00:12 +00:00
|
|
|
setupDebug(callback : BreakpointCallback) : void {
|
2018-07-11 15:17:37 +00:00
|
|
|
this.onBreakpointHit = callback;
|
|
|
|
}
|
|
|
|
clearDebug() {
|
2023-11-06 03:26:39 +00:00
|
|
|
if (this.debugBreakState != null && this.debugSavedState != null) {
|
2022-09-07 00:32:02 +00:00
|
|
|
this.loadState(this.debugSavedState);
|
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
this.debugSavedState = null;
|
|
|
|
this.debugBreakState = null;
|
2018-08-17 02:45:59 +00:00
|
|
|
this.debugTargetClock = -1;
|
2018-07-11 15:17:37 +00:00
|
|
|
this.debugClock = 0;
|
|
|
|
this.onBreakpointHit = null;
|
2019-03-08 01:00:12 +00:00
|
|
|
this.clearBreakpoint('debug');
|
2019-08-27 03:31:39 +00:00
|
|
|
this.frameCount = 0;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
setDebugCondition(debugCond : DebugCondition) {
|
2019-03-08 01:00:12 +00:00
|
|
|
this.setBreakpoint('debug', debugCond);
|
|
|
|
}
|
2022-08-31 16:19:12 +00:00
|
|
|
resetDebugging() {
|
2018-07-11 15:17:37 +00:00
|
|
|
if (this.debugSavedState) {
|
|
|
|
this.loadState(this.debugSavedState);
|
|
|
|
} else {
|
|
|
|
this.debugSavedState = this.saveState();
|
|
|
|
}
|
|
|
|
this.debugClock = 0;
|
2019-03-08 01:00:12 +00:00
|
|
|
this.debugCallback = this.getDebugCallback();
|
2018-07-11 15:17:37 +00:00
|
|
|
this.debugBreakState = null;
|
2022-08-31 16:19:12 +00:00
|
|
|
}
|
|
|
|
restartDebugging() {
|
|
|
|
this.resetDebugging();
|
2018-07-11 15:17:37 +00:00
|
|
|
this.resume();
|
|
|
|
}
|
2018-08-23 20:02:13 +00:00
|
|
|
preFrame() {
|
2019-09-02 19:50:31 +00:00
|
|
|
// save state before frame, to record any inputs that happened pre-frame
|
|
|
|
if (this.debugCallback && !this.debugBreakState) {
|
|
|
|
// save state every frame and rewind debug clocks
|
|
|
|
this.debugSavedState = this.saveState();
|
|
|
|
this.debugTargetClock -= this.debugClock;
|
|
|
|
this.debugClock = 0;
|
|
|
|
}
|
2018-08-23 20:02:13 +00:00
|
|
|
}
|
|
|
|
postFrame() {
|
2019-09-02 19:50:31 +00:00
|
|
|
// reload debug state at end of frame after breakpoint
|
|
|
|
if (this.debugCallback && this.debugBreakState) {
|
|
|
|
this.loadState(this.debugBreakState);
|
2019-08-26 22:14:50 +00:00
|
|
|
}
|
2019-09-12 15:14:33 +00:00
|
|
|
this.frameCount++;
|
2018-08-23 20:02:13 +00:00
|
|
|
}
|
2019-06-07 17:03:30 +00:00
|
|
|
pollControls() {
|
|
|
|
}
|
2020-10-17 19:34:08 +00:00
|
|
|
nextFrame(novideo : boolean) : number {
|
2019-06-07 17:03:30 +00:00
|
|
|
this.pollControls();
|
|
|
|
this.updateRecorder();
|
2018-08-23 20:02:13 +00:00
|
|
|
this.preFrame();
|
2020-07-04 14:16:45 +00:00
|
|
|
var steps = this.advance(novideo);
|
2018-08-23 20:02:13 +00:00
|
|
|
this.postFrame();
|
2020-07-04 14:16:45 +00:00
|
|
|
return steps;
|
2018-08-23 20:02:13 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
// default debugging
|
|
|
|
abstract getSP() : number;
|
|
|
|
abstract getPC() : number;
|
|
|
|
abstract isStable() : boolean;
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2018-07-27 17:39:09 +00:00
|
|
|
evalDebugCondition() {
|
2019-03-08 01:00:12 +00:00
|
|
|
if (this.debugCallback && !this.debugBreakState) {
|
|
|
|
this.debugCallback();
|
2018-07-27 17:39:09 +00:00
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
wasBreakpointHit() : boolean {
|
|
|
|
return this.debugBreakState != null;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-12-01 17:47:42 +00:00
|
|
|
breakpointHit(targetClock : number, reason? : string) {
|
2019-08-26 22:14:50 +00:00
|
|
|
console.log(this.debugTargetClock, targetClock, this.debugClock, this.isStable());
|
2018-07-11 15:17:37 +00:00
|
|
|
this.debugTargetClock = targetClock;
|
|
|
|
this.debugBreakState = this.saveState();
|
|
|
|
console.log("Breakpoint at clk", this.debugClock, "PC", this.debugBreakState.c.PC.toString(16));
|
|
|
|
this.pause();
|
|
|
|
if (this.onBreakpointHit) {
|
2019-12-01 17:47:42 +00:00
|
|
|
this.onBreakpointHit(this.debugBreakState, reason);
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-01 17:47:42 +00:00
|
|
|
haltAndCatchFire(reason : string) {
|
|
|
|
this.breakpointHit(this.debugClock, reason);
|
|
|
|
}
|
2018-07-27 17:39:09 +00:00
|
|
|
runEval(evalfunc : DebugEvalCondition) {
|
|
|
|
this.setDebugCondition( () => {
|
2019-08-26 22:14:50 +00:00
|
|
|
if (++this.debugClock >= this.debugTargetClock && this.isStable()) {
|
2018-07-27 17:39:09 +00:00
|
|
|
var cpuState = this.getCPUState();
|
|
|
|
if (evalfunc(cpuState)) {
|
2019-08-26 22:14:50 +00:00
|
|
|
this.breakpointHit(this.debugClock);
|
2018-07-27 17:39:09 +00:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-07-18 16:13:32 +00:00
|
|
|
runToPC(pc: number) {
|
|
|
|
this.debugTargetClock++;
|
|
|
|
this.runEval((c) => {
|
|
|
|
return c.PC == pc;
|
|
|
|
});
|
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
runUntilReturn() {
|
|
|
|
var SP0 = this.getSP();
|
|
|
|
this.runEval( (c:CpuState) : boolean => {
|
2019-12-27 19:19:11 +00:00
|
|
|
return c.SP > SP0; // TODO: check for RTS/RET opcode
|
2019-08-26 22:14:50 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
runToFrameClock(clock : number) : void {
|
2019-04-07 01:47:42 +00:00
|
|
|
this.restartDebugging();
|
|
|
|
this.debugTargetClock = clock;
|
2019-08-26 22:14:50 +00:00
|
|
|
this.runEval(() : boolean => { return true; });
|
2019-04-07 01:47:42 +00:00
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
step() {
|
2019-08-26 22:14:50 +00:00
|
|
|
this.runToFrameClock(this.debugClock+1);
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
stepBack() {
|
|
|
|
var prevState;
|
|
|
|
var prevClock;
|
2019-08-26 22:14:50 +00:00
|
|
|
var clock0 = this.debugTargetClock;
|
|
|
|
this.restartDebugging();
|
|
|
|
this.debugTargetClock = clock0 - 25; // TODO: depends on CPU
|
|
|
|
this.runEval( (c:CpuState) : boolean => {
|
|
|
|
if (this.debugClock < clock0) {
|
|
|
|
prevState = this.saveState();
|
|
|
|
prevClock = this.debugClock;
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
if (prevState) {
|
|
|
|
this.loadState(prevState);
|
|
|
|
this.debugClock = prevClock;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
return true;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2019-08-27 03:31:39 +00:00
|
|
|
runToVsync() {
|
2019-09-12 15:14:33 +00:00
|
|
|
this.restartDebugging();
|
2019-08-27 03:31:39 +00:00
|
|
|
var frame0 = this.frameCount;
|
|
|
|
this.runEval( () : boolean => {
|
|
|
|
return this.frameCount > frame0;
|
|
|
|
});
|
|
|
|
}
|
2020-07-06 23:53:20 +00:00
|
|
|
}
|
2020-01-05 23:49:08 +00:00
|
|
|
|
2020-07-06 23:53:20 +00:00
|
|
|
export function inspectSymbol(platform : Platform, sym : string) : string {
|
|
|
|
if (!platform.debugSymbols) return;
|
|
|
|
var symmap = platform.debugSymbols.symbolmap;
|
|
|
|
var addr2sym = platform.debugSymbols.addr2symbol;
|
|
|
|
if (!symmap || !platform.readAddress) return null;
|
|
|
|
var addr = symmap["_"+sym] || symmap[sym]; // look for C or asm symbol
|
|
|
|
if (!(typeof addr == 'number')) return null;
|
|
|
|
var b = platform.readAddress(addr);
|
|
|
|
// don't show 2 bytes if there's a symbol at the next address
|
|
|
|
if (addr2sym && addr2sym[addr+1] != null) {
|
|
|
|
return "$"+hex(addr,4) + " = $"+hex(b,2)+" ("+b+" decimal)"; // unsigned
|
|
|
|
} else {
|
|
|
|
let b2 = platform.readAddress(addr+1);
|
|
|
|
let w = b | (b2<<8);
|
|
|
|
return "$"+hex(addr,4) + " = $"+hex(b,2)+" $"+hex(b2,2)+" ("+((w<<16)>>16)+" decimal)"; // signed
|
2020-01-05 23:49:08 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
////// 6502
|
|
|
|
|
|
|
|
export function getToolForFilename_6502(fn:string) : string {
|
2023-11-05 18:54:24 +00:00
|
|
|
if (fn.endsWith("-llvm.c")) return "remote:llvm-mos";
|
2019-08-26 22:14:50 +00:00
|
|
|
if (fn.endsWith(".c")) return "cc65";
|
|
|
|
if (fn.endsWith(".h")) return "cc65";
|
|
|
|
if (fn.endsWith(".s")) return "ca65";
|
|
|
|
if (fn.endsWith(".ca65")) return "ca65";
|
|
|
|
if (fn.endsWith(".dasm")) return "dasm";
|
|
|
|
if (fn.endsWith(".acme")) return "acme";
|
2021-03-04 14:20:00 +00:00
|
|
|
if (fn.endsWith(".wiz")) return "wiz";
|
2022-01-29 17:34:26 +00:00
|
|
|
if (fn.endsWith(".ecs")) return "ecs";
|
2019-08-26 22:14:50 +00:00
|
|
|
return "dasm"; // .a
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: can merge w/ Z80?
|
|
|
|
export abstract class Base6502Platform extends BaseDebugPlatform {
|
|
|
|
|
|
|
|
// some platforms store their PC one byte before or after the first opcode
|
|
|
|
// so we correct when saving and loading from state
|
|
|
|
debugPCDelta = -1;
|
|
|
|
fixPC(c) { c.PC = (c.PC + this.debugPCDelta) & 0xffff; return c; }
|
|
|
|
unfixPC(c) { c.PC = (c.PC - this.debugPCDelta) & 0xffff; return c;}
|
|
|
|
getSP() { return this.getCPUState().SP };
|
|
|
|
getPC() { return this.getCPUState().PC };
|
|
|
|
isStable() { return !this.getCPUState()['T']; }
|
2020-08-05 04:48:29 +00:00
|
|
|
abstract readAddress(addr:number) : number;
|
2018-07-27 17:39:09 +00:00
|
|
|
|
|
|
|
newCPU(membus : MemoryBus) {
|
2022-02-21 22:54:33 +00:00
|
|
|
var cpu = new _MOS6502();
|
2018-07-27 17:39:09 +00:00
|
|
|
cpu.connectBus(membus);
|
|
|
|
return cpu;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-07-27 17:39:09 +00:00
|
|
|
|
|
|
|
getOpcodeMetadata(opcode, offset) {
|
2018-08-02 15:00:47 +00:00
|
|
|
return getOpcodeMetadata_6502(opcode, offset);
|
2018-07-27 17:39:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getOriginPC() : number {
|
|
|
|
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
|
|
|
}
|
|
|
|
getToolForFilename = getToolForFilename_6502;
|
|
|
|
getDefaultExtension() { return ".a"; };
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
getDebugCategories() {
|
|
|
|
return ['CPU','ZPRAM','Stack'];
|
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_6502(state.c);
|
2019-08-23 14:37:30 +00:00
|
|
|
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
|
|
|
|
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
2018-07-29 20:26:05 +00:00
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
export function cpuStateToLongString_6502(c) : string {
|
2018-07-11 15:17:37 +00:00
|
|
|
function decodeFlags(c) {
|
|
|
|
var s = "";
|
|
|
|
s += c.N ? " N" : " -";
|
|
|
|
s += c.V ? " V" : " -";
|
|
|
|
s += c.D ? " D" : " -";
|
|
|
|
s += c.Z ? " Z" : " -";
|
|
|
|
s += c.C ? " C" : " -";
|
2018-07-30 15:51:57 +00:00
|
|
|
s += c.I ? " I" : " -";
|
2018-07-11 15:17:37 +00:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
return "PC " + hex(c.PC,4) + " " + decodeFlags(c) + "\n"
|
|
|
|
+ " A " + hex(c.A) + " " + (c.R ? "" : "BUSY") + "\n"
|
|
|
|
+ " X " + hex(c.X) + "\n"
|
|
|
|
+ " Y " + hex(c.Y) + " " + "SP " + hex(c.SP) + "\n";
|
|
|
|
}
|
|
|
|
|
2018-08-02 17:08:37 +00:00
|
|
|
var OPMETA_6502 = {
|
|
|
|
cycletime: [
|
|
|
|
7, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 6, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 0, 6, 0, 6, 3, 3, 3, 3, 2, 0, 2, 0, 4, 4, 4, 4, 2, 6, 0, 0, 4, 4, 4, 4, 2, 5, 2, 0, 0, 5, 0, 0, 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 0, 4, 4, 4, 4, 2, 5, 0, 5, 4, 4, 4, 4, 2, 4, 2, 0, 4, 4, 4, 4, 2, 6, 0, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 3, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 2, 6, 0, 8, 3, 3, 5, 5, 2, 2, 2, 0, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7
|
|
|
|
],
|
|
|
|
extracycles: [
|
|
|
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1
|
|
|
|
],
|
|
|
|
insnlengths: [
|
|
|
|
1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 3, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 0, 2, 0, 2, 2, 2, 2, 2, 1, 0, 1, 0, 3, 3, 3, 3, 2, 2, 0, 0, 2, 2, 2, 3, 1, 3, 1, 0, 0, 3, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 0, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 0, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 0, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3
|
|
|
|
],
|
|
|
|
validinsns: [
|
|
|
|
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 0, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0, 2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
export function getOpcodeMetadata_6502(opcode, address) {
|
2018-08-02 15:00:47 +00:00
|
|
|
// TODO: more intelligent maximum cycles
|
2018-08-18 00:46:55 +00:00
|
|
|
// TODO: must always be new object, b/c we might modify it
|
2018-08-02 15:00:47 +00:00
|
|
|
return {
|
|
|
|
opcode:opcode,
|
2018-08-02 17:08:37 +00:00
|
|
|
minCycles:OPMETA_6502.cycletime[opcode],
|
|
|
|
maxCycles:OPMETA_6502.cycletime[opcode] + OPMETA_6502.extracycles[opcode],
|
|
|
|
insnlength:OPMETA_6502.insnlengths[opcode]
|
2018-08-02 15:00:47 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
////// Z80
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
export function cpuStateToLongString_Z80(c) {
|
2018-07-11 15:17:37 +00:00
|
|
|
function decodeFlags(flags) {
|
2018-08-29 17:43:46 +00:00
|
|
|
return printFlags(flags, ["S","Z",,"H",,"V","N","C"], true);
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-25 19:09:21 +00:00
|
|
|
return "PC " + hex(c.PC,4) + " " + decodeFlags(c.AF) + " " + (c.iff1?"I":"-") + (c.iff2?"I":"-") + "\n"
|
2018-07-11 15:17:37 +00:00
|
|
|
+ "SP " + hex(c.SP,4) + " IR " + hex(c.IR,4) + "\n"
|
|
|
|
+ "IX " + hex(c.IX,4) + " IY " + hex(c.IY,4) + "\n"
|
|
|
|
+ "AF " + hex(c.AF,4) + " BC " + hex(c.BC,4) + "\n"
|
|
|
|
+ "DE " + hex(c.DE,4) + " HL " + hex(c.HL,4) + "\n"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2018-07-12 04:59:35 +00:00
|
|
|
export abstract class BaseZ80Platform extends BaseDebugPlatform {
|
2018-07-11 15:17:37 +00:00
|
|
|
|
|
|
|
_cpu;
|
2019-08-25 20:20:12 +00:00
|
|
|
waitCycles : number = 0;
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2019-08-25 20:20:12 +00:00
|
|
|
newCPU(membus : MemoryBus, iobus : MemoryBus) {
|
|
|
|
this._cpu = new Z80();
|
|
|
|
this._cpu.connectMemoryBus(membus);
|
|
|
|
this._cpu.connectIOBus(iobus);
|
|
|
|
return this._cpu;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getPC() { return this._cpu.getPC(); }
|
|
|
|
getSP() { return this._cpu.getSP(); }
|
2019-08-26 22:14:50 +00:00
|
|
|
isStable() { return true; }
|
2018-07-11 15:17:37 +00:00
|
|
|
|
|
|
|
// TODO: refactor other parts into here
|
2020-06-09 01:26:57 +00:00
|
|
|
runCPU(cpu, cycles:number) : number {
|
2018-07-11 15:17:37 +00:00
|
|
|
this._cpu = cpu; // TODO?
|
2019-08-25 20:20:12 +00:00
|
|
|
this.waitCycles = 0; // TODO: needs to spill over betwenn calls
|
2018-07-11 15:17:37 +00:00
|
|
|
if (this.wasBreakpointHit())
|
|
|
|
return 0;
|
|
|
|
var debugCond = this.getDebugCallback();
|
2019-08-25 20:20:12 +00:00
|
|
|
var n = 0;
|
|
|
|
this.waitCycles += cycles;
|
|
|
|
while (this.waitCycles > 0) {
|
|
|
|
if (debugCond && debugCond()) {
|
|
|
|
debugCond = null;
|
|
|
|
break;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-25 20:20:12 +00:00
|
|
|
var cyc = cpu.advanceInsn();
|
|
|
|
n += cyc;
|
|
|
|
this.waitCycles -= cyc;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-25 20:20:12 +00:00
|
|
|
return n;
|
2018-09-14 13:10:41 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
getToolForFilename = getToolForFilename_z80;
|
|
|
|
getDefaultExtension() { return ".c"; };
|
2019-06-03 14:08:29 +00:00
|
|
|
// TODO: Z80 opcode metadata
|
2018-07-11 15:17:37 +00:00
|
|
|
//this.getOpcodeMetadata = function() { }
|
2018-07-29 20:26:05 +00:00
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
getDebugCategories() {
|
2018-08-25 02:55:16 +00:00
|
|
|
return ['CPU','Stack'];
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_Z80(state.c);
|
2018-08-25 02:55:16 +00:00
|
|
|
case 'Stack': {
|
2018-09-17 20:09:09 +00:00
|
|
|
var sp = (state.c.SP-1) & 0xffff;
|
2018-09-05 02:28:12 +00:00
|
|
|
var start = sp & 0xff00;
|
2018-08-25 02:55:16 +00:00
|
|
|
var end = start + 0xff;
|
|
|
|
if (sp == 0) sp = 0x10000;
|
2018-09-17 20:09:09 +00:00
|
|
|
console.log(sp,start,end);
|
2018-08-25 02:55:16 +00:00
|
|
|
return dumpStackToString(<Platform><any>this, [], start, end, sp, 0xcd);
|
|
|
|
}
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
2018-07-29 20:26:05 +00:00
|
|
|
}
|
2018-08-27 21:31:49 +00:00
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3));
|
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2021-03-04 16:22:12 +00:00
|
|
|
export function getToolForFilename_z80(fn:string) : string {
|
2018-07-11 15:17:37 +00:00
|
|
|
if (fn.endsWith(".c")) return "sdcc";
|
2018-11-24 21:08:33 +00:00
|
|
|
if (fn.endsWith(".h")) return "sdcc";
|
2018-07-11 15:17:37 +00:00
|
|
|
if (fn.endsWith(".s")) return "sdasz80";
|
|
|
|
if (fn.endsWith(".ns")) return "naken";
|
|
|
|
if (fn.endsWith(".scc")) return "sccz80";
|
2018-08-28 17:59:12 +00:00
|
|
|
if (fn.endsWith(".z")) return "zmac";
|
2021-03-04 14:20:00 +00:00
|
|
|
if (fn.endsWith(".wiz")) return "wiz";
|
2018-08-28 14:10:59 +00:00
|
|
|
return "zmac";
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
////// 6809
|
|
|
|
|
|
|
|
export function cpuStateToLongString_6809(c) {
|
|
|
|
function decodeFlags(flags) {
|
2018-08-29 17:43:46 +00:00
|
|
|
return printFlags(flags, ["E","F","H","I", "N","Z","V","C"], true);
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
|
|
|
return "PC " + hex(c.PC,4) + " " + decodeFlags(c.CC) + "\n"
|
|
|
|
+ "SP " + hex(c.SP,4) + "\n"
|
2020-06-09 01:26:57 +00:00
|
|
|
+ "DP " + hex(c.DP,2) + "\n"
|
2018-08-16 23:19:20 +00:00
|
|
|
+ " A " + hex(c.A,2) + "\n"
|
|
|
|
+ " B " + hex(c.B,2) + "\n"
|
|
|
|
+ " X " + hex(c.X,4) + "\n"
|
|
|
|
+ " Y " + hex(c.Y,4) + "\n"
|
|
|
|
+ " U " + hex(c.U,4) + "\n"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2019-12-07 23:20:01 +00:00
|
|
|
export function getToolForFilename_6809(fn:string) : string {
|
|
|
|
if (fn.endsWith(".c")) return "cmoc";
|
|
|
|
if (fn.endsWith(".h")) return "cmoc";
|
|
|
|
if (fn.endsWith(".xasm")) return "xasm6809";
|
2023-11-05 18:12:14 +00:00
|
|
|
if (fn.endsWith(".lwasm")) return "lwasm";
|
|
|
|
return "cmoc";
|
2019-12-07 23:20:01 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
export abstract class Base6809Platform extends BaseZ80Platform {
|
|
|
|
|
|
|
|
newCPU(membus : MemoryBus) {
|
2021-08-06 02:19:43 +00:00
|
|
|
var cpu = Object.create(CPU6809());
|
2018-08-16 23:19:20 +00:00
|
|
|
cpu.init(membus.write, membus.read, 0);
|
|
|
|
return cpu;
|
|
|
|
}
|
|
|
|
|
2019-04-07 01:47:42 +00:00
|
|
|
cpuStateToLongString(c:CpuState) {
|
2018-08-16 23:19:20 +00:00
|
|
|
return cpuStateToLongString_6809(c);
|
|
|
|
}
|
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
// TODO: don't create new CPU
|
2021-08-06 02:19:43 +00:00
|
|
|
return Object.create(CPU6809()).disasm(read(pc), read(pc+1), read(pc+2), read(pc+3), read(pc+4), pc);
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
2019-03-21 01:38:53 +00:00
|
|
|
getDefaultExtension() : string { return ".asm"; };
|
2018-08-16 23:19:20 +00:00
|
|
|
//this.getOpcodeMetadata = function() { }
|
2019-12-07 23:20:01 +00:00
|
|
|
getToolForFilename = getToolForFilename_6809;
|
2019-03-21 01:38:53 +00:00
|
|
|
getDebugCategories() {
|
|
|
|
return ['CPU','Stack'];
|
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_6809(state.c);
|
|
|
|
default: return super.getDebugInfo(category, state);
|
|
|
|
}
|
|
|
|
}
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-02 17:33:22 +00:00
|
|
|
//TODO: how to get stack_end?
|
2022-08-28 22:44:05 +00:00
|
|
|
export function dumpStackToString(platform:Platform, mem:Uint8Array|number[], start:number, end:number, sp:number, jsrop:number, bigendian?:boolean) : string {
|
2018-08-25 02:55:16 +00:00
|
|
|
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;
|
2022-08-28 22:44:05 +00:00
|
|
|
if (bigendian) { addr = ((addr & 0xff) << 8) | ((addr & 0xff00) >> 8) }
|
2019-05-04 23:17:15 +00:00
|
|
|
var jsrofs = jsrop==0x20 ? -2 : -3; // 6502 vs Z80
|
2018-08-25 02:55:16 +00:00
|
|
|
var opcode = read(addr + jsrofs); // might be out of bounds
|
|
|
|
if (opcode == jsrop) { // JSR
|
|
|
|
s += "\n$" + hex(sp) + ": ";
|
2019-03-03 16:32:25 +00:00
|
|
|
s += hex(addr,4) + " " + lookupSymbol(platform, addr, true);
|
2018-08-25 02:55:16 +00:00
|
|
|
sp++;
|
|
|
|
nraw = 0;
|
|
|
|
} else {
|
|
|
|
if (nraw == 0)
|
|
|
|
s += "\n$" + hex(sp) + ": ";
|
2018-09-05 02:28:12 +00:00
|
|
|
s += hex(read(sp)) + " ";
|
2018-08-25 02:55:16 +00:00
|
|
|
if (++nraw == 8) nraw = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s+"\n";
|
|
|
|
}
|
2018-11-30 01:36:08 +00:00
|
|
|
|
2019-08-20 12:28:59 +00:00
|
|
|
// TODO: slow, funky, uses global
|
2019-03-03 16:32:25 +00:00
|
|
|
export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
|
2018-11-30 01:36:08 +00:00
|
|
|
var start = addr;
|
|
|
|
var addr2symbol = platform.debugSymbols && platform.debugSymbols.addr2symbol;
|
|
|
|
while (addr2symbol && addr >= 0) {
|
|
|
|
var sym = addr2symbol[addr];
|
2019-03-03 16:32:25 +00:00
|
|
|
if (sym) { // return first symbol we find
|
|
|
|
var sym = addr2symbol[addr];
|
2019-08-20 12:28:59 +00:00
|
|
|
return extra ? (sym + " + $" + hex(start-addr)) : sym;
|
2018-11-30 01:36:08 +00:00
|
|
|
}
|
2019-08-24 15:28:04 +00:00
|
|
|
if (!extra) break;
|
2018-11-30 01:36:08 +00:00
|
|
|
addr--;
|
|
|
|
}
|
2019-03-03 16:32:25 +00:00
|
|
|
return "";
|
2018-11-30 01:36:08 +00:00
|
|
|
}
|
|
|
|
|
2019-08-25 17:01:07 +00:00
|
|
|
/// new Machine platform adapters
|
2019-08-23 14:37:30 +00:00
|
|
|
|
2020-01-06 18:16:00 +00:00
|
|
|
export interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState>, SavesInputState<any> {
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-14 22:33:15 +00:00
|
|
|
export function hasVideo(arg:any): arg is VideoSource {
|
2019-08-23 14:37:30 +00:00
|
|
|
return typeof arg.connectVideo === 'function';
|
|
|
|
}
|
2020-10-14 22:33:15 +00:00
|
|
|
export function hasAudio(arg:any): arg is SampledAudioSource {
|
2019-08-23 14:37:30 +00:00
|
|
|
return typeof arg.connectAudio === 'function';
|
|
|
|
}
|
2020-10-14 22:33:15 +00:00
|
|
|
export function hasKeyInput(arg:any): arg is AcceptsKeyInput {
|
2019-08-23 22:52:59 +00:00
|
|
|
return typeof arg.setKeyInput === 'function';
|
|
|
|
}
|
2021-07-25 02:45:55 +00:00
|
|
|
export function hasJoyInput(arg:any): arg is AcceptsJoyInput {
|
|
|
|
return typeof arg.setJoyInput === 'function';
|
|
|
|
}
|
2020-10-14 22:33:15 +00:00
|
|
|
export function hasPaddleInput(arg:any): arg is AcceptsPaddleInput {
|
2019-08-26 16:58:42 +00:00
|
|
|
return typeof arg.setPaddleInput === 'function';
|
|
|
|
}
|
2020-10-14 22:33:15 +00:00
|
|
|
export function isRaster(arg:any): arg is RasterFrameBased {
|
2019-08-23 22:52:59 +00:00
|
|
|
return typeof arg.getRasterY === 'function';
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
2020-10-14 22:33:15 +00:00
|
|
|
export function hasProbe(arg:any): arg is Probeable {
|
2019-08-24 15:28:04 +00:00
|
|
|
return typeof arg.connectProbe == 'function';
|
|
|
|
}
|
2020-10-14 22:33:15 +00:00
|
|
|
export function hasBIOS(arg:any): arg is AcceptsBIOS {
|
2020-07-18 20:38:39 +00:00
|
|
|
return typeof arg.loadBIOS == 'function';
|
|
|
|
}
|
2020-10-17 19:34:08 +00:00
|
|
|
export function hasSerialIO(arg:any): arg is HasSerialIO {
|
|
|
|
return typeof arg.connectSerialIO === 'function';
|
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
|
|
|
|
export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPlatform implements Platform {
|
|
|
|
machine : T;
|
|
|
|
mainElement : HTMLElement;
|
|
|
|
timer : AnimationTimer;
|
|
|
|
video : RasterVideo;
|
|
|
|
audio : SampledAudio;
|
2019-08-23 22:34:40 +00:00
|
|
|
poller : ControllerPoller;
|
2021-06-14 19:38:51 +00:00
|
|
|
serialIOInterface : SerialIOInterface;
|
|
|
|
serialVisualizer : SerialIOVisualizer;
|
|
|
|
|
2019-08-24 15:28:04 +00:00
|
|
|
probeRecorder : ProbeRecorder;
|
|
|
|
startProbing;
|
|
|
|
stopProbing;
|
2021-06-14 19:38:51 +00:00
|
|
|
|
2019-08-23 14:37:30 +00:00
|
|
|
abstract newMachine() : T;
|
|
|
|
abstract getToolForFilename(s:string) : string;
|
|
|
|
abstract getDefaultExtension() : string;
|
|
|
|
abstract getPresets() : Preset[];
|
|
|
|
|
|
|
|
constructor(mainElement : HTMLElement) {
|
|
|
|
super();
|
|
|
|
this.mainElement = mainElement;
|
|
|
|
}
|
|
|
|
|
2021-06-14 19:38:51 +00:00
|
|
|
reset() {
|
|
|
|
this.machine.reset();
|
|
|
|
if (this.serialVisualizer != null) this.serialVisualizer.reset();
|
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
loadState(s) { this.machine.loadState(s); }
|
|
|
|
saveState() { return this.machine.saveState(); }
|
|
|
|
getSP() { return this.machine.cpu.getSP(); }
|
|
|
|
getPC() { return this.machine.cpu.getPC(); }
|
2019-08-26 22:14:50 +00:00
|
|
|
isStable() { return this.machine.cpu.isStable(); }
|
2019-08-23 14:37:30 +00:00
|
|
|
getCPUState() { return this.machine.cpu.saveState(); }
|
2019-08-23 22:52:59 +00:00
|
|
|
loadControlsState(s) { this.machine.loadControlsState(s); }
|
|
|
|
saveControlsState() { return this.machine.saveControlsState(); }
|
2019-08-23 14:37:30 +00:00
|
|
|
|
2021-06-03 23:17:06 +00:00
|
|
|
async start() {
|
|
|
|
this.machine = this.newMachine();
|
2019-08-24 15:28:04 +00:00
|
|
|
const m = this.machine;
|
2021-06-03 23:17:06 +00:00
|
|
|
// block on WASM loading
|
|
|
|
if (m instanceof BaseWASMMachine) {
|
|
|
|
await m.loadWASM();
|
|
|
|
}
|
2019-12-18 17:20:15 +00:00
|
|
|
var videoFrequency;
|
2019-08-23 14:37:30 +00:00
|
|
|
if (hasVideo(m)) {
|
|
|
|
var vp = m.getVideoParams();
|
2021-11-22 16:49:16 +00:00
|
|
|
this.video = new RasterVideo(this.mainElement, vp.width, vp.height,
|
|
|
|
{overscan: !!vp.overscan,
|
|
|
|
rotate: vp.rotate|0,
|
|
|
|
aspect: vp.aspect});
|
2019-08-23 14:37:30 +00:00
|
|
|
this.video.create();
|
|
|
|
m.connectVideo(this.video.getFrameData());
|
2019-09-08 15:39:54 +00:00
|
|
|
// TODO: support keyboard w/o video?
|
|
|
|
if (hasKeyInput(m)) {
|
|
|
|
this.video.setKeyboardEvents(m.setKeyInput.bind(m));
|
|
|
|
this.poller = new ControllerPoller(m.setKeyInput.bind(m));
|
|
|
|
}
|
2019-12-18 17:20:15 +00:00
|
|
|
videoFrequency = vp.videoFrequency;
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
2019-12-18 17:20:15 +00:00
|
|
|
this.timer = new AnimationTimer(videoFrequency || 60, this.nextFrame.bind(this));
|
2019-08-23 14:37:30 +00:00
|
|
|
if (hasAudio(m)) {
|
|
|
|
var ap = m.getAudioParams();
|
|
|
|
this.audio = new SampledAudio(ap.sampleRate);
|
|
|
|
this.audio.start();
|
|
|
|
m.connectAudio(this.audio);
|
|
|
|
}
|
2019-08-26 16:58:42 +00:00
|
|
|
if (hasPaddleInput(m)) {
|
|
|
|
this.video.setupMouseEvents();
|
|
|
|
}
|
2019-08-24 15:28:04 +00:00
|
|
|
if (hasProbe(m)) {
|
|
|
|
this.probeRecorder = new ProbeRecorder(m);
|
|
|
|
this.startProbing = () => {
|
|
|
|
m.connectProbe(this.probeRecorder);
|
|
|
|
return this.probeRecorder;
|
|
|
|
};
|
|
|
|
this.stopProbing = () => {
|
|
|
|
m.connectProbe(null);
|
|
|
|
};
|
|
|
|
}
|
2020-07-18 20:38:39 +00:00
|
|
|
if (hasBIOS(m)) {
|
|
|
|
this.loadBIOS = (title, data) => {
|
|
|
|
m.loadBIOS(data, title);
|
|
|
|
};
|
|
|
|
}
|
2021-06-14 19:38:51 +00:00
|
|
|
if (hasSerialIO(m)) {
|
|
|
|
if (this.serialIOInterface == null) {
|
|
|
|
this.serialVisualizer = new SerialIOVisualizer(this.mainElement, m);
|
|
|
|
} else {
|
|
|
|
m.connectSerialIO(this.serialIOInterface);
|
|
|
|
}
|
2020-10-17 19:34:08 +00:00
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loadROM(title, data) {
|
2022-09-02 16:05:03 +00:00
|
|
|
this.machine.loadROM(data, title);
|
2019-08-23 14:37:30 +00:00
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
2020-10-17 19:34:08 +00:00
|
|
|
loadBIOS : (title, data) => void; // only set if hasBIOS() is true
|
|
|
|
|
2019-08-26 16:58:42 +00:00
|
|
|
pollControls() {
|
|
|
|
this.poller && this.poller.poll();
|
|
|
|
if (hasPaddleInput(this.machine)) {
|
|
|
|
this.machine.setPaddleInput(0, this.video.paddle_x);
|
|
|
|
this.machine.setPaddleInput(1, this.video.paddle_y);
|
|
|
|
}
|
2020-07-18 20:38:39 +00:00
|
|
|
// TODO: put into interface
|
|
|
|
if (this.machine['pollControls']) {
|
|
|
|
this.machine['pollControls']();
|
|
|
|
}
|
2019-08-26 16:58:42 +00:00
|
|
|
}
|
2019-08-23 22:34:40 +00:00
|
|
|
|
2019-08-23 14:37:30 +00:00
|
|
|
advance(novideo:boolean) {
|
2022-08-31 16:19:12 +00:00
|
|
|
let trap = this.getDebugCallback();
|
|
|
|
var steps = this.machine.advanceFrame(trap);
|
2019-09-08 15:39:54 +00:00
|
|
|
if (!novideo && this.video) this.video.updateFrame();
|
2021-06-14 19:38:51 +00:00
|
|
|
if (!novideo && this.serialVisualizer) this.serialVisualizer.refresh();
|
2020-07-04 14:16:45 +00:00
|
|
|
return steps;
|
|
|
|
}
|
|
|
|
|
|
|
|
advanceFrameClock(trap, step) {
|
|
|
|
if (!(step > 0)) return;
|
|
|
|
if (this.machine instanceof BaseWASMMachine) {
|
|
|
|
return this.machine.advanceFrameClock(trap, step);
|
|
|
|
} else {
|
|
|
|
return this.machine.advanceFrame(() => {
|
|
|
|
return --step <= 0;
|
|
|
|
});
|
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
isRunning() {
|
2019-12-18 01:30:42 +00:00
|
|
|
return this.timer && this.timer.isRunning();
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
resume() {
|
|
|
|
this.timer.start();
|
|
|
|
this.audio && this.audio.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
pause() {
|
|
|
|
this.timer.stop();
|
|
|
|
this.audio && this.audio.stop();
|
2020-01-04 19:17:42 +00:00
|
|
|
}
|
2022-08-11 20:27:20 +00:00
|
|
|
|
2020-07-11 20:26:59 +00:00
|
|
|
// so probe views stick around TODO: must be a better way?
|
2020-01-04 19:17:42 +00:00
|
|
|
runToVsync() {
|
2022-08-11 20:27:20 +00:00
|
|
|
this.restartDebugging();
|
|
|
|
var flag = false;
|
|
|
|
this.runEval( () : boolean => {
|
|
|
|
if (this.getRasterScanline() > 0) flag = true;
|
|
|
|
else return flag;
|
|
|
|
});
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
2022-08-11 20:27:20 +00:00
|
|
|
// TODO: reset target clock counter
|
2019-08-23 22:52:59 +00:00
|
|
|
getRasterScanline() {
|
|
|
|
return isRaster(this.machine) && this.machine.getRasterY();
|
|
|
|
}
|
2020-07-08 23:04:23 +00:00
|
|
|
|
|
|
|
readAddress(addr : number) : number {
|
|
|
|
return this.machine.read(addr);
|
|
|
|
}
|
2021-06-06 05:50:45 +00:00
|
|
|
|
|
|
|
getDebugCategories() {
|
|
|
|
if (isDebuggable(this.machine))
|
|
|
|
return this.machine.getDebugCategories();
|
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
return isDebuggable(this.machine) && this.machine.getDebugInfo(category, state);
|
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
2019-08-23 22:52:59 +00:00
|
|
|
// TODO: move debug info into CPU?
|
|
|
|
|
2019-08-23 14:37:30 +00:00
|
|
|
export abstract class Base6502MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
|
|
|
|
|
|
|
|
getOpcodeMetadata = getOpcodeMetadata_6502;
|
|
|
|
getToolForFilename = getToolForFilename_6502;
|
|
|
|
|
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
|
|
|
}
|
|
|
|
getDebugCategories() {
|
2019-08-23 19:05:12 +00:00
|
|
|
if (isDebuggable(this.machine))
|
|
|
|
return this.machine.getDebugCategories();
|
|
|
|
else
|
|
|
|
return ['CPU','ZPRAM','Stack'];
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_6502(state.c);
|
|
|
|
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
|
|
|
|
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
|
2019-08-23 19:05:12 +00:00
|
|
|
default: return isDebuggable(this.machine) && this.machine.getDebugInfo(category, state);
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-23 22:52:59 +00:00
|
|
|
|
|
|
|
export abstract class BaseZ80MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
|
|
|
|
|
|
|
|
//getOpcodeMetadata = getOpcodeMetadata_z80;
|
|
|
|
getToolForFilename = getToolForFilename_z80;
|
|
|
|
|
|
|
|
getDebugCategories() {
|
2019-08-25 17:01:07 +00:00
|
|
|
if (isDebuggable(this.machine))
|
|
|
|
return this.machine.getDebugCategories();
|
|
|
|
else
|
|
|
|
return ['CPU','Stack'];
|
2019-08-23 22:52:59 +00:00
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_Z80(state.c);
|
|
|
|
case 'Stack': {
|
|
|
|
var sp = (state.c.SP-1) & 0xffff;
|
|
|
|
var start = sp & 0xff00;
|
|
|
|
var end = start + 0xff;
|
|
|
|
if (sp == 0) sp = 0x10000;
|
|
|
|
return dumpStackToString(<Platform><any>this, [], start, end, sp, 0xcd);
|
|
|
|
}
|
2019-08-25 19:09:21 +00:00
|
|
|
default: return isDebuggable(this.machine) && this.machine.getDebugInfo(category, state);
|
2019-08-23 22:52:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-12-18 01:30:42 +00:00
|
|
|
|
2022-08-28 22:44:05 +00:00
|
|
|
export abstract class Base6809MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
|
|
|
|
|
|
|
|
getToolForFilename = getToolForFilename_6809;
|
|
|
|
|
|
|
|
getDebugCategories() {
|
|
|
|
if (isDebuggable(this.machine))
|
|
|
|
return this.machine.getDebugCategories();
|
|
|
|
else
|
|
|
|
return ['CPU','Stack'];
|
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_6809(state.c);
|
|
|
|
case 'Stack': {
|
|
|
|
var sp = (state.c.SP-1) & 0xffff;
|
|
|
|
var start = sp & 0xff00;
|
|
|
|
var end = start + 0xff;
|
|
|
|
if (sp == 0) sp = 0x10000;
|
|
|
|
return dumpStackToString(<Platform><any>this, [], start, end, sp, 0x17, true);
|
|
|
|
}
|
|
|
|
default: return super.getDebugInfo(category, state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
// TODO: don't create new CPU
|
|
|
|
return Object.create(CPU6809()).disasm(read(pc), read(pc+1), read(pc+2), read(pc+3), read(pc+4), pc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-06 00:16:13 +00:00
|
|
|
///
|
2021-07-25 02:45:55 +00:00
|
|
|
|
2021-06-14 19:38:51 +00:00
|
|
|
class SerialIOVisualizer {
|
|
|
|
|
|
|
|
textarea : HTMLTextAreaElement;
|
|
|
|
//vlist: VirtualTextScroller;
|
|
|
|
device: HasSerialIO;
|
|
|
|
lastOutCount = -1;
|
|
|
|
lastInCount = -1;
|
|
|
|
|
|
|
|
constructor(parentElement: HTMLElement, device: HasSerialIO) {
|
|
|
|
this.device = device;
|
|
|
|
this.textarea = document.createElement("textarea");
|
|
|
|
this.textarea.classList.add('transcript');
|
|
|
|
this.textarea.classList.add('transcript-style-2');
|
|
|
|
this.textarea.style.display = 'none';
|
|
|
|
parentElement.appendChild(this.textarea);
|
|
|
|
/*
|
|
|
|
this.vlist = new VirtualTextScroller(parentElement);
|
|
|
|
this.vlist.create(parentElement, 1024, this.getMemoryLineAt.bind(this));
|
|
|
|
this.vlist.maindiv.style.height = '8em';
|
|
|
|
this.vlist.maindiv.style.overflow = 'clip';
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
reset() {
|
|
|
|
this.lastOutCount = 0;
|
|
|
|
this.lastInCount = 0;
|
|
|
|
this.textarea.style.display = 'none';
|
|
|
|
}
|
|
|
|
refresh() {
|
|
|
|
var lastop = '';
|
|
|
|
if (this.device.serialOut.length != this.lastOutCount) {
|
|
|
|
var s = '';
|
|
|
|
for (var ev of this.device.serialOut) {
|
|
|
|
if (lastop != ev.op) {
|
|
|
|
if (s != '') s += '\n';
|
|
|
|
if (ev.op === 'read') s += '<< ';
|
|
|
|
else if (ev.op === 'write') s += '>> ';
|
|
|
|
lastop = ev.op;
|
|
|
|
}
|
|
|
|
if (ev.value == 10) { s += '\u21b5'; lastop = ''; }
|
|
|
|
else { s += byteToASCII(ev.value); }
|
|
|
|
}
|
|
|
|
this.textarea.value = s;
|
|
|
|
this.lastOutCount = this.device.serialOut.length;
|
|
|
|
this.textarea.style.display = 'block';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|