1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2026-04-20 15:16:38 +00:00

Deploying to gh-pages from @ sehugg/8bitworkshop@1c0b3e2fdd 🚀

This commit is contained in:
sehugg
2023-11-13 22:30:17 +00:00
parent 38e2de5f05
commit 7e6bd48c21
175 changed files with 2541 additions and 873 deletions
+137 -131
View File
@@ -1,30 +1,30 @@
import { hex, byte2signed } from "./util";
import { Platform } from "./baseplatform";
import { OpcodeMetadata, Platform } from "./baseplatform";
const debug = false;
export interface CodeAnalyzer {
showLoopTimingForPC(pc:number);
pc2clockrange : {[key:number]:ClockRange};
MAX_CLOCKS : number;
showLoopTimingForPC(pc: number);
pc2clockrange: { [key: number]: ClockRange };
MAX_CLOCKS: number;
}
/// VCS TIMING ANALYSIS
// [taken, not taken]
const BRANCH_CONSTRAINTS = [
[{N:0},{N:1}],
[{N:1},{N:0}],
[{V:0},{V:1}],
[{V:1},{V:0}],
[{C:0},{C:1}],
[{C:1},{C:0}],
[{Z:0},{Z:1}],
[{Z:1},{Z:0}]
[{ N: 0 }, { N: 1 }],
[{ N: 1 }, { N: 0 }],
[{ V: 0 }, { V: 1 }],
[{ V: 1 }, { V: 0 }],
[{ C: 0 }, { C: 1 }],
[{ C: 1 }, { C: 0 }],
[{ Z: 0 }, { Z: 1 }],
[{ Z: 1 }, { Z: 0 }]
];
function constraintEquals(a,b) {
function constraintEquals(a, b) {
if (a == null || b == null)
return null;
for (var n in a) {
@@ -44,15 +44,15 @@ interface ClockRange {
}
abstract class CodeAnalyzer6502 implements CodeAnalyzer {
pc2clockrange : {[key:number]:ClockRange} = {};
jsrresult : {[key:number]:ClockRange} = {};
START_CLOCKS : number;
MAX_CLOCKS : number;
WRAP_CLOCKS : boolean;
platform : Platform;
MAX_CYCLES : number = 2000;
constructor(platform : Platform) {
pc2clockrange: { [key: number]: ClockRange } = {};
jsrresult: { [key: number]: ClockRange } = {};
START_CLOCKS: number;
MAX_CLOCKS: number;
WRAP_CLOCKS: boolean;
platform: Platform;
MAX_CYCLES: number = 2000;
constructor(platform: Platform) {
this.platform = platform;
}
@@ -62,12 +62,12 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
return meta; // minCycles, maxCycles
}
traceInstructions(pc:number, minclocks:number, maxclocks:number, subaddr:number, constraints) {
traceInstructions(pc: number, minclocks: number, maxclocks: number, subaddr: number, constraints) {
if (debug) console.log("trace", hex(pc), minclocks, maxclocks);
if (!constraints) constraints = {};
var modified = true;
var abort = false;
for (let i=0; modified && !abort; i++) {
for (let i = 0; modified && !abort; i++) {
if (i >= this.MAX_CYCLES) {
console.log("too many cycles @", hex(pc), "routine", hex(subaddr));
break;
@@ -77,10 +77,10 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
// wrap clocks
minclocks = minclocks % this.MAX_CLOCKS;
maxclocks = maxclocks % this.MAX_CLOCKS;
if (maxclocks == minclocks-1) {
if (maxclocks == minclocks - 1) {
if (debug) console.log("0-75", hex(pc), minclocks, maxclocks);
minclocks = 0;
maxclocks = this.MAX_CLOCKS-1;
maxclocks = this.MAX_CLOCKS - 1;
}
} else {
// truncate clocks
@@ -88,13 +88,13 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
maxclocks = Math.min(this.MAX_CLOCKS, maxclocks);
}
let meta = this.getClockCountsAtPC(pc);
let lob = this.platform.readAddress(pc+1);
let hib = this.platform.readAddress(pc+2);
let lob = this.platform.readAddress(pc + 1);
let hib = this.platform.readAddress(pc + 2);
let addr = lob + (hib << 8);
let pc0 = pc;
let pcrange = this.pc2clockrange[pc0];
if (pcrange == null) {
this.pc2clockrange[pc0] = pcrange = {minclocks:minclocks, maxclocks:maxclocks};
this.pc2clockrange[pc0] = pcrange = { minclocks: minclocks, maxclocks: maxclocks };
if (debug) console.log("new", hex(pc), hex(pc0), hex(subaddr), minclocks, maxclocks);
modified = true;
}
@@ -103,7 +103,7 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
if (this.WRAP_CLOCKS && (minclocks <= maxclocks) != (pcrange.minclocks <= pcrange.maxclocks)) {
if (debug) console.log("wrap", hex(pc), hex(pc0), hex(subaddr), minclocks, maxclocks, pcrange);
pcrange.minclocks = minclocks = 0;
pcrange.maxclocks = maxclocks = this.MAX_CLOCKS-1;
pcrange.maxclocks = maxclocks = this.MAX_CLOCKS - 1;
modified = true;
}
if (minclocks < pcrange.minclocks) {
@@ -124,106 +124,88 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
pc += meta.insnlength;
var oldconstraints = constraints;
constraints = null;
// TODO: if jump to zero-page, maybe assume RTS?
switch (meta.opcode) {
case 0x19: case 0x1d:
case 0x39: case 0x3d:
case 0x59: case 0x5d:
case 0x79: case 0x7d:
case 0xb9: case 0xbb:
case 0xbc: case 0xbd: case 0xbe: case 0xbf:
case 0xd9: case 0xdd:
case 0xf9: case 0xfd:
if (lob == 0) meta.maxCycles -= 1; // no page boundary crossed
break;
// TODO: only VCS
case 0x85:
if (lob == 0x2) { // STA WSYNC
minclocks = maxclocks = 0;
meta.minCycles = meta.maxCycles = 0;
}
break;
// TODO: only NES (sprite 0 poll)
case 0x2c:
if (lob == 0x02 && hib == 0x20) { // BIT $2002
minclocks = 0;
maxclocks = 4; // uncertainty b/c of assumed branch poll
meta.minCycles = meta.maxCycles = 0;
}
break;
// TODO: only Apple2 (vapor lock)
/*
case 0xad:
if (lob == 0x61 && hib == 0xc0) { // LDA $C061
minclocks = 0;
maxclocks = 4; // uncertainty?
meta.minCycles = meta.maxCycles = 0;
}
break;
*/
case 0x20: // JSR
// TODO: handle bare RTS case
minclocks += meta.minCycles;
maxclocks += meta.maxCycles;
this.traceInstructions(addr, minclocks, maxclocks, addr, constraints);
var result = this.jsrresult[addr];
if (result) {
minclocks = result.minclocks;
maxclocks = result.maxclocks;
} else {
console.log("No JSR result!", hex(pc), hex(addr));
minclocks = maxclocks;
//return;
}
break;
case 0x4c: // JMP
pc = addr; // TODO: make sure in ROM space
break;
case 0x40: // RTI
abort = true;
break;
case 0x60: // RTS
if (subaddr) { // TODO: 0 doesn't work
// TODO: combine with previous result
var result = this.jsrresult[subaddr];
if (!result) {
result = {minclocks:minclocks, maxclocks:maxclocks};
let syncMaxCycles = this.getMaxCyclesForSync(meta, lob, hib);
if (typeof syncMaxCycles === 'number') {
minclocks = 0;
maxclocks = syncMaxCycles;
meta.minCycles = meta.maxCycles = 0;
} else {
// TODO: if jump to zero-page, maybe assume RTS?
switch (meta.opcode) {
case 0x19: case 0x1d:
case 0x39: case 0x3d:
case 0x59: case 0x5d:
case 0x79: case 0x7d:
case 0xb9: case 0xbb:
case 0xbc: case 0xbd: case 0xbe: case 0xbf:
case 0xd9: case 0xdd:
case 0xf9: case 0xfd:
if (lob == 0) meta.maxCycles -= 1; // no page boundary crossed
break;
case 0x20: // JSR
// TODO: handle bare RTS case
minclocks += meta.minCycles;
maxclocks += meta.maxCycles;
this.traceInstructions(addr, minclocks, maxclocks, addr, constraints);
var result = this.jsrresult[addr];
if (result) {
minclocks = result.minclocks;
maxclocks = result.maxclocks;
} else {
result = {
minclocks:Math.min(minclocks,result.minclocks),
maxclocks:Math.max(maxclocks,result.maxclocks)
}
console.log("No JSR result!", hex(pc), hex(addr));
minclocks = maxclocks;
//return;
}
this.jsrresult[subaddr] = result;
console.log("RTS", hex(pc), hex(subaddr), this.jsrresult[subaddr]);
}
return;
case 0x10: case 0x30: // branch
case 0x50: case 0x70:
case 0x90: case 0xB0:
case 0xD0: case 0xF0:
var newpc = pc + byte2signed(lob);
var crosspage = (pc>>8) != (newpc>>8);
if (!crosspage) meta.maxCycles--;
// TODO: other instructions might modify flags too
var cons = BRANCH_CONSTRAINTS[Math.floor((meta.opcode-0x10)/0x20)];
var cons0 = constraintEquals(oldconstraints, cons[0]);
var cons1 = constraintEquals(oldconstraints, cons[1]);
// recursively trace the taken branch
if (true || cons0 !== false) { // TODO?
this.traceInstructions(newpc, minclocks+meta.maxCycles, maxclocks+meta.maxCycles, subaddr, cons[0]);
}
// abort if we will always take the branch
if (cons1 === false) {
console.log("branch always taken", hex(pc), oldconstraints, cons[1]);
break;
case 0x4c: // JMP
pc = addr; // TODO: make sure in ROM space
break;
case 0x40: // RTI
abort = true;
}
constraints = cons[1]; // not taken
meta.maxCycles = meta.minCycles; // branch not taken, no extra clock(s)
break;
case 0x6c:
console.log("Instruction not supported!", hex(pc), hex(meta.opcode), meta); // TODO
return;
break;
case 0x60: // RTS
if (subaddr) { // TODO: 0 doesn't work
// TODO: combine with previous result
var result = this.jsrresult[subaddr];
if (!result) {
result = { minclocks: minclocks, maxclocks: maxclocks };
} else {
result = {
minclocks: Math.min(minclocks, result.minclocks),
maxclocks: Math.max(maxclocks, result.maxclocks)
}
}
this.jsrresult[subaddr] = result;
console.log("RTS", hex(pc), hex(subaddr), this.jsrresult[subaddr]);
}
return;
case 0x10: case 0x30: // branch
case 0x50: case 0x70:
case 0x90: case 0xB0:
case 0xD0: case 0xF0:
var newpc = pc + byte2signed(lob);
var crosspage = (pc >> 8) != (newpc >> 8);
if (!crosspage) meta.maxCycles--;
// TODO: other instructions might modify flags too
var cons = BRANCH_CONSTRAINTS[Math.floor((meta.opcode - 0x10) / 0x20)];
var cons0 = constraintEquals(oldconstraints, cons[0]);
var cons1 = constraintEquals(oldconstraints, cons[1]);
// recursively trace the taken branch
if (true || cons0 !== false) { // TODO?
this.traceInstructions(newpc, minclocks + meta.maxCycles, maxclocks + meta.maxCycles, subaddr, cons[0]);
}
// abort if we will always take the branch
if (cons1 === false) {
console.log("branch always taken", hex(pc), oldconstraints, cons[1]);
abort = true;
}
constraints = cons[1]; // not taken
meta.maxCycles = meta.minCycles; // branch not taken, no extra clock(s)
break;
case 0x6c:
console.log("Instruction not supported!", hex(pc), hex(meta.opcode), meta); // TODO
return;
}
}
// add min/max instruction time to min/max clocks bound
if (debug) console.log("add", hex(pc), meta.minCycles, meta.maxCycles);
@@ -232,41 +214,65 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
}
}
showLoopTimingForPC(pc:number) {
showLoopTimingForPC(pc: number) {
this.pc2clockrange = {};
this.jsrresult = {};
// recurse through all traces
this.traceInstructions(pc | this.platform.getOriginPC(), this.START_CLOCKS, this.MAX_CLOCKS, 0, {});
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
}
}
// 76 cycles
export class CodeAnalyzer_vcs extends CodeAnalyzer6502 {
constructor(platform : Platform) {
constructor(platform: Platform) {
super(platform);
this.MAX_CLOCKS = 76; // 1 scanline
this.START_CLOCKS = 0; // TODO?
this.WRAP_CLOCKS = true;
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
if (meta.opcode == 0x85) {
if (lob == 0x2) { // STA WSYNC
return 0;
}
}
}
}
// https://wiki.nesdev.com/w/index.php/PPU_rendering#Line-by-line_timing
// TODO: sprite 0 hit, CPU stalls
export class CodeAnalyzer_nes extends CodeAnalyzer6502 {
constructor(platform : Platform) {
constructor(platform: Platform) {
super(platform);
this.MAX_CLOCKS = 114; // 341 clocks for 3 scanlines
this.START_CLOCKS = 0;
this.WRAP_CLOCKS = true;
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
if (meta.opcode == 0x2c) {
if (lob == 0x02 && hib == 0x20) { // BIT $2002
return 4; // uncertainty b/c of assumed branch poll
}
}
}
}
export class CodeAnalyzer_apple2 extends CodeAnalyzer6502 {
constructor(platform : Platform) {
constructor(platform: Platform) {
super(platform);
this.MAX_CLOCKS = 65;
this.START_CLOCKS = 0;
this.WRAP_CLOCKS = true;
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
if (meta.opcode == 0xad) {
if (lob == 0x61 && hib == 0xc0) { // LDA $C061
return 4; // uncertainty b/c of assumed branch poll
}
}
}
}
+25 -5
View File
@@ -1,5 +1,5 @@
import { RasterVideo, dumpRAM, AnimationTimer, ControllerPoller } from "./emu";
import { RasterVideo, dumpRAM, AnimationTimer, ControllerPoller, drawCrosshair } from "./emu";
import { hex, printFlags, invertMap, byteToASCII } from "./util";
import { CodeAnalyzer } from "./analysis";
import { Segment, FileData } from "./workertypes";
@@ -850,7 +850,7 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
}
}
}
loadROM(title, data) {
this.machine.loadROM(data, title);
this.reset();
@@ -873,11 +873,28 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
advance(novideo:boolean) {
let trap = this.getDebugCallback();
var steps = this.machine.advanceFrame(trap);
if (!novideo && this.video) this.video.updateFrame();
if (!novideo && this.serialVisualizer) this.serialVisualizer.refresh();
if (!novideo && this.video) {
this.video.updateFrame();
this.updateVideoDebugger();
}
if (!novideo && this.serialVisualizer) {
this.serialVisualizer.refresh();
}
return steps;
}
updateVideoDebugger() {
if (!this.isRunning() && isRaster(this.machine) && this.machine.getRasterCanvasPosition) {
const {x,y} = this.machine.getRasterCanvasPosition();
if (x >= 0 || y >= 0) {
const ctx = this.video?.getContext();
if (ctx) {
drawCrosshair(ctx, x, y, 1);
}
}
}
}
advanceFrameClock(trap, step) {
if (!(step > 0)) return;
if (this.machine instanceof BaseWASMMachine) {
@@ -915,7 +932,10 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
// TODO: reset target clock counter
getRasterScanline() {
return isRaster(this.machine) && this.machine.getRasterY();
return isRaster(this.machine) && this.machine.getRasterY ? this.machine.getRasterY() : -1;
}
getRasterLineClock() {
return isRaster(this.machine) && this.machine.getRasterX ? this.machine.getRasterX() : -1;
}
readAddress(addr : number) : number {
+146 -143
View File
@@ -1,115 +1,116 @@
export interface SavesState<S> {
saveState() : S;
loadState(state:S) : void;
saveState(): S;
loadState(state: S): void;
}
export interface Bus {
read(a:number) : number;
write(a:number, v:number) : void;
readConst?(a:number) : number;
read(a: number): number;
write(a: number, v: number): void;
readConst?(a: number): number;
}
export interface ClockBased {
advanceClock() : void;
advanceClock(): void;
}
export interface InstructionBased {
advanceInsn() : number;
advanceInsn(): number;
}
export type TrapCondition = () => boolean;
export interface FrameBased {
advanceFrame(trap:TrapCondition) : number;
advanceFrame(trap: TrapCondition): number;
}
export interface VideoSource {
getVideoParams() : VideoParams;
connectVideo(pixels:Uint32Array) : void;
getVideoParams(): VideoParams;
connectVideo(pixels: Uint32Array): void;
}
export interface RasterFrameBased extends FrameBased, VideoSource {
getRasterY() : number;
getRasterX() : number;
getRasterY(): number;
getRasterX(): number;
getRasterCanvasPosition?(): { x: number, y: number };
}
export interface VideoParams {
width : number;
height : number;
overscan? : boolean;
rotate? : number;
videoFrequency? : number; // default = 60
aspect? : number;
width: number;
height: number;
overscan?: boolean;
rotate?: number;
videoFrequency?: number; // default = 60
aspect?: number;
}
// TODO: frame buffer optimization (apple2, etc)
export interface SampledAudioParams {
sampleRate : number;
stereo : boolean;
sampleRate: number;
stereo: boolean;
}
export interface SampledAudioSink {
feedSample(value:number, count:number) : void;
//sendAudioFrame(samples:Uint16Array) : void;
feedSample(value: number, count: number): void;
//sendAudioFrame(samples:Uint16Array) : void;
}
export interface SampledAudioSource {
getAudioParams() : SampledAudioParams;
connectAudio(audio : SampledAudioSink) : void;
getAudioParams(): SampledAudioParams;
connectAudio(audio: SampledAudioSink): void;
}
export interface AcceptsROM {
loadROM(data:Uint8Array, title?:string) : void;
loadROM(data: Uint8Array, title?: string): void;
}
export interface AcceptsBIOS {
loadBIOS(data:Uint8Array, title?:string) : void;
loadBIOS(data: Uint8Array, title?: string): void;
}
export interface Resettable {
reset() : void;
reset(): void;
}
export interface MemoryBusConnected {
connectMemoryBus(bus:Bus) : void;
connectMemoryBus(bus: Bus): void;
}
export interface IOBusConnected {
connectIOBus(bus:Bus) : void;
connectIOBus(bus: Bus): void;
}
export interface CPU extends MemoryBusConnected, Resettable, SavesState<any> {
getPC() : number;
getSP() : number;
isStable() : boolean;
getPC(): number;
getSP(): number;
isStable(): boolean;
}
export interface HasCPU extends Resettable {
cpu : CPU;
cpu: CPU;
}
export interface Interruptable<IT> {
interrupt(type:IT) : void;
interrupt(type: IT): void;
}
export interface SavesInputState<CS> {
loadControlsState(cs:CS) : void;
saveControlsState() : CS;
loadControlsState(cs: CS): void;
saveControlsState(): CS;
}
export interface AcceptsKeyInput {
setKeyInput(key:number, code:number, flags:number) : void;
setKeyInput(key: number, code: number, flags: number): void;
}
export interface AcceptsPaddleInput {
setPaddleInput(controller:number, value:number) : void;
setPaddleInput(controller: number, value: number): void;
}
// TODO: interface not yet used (setKeyInput() handles joystick)
export interface AcceptsJoyInput {
setJoyInput(joy:number, bitmask:number) : void;
setJoyInput(joy: number, bitmask: number): void;
}
// SERIAL I/O
@@ -123,15 +124,15 @@ export interface SerialEvent {
// TODO: all these needed?
export interface SerialIOInterface {
// from machine to platform
clearToSend() : boolean;
sendByte(b : number);
clearToSend(): boolean;
sendByte(b: number);
// from platform to machine
byteAvailable() : boolean;
recvByte() : number;
byteAvailable(): boolean;
recvByte(): number;
// implement these too
reset() : void;
advance(clocks: number) : void;
// refresh() : void;
reset(): void;
advance(clocks: number): void;
// refresh() : void;
}
export interface HasSerialIO {
@@ -143,62 +144,62 @@ export interface HasSerialIO {
/// PROFILER
export interface Probeable {
connectProbe(probe: ProbeAll) : void;
connectProbe(probe: ProbeAll): void;
}
export interface ProbeTime {
logClocks(clocks:number);
logClocks(clocks: number);
logNewScanline();
logNewFrame();
}
export interface ProbeCPU {
logExecute(address:number, SP:number);
logInterrupt(type:number);
logIllegal(address:number);
logWait(address:number);
logExecute(address: number, SP: number);
logInterrupt(type: number);
logIllegal(address: number);
logWait(address: number);
}
export interface ProbeBus {
logRead(address:number, value:number);
logWrite(address:number, value:number);
logDMARead(address:number, value:number);
logDMAWrite(address:number, value:number);
logRead(address: number, value: number);
logWrite(address: number, value: number);
logDMARead(address: number, value: number);
logDMAWrite(address: number, value: number);
}
export interface ProbeIO {
logIORead(address:number, value:number);
logIOWrite(address:number, value:number);
logIORead(address: number, value: number);
logIOWrite(address: number, value: number);
}
export interface ProbeVRAM {
logVRAMRead(address:number, value:number);
logVRAMWrite(address:number, value:number);
logVRAMRead(address: number, value: number);
logVRAMWrite(address: number, value: number);
}
export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO, ProbeVRAM {
logData(data:number); // entire 32 bits
logData(data: number); // entire 32 bits
addLogBuffer(src: Uint32Array);
}
export class NullProbe implements ProbeAll {
logClocks() {}
logNewScanline() {}
logNewFrame() {}
logExecute() {}
logInterrupt() {}
logRead() {}
logWrite() {}
logIORead() {}
logIOWrite() {}
logVRAMRead() {}
logVRAMWrite() {}
logIllegal() {}
logWait() {}
logDMARead() {}
logDMAWrite() {}
logData() {}
addLogBuffer(src: Uint32Array) {}
logClocks() { }
logNewScanline() { }
logNewFrame() { }
logExecute() { }
logInterrupt() { }
logRead() { }
logWrite() { }
logIORead() { }
logIOWrite() { }
logVRAMRead() { }
logVRAMWrite() { }
logIllegal() { }
logWait() { }
logDMARead() { }
logDMAWrite() { }
logData() { }
addLogBuffer(src: Uint32Array) { }
}
/// CONVENIENCE
@@ -215,32 +216,32 @@ export interface BasicMachineState extends BasicMachineControlsState {
export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, Probeable,
SavesState<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
abstract cpuFrequency : number;
abstract defaultROMSize : number;
abstract cpuFrequency: number;
abstract defaultROMSize: number;
abstract cpu : CPU;
abstract ram : Uint8Array;
abstract cpu: CPU;
abstract ram: Uint8Array;
rom : Uint8Array;
inputs : Uint8Array = new Uint8Array(32);
handler : (key,code,flags) => void; // keyboard handler
rom: Uint8Array;
inputs: Uint8Array = new Uint8Array(32);
handler: (key, code, flags) => void; // keyboard handler
nullProbe = new NullProbe();
probe : ProbeAll = this.nullProbe;
abstract read(a:number) : number;
abstract write(a:number, v:number) : void;
probe: ProbeAll = this.nullProbe;
setKeyInput(key:number, code:number, flags:number) : void {
this.handler && this.handler(key,code,flags);
abstract read(a: number): number;
abstract write(a: number, v: number): void;
setKeyInput(key: number, code: number, flags: number): void {
this.handler && this.handler(key, code, flags);
}
connectProbe(probe: ProbeAll) : void {
connectProbe(probe: ProbeAll): void {
this.probe = probe || this.nullProbe;
}
reset() {
this.cpu.reset();
}
loadROM(data:Uint8Array, title?:string) : void {
loadROM(data: Uint8Array, title?: string): void {
if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize);
this.rom.set(data);
}
@@ -251,9 +252,9 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
}
saveState() {
return {
c:this.cpu.saveState(),
ram:this.ram.slice(0),
inputs:this.inputs.slice(0),
c: this.cpu.saveState(),
ram: this.ram.slice(0),
inputs: this.inputs.slice(0),
};
}
loadControlsState(state) {
@@ -261,7 +262,7 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
}
saveControlsState() {
return {
inputs:this.inputs.slice(0)
inputs: this.inputs.slice(0)
};
}
advanceCPU() {
@@ -273,102 +274,104 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
this.probe.logClocks(n);
return n;
}
probeMemoryBus(membus:Bus) : Bus {
probeMemoryBus(membus: Bus): Bus {
return {
read: (a) => {
let val = membus.read(a);
this.probe.logRead(a,val);
this.probe.logRead(a, val);
return val;
},
write: (a,v) => {
this.probe.logWrite(a,v);
membus.write(a,v);
write: (a, v) => {
this.probe.logWrite(a, v);
membus.write(a, v);
}
};
}
connectCPUMemoryBus(membus:Bus) : void {
connectCPUMemoryBus(membus: Bus): void {
this.cpu.connectMemoryBus(this.probeMemoryBus(membus));
}
probeIOBus(iobus:Bus) : Bus {
probeIOBus(iobus: Bus): Bus {
return {
read: (a) => {
let val = iobus.read(a);
this.probe.logIORead(a,val);
this.probe.logIORead(a, val);
return val;
},
write: (a,v) => {
this.probe.logIOWrite(a,v);
iobus.write(a,v);
write: (a, v) => {
this.probe.logIOWrite(a, v);
iobus.write(a, v);
}
};
}
probeDMABus(iobus:Bus) : Bus {
probeDMABus(iobus: Bus): Bus {
return {
read: (a) => {
let val = iobus.read(a);
this.probe.logDMARead(a,val);
this.probe.logDMARead(a, val);
return val;
},
write: (a,v) => {
this.probe.logDMAWrite(a,v);
iobus.write(a,v);
write: (a, v) => {
this.probe.logDMAWrite(a, v);
iobus.write(a, v);
}
};
}
connectCPUIOBus(iobus:Bus) : void {
connectCPUIOBus(iobus: Bus): void {
this.cpu['connectIOBus'](this.probeIOBus(iobus));
}
}
export abstract class BasicMachine extends BasicHeadlessMachine implements SampledAudioSource {
abstract canvasWidth : number;
abstract numVisibleScanlines : number;
abstract sampleRate : number;
overscan : boolean = false;
rotate : number = 0;
aspectRatio : number;
pixels : Uint32Array;
audio : SampledAudioSink;
abstract canvasWidth: number;
abstract numVisibleScanlines: number;
abstract sampleRate: number;
overscan: boolean = false;
rotate: number = 0;
aspectRatio: number;
scanline : number;
getAudioParams() : SampledAudioParams {
return {sampleRate:this.sampleRate, stereo:false};
pixels: Uint32Array;
audio: SampledAudioSink;
scanline: number;
getAudioParams(): SampledAudioParams {
return { sampleRate: this.sampleRate, stereo: false };
}
connectAudio(audio : SampledAudioSink) : void {
connectAudio(audio: SampledAudioSink): void {
this.audio = audio;
}
getVideoParams() : VideoParams {
return {width:this.canvasWidth,
height:this.numVisibleScanlines,
aspect:this.aspectRatio,
overscan:this.overscan,
rotate:this.rotate};
getVideoParams(): VideoParams {
return {
width: this.canvasWidth,
height: this.numVisibleScanlines,
aspect: this.aspectRatio,
overscan: this.overscan,
rotate: this.rotate
};
}
connectVideo(pixels:Uint32Array) : void {
connectVideo(pixels: Uint32Array): void {
this.pixels = pixels;
}
}
export abstract class BasicScanlineMachine extends BasicMachine implements RasterFrameBased {
abstract numTotalScanlines : number;
abstract cpuCyclesPerLine : number;
abstract numTotalScanlines: number;
abstract cpuCyclesPerLine: number;
abstract startScanline() : void;
abstract drawScanline() : void;
abstract startScanline(): void;
abstract drawScanline(): void;
frameCycles : number;
advanceFrame(trap: TrapCondition) : number {
frameCycles: number;
advanceFrame(trap: TrapCondition): number {
this.preFrame();
var endLineClock = 0;
var steps = 0;
this.probe.logNewFrame();
this.frameCycles = 0;
for (var sl=0; sl<this.numTotalScanlines; sl++) {
for (var sl = 0; sl < this.numTotalScanlines; sl++) {
endLineClock += this.cpuCyclesPerLine; // could be fractional
this.scanline = sl;
this.startScanline();
+16
View File
@@ -215,6 +215,22 @@ export class VectorVideo extends RasterVideo {
}
}
export function drawCrosshair(ctx:CanvasRenderingContext2D, x:number, y:number, width:number) {
if (!ctx?.setLineDash) return; // for unit testing
ctx.fillStyle = 'rgba(0,0,0,0.25)';
ctx.fillRect(x-2, 0, 5, 32767);
ctx.fillRect(0, y-2, 32767, 5);
ctx.lineWidth = width;
ctx.strokeStyle = 'rgba(255,255,255,0.75)';
ctx.setLineDash([width*2,width*2]);
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 32767);
ctx.moveTo(0, y);
ctx.lineTo(32767, y);
ctx.stroke();
}
export class RAM {
mem : Uint8Array;
constructor(size:number) {
+6
View File
@@ -695,3 +695,9 @@ export function coerceToArray<T>(arrobj: any) : T[] {
else if (typeof arrobj === 'object') return Array.from(Object.values(arrobj))
else throw new Error(`Expected array or object, got "${arrobj}"`);
}
export function replaceAll(s:string, search:string, replace:string) : string {
if (s == '') return '';
if (search == '') return s;
return s.split(search).join(replace);
}
+5
View File
@@ -217,6 +217,11 @@ export class CodeProject {
while (m = re6.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// for acme
let re7 = /^[!]src\s+"(.+?)"/gmi;
while (m = re7.exec(text)) {
this.pushAllFiles(files, m[1]);
}
}
return files;
}
+4
View File
@@ -129,6 +129,7 @@ const TOOL_TO_SOURCE_STYLE = {
'remote:llvm-mos': 'text/x-csrc',
}
// TODO: move into tool class
const TOOL_TO_HELPURL = {
'dasm': 'https://raw.githubusercontent.com/sehugg/dasm/master/doc/dasm.txt',
'cc65': 'https://cc65.github.io/doc/cc65.html',
@@ -142,6 +143,7 @@ const TOOL_TO_HELPURL = {
'zmac': "https://raw.githubusercontent.com/sehugg/zmac/master/doc.txt",
'cmoc': "http://perso.b2b2c.ca/~sarrazip/dev/cmoc.html",
'remote:llvm-mos': 'https://llvm-mos.org/wiki/Welcome',
'acme': 'https://raw.githubusercontent.com/sehugg/acme/main/docs/QuickRef.txt',
}
function gaEvent(category:string, action:string, label?:string, value?:string) {
@@ -1895,6 +1897,8 @@ function _addIncludeFile() {
addFileToProject("Include", ".wiz", (s) => { return 'import "'+s+'";' });
else if (tool == 'ecs')
addFileToProject("Include", ".ecs", (s) => { return 'import "'+s+'"' });
else if (tool == 'acme')
addFileToProject("Include", ".acme", (s) => { return '!src "'+s+'"' });
else
alertError("Can't add include file to this project type (" + tool + ")");
}
+5 -1
View File
@@ -64,6 +64,7 @@ export class SourceEditor implements ProjectView {
errorwidgets = [];
errormarks = [];
inspectWidget;
refreshDelayMsec = 300;
createDiv(parent:HTMLElement) {
var div = document.createElement('div');
@@ -77,6 +78,9 @@ export class SourceEditor implements ProjectView {
this.editor.setSelection({line:0,ch:0}, {line:0,ch:0}, {scroll:true}); // move cursor to start
}
this.setupEditor();
if (current_project.getToolForFilename(this.path).startsWith("remote:")) {
this.refreshDelayMsec = 1000; // remote URLs get slower refresh
}
return div;
}
@@ -114,7 +118,7 @@ export class SourceEditor implements ProjectView {
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout( () => {
current_project.updateFile(this.path, this.editor.getValue());
}, 300);
}, this.refreshDelayMsec);
if (this.markHighlight) {
this.markHighlight.clear();
this.markHighlight = null;
+1
View File
@@ -556,6 +556,7 @@ export class BallyAstrocade extends BasicScanlineMachine implements AcceptsPaddl
case 'Astro': return this.m.toLongString(state);
}
}
getRasterCanvasPosition() { return { x: this.getRasterX(), y: this.getRasterY() }; }
}
+10 -2
View File
@@ -516,6 +516,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
var mc = 0;
var fc = 0;
var steps = 0;
this.lastFrameCycles = -1;
this.probe.logNewFrame();
//console.log(hex(this.cpu.getPC()), hex(this.maria.dll));
// visible lines
@@ -530,6 +531,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
if (trap && trap()) {
trap = null;
sl = 999;
this.lastFrameCycles = mc;
break; // TODO?
}
mc += this.advanceCPU() << 2;
@@ -567,6 +569,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
if (trap && trap()) {
trap = null;
sl = 999;
this.lastFrameCycles = mc;
break;
}
mc += this.advanceCPU() << 2;
@@ -583,13 +586,18 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
// TODO let bkcol = this.maria.regs[0x0];
// TODO $(this.video.canvas).css('background-color', COLORS_WEB[bkcol]);
*/
this.lastFrameCycles = fc;
return steps;
}
getRasterX() { return this.lastFrameCycles % colorClocksPerLine; }
// TODO: doesn't work when breakpoint
getRasterX() { return (this.lastFrameCycles + colorClocksPerLine) % colorClocksPerLine; }
getRasterY() { return this.scanline; }
getRasterCanvasPosition() {
return { x: this.getRasterX(), y: this.getRasterY() };
}
loadROM(data) {
if (data.length == 0xc080) data = data.slice(0x80); // strip header
this.rom = padBytes(data, this.defaultROMSize, true);
+8 -2
View File
@@ -267,12 +267,18 @@ export class Atari800 extends BasicScanlineMachine implements AcceptsPaddleInput
keycode: this.keycode,
};
}
getRasterScanline() {
getRasterY() {
return this.antic.v;
}
getRasterLineClock() {
getRasterX() {
return this.antic.h;
}
getRasterCanvasPosition() {
return {
x: this.antic.h * 4 - this.firstVisibleClock,
y: this.antic.v - this.firstVisibleScanline,
}
}
getDebugCategories() {
return ['CPU', 'Stack', 'ANTIC', 'GTIA', 'POKEY'];
}
+9
View File
@@ -200,9 +200,18 @@ export class C64_WASMMachine extends BaseWASMMachine
}
this.exports.c64_joystick(this.sys, this.joymask0, this.joymask1);
}
getRasterX() {
return this.statearr[0xf4];
}
getRasterY() {
return this.exports.machine_get_raster_line(this.sys);
}
getRasterCanvasPosition() {
return {
x: this.getRasterX() * 392/63,
y: this.getRasterY() - 14,
}
}
getDebugStateOffset(index: number) {
var p = this.exports.machine_get_debug_pointer(this.sys, index);
return p - this.sys;
+6
View File
@@ -69,6 +69,12 @@ export class CPC_WASMMachine extends BaseWASMMachine implements Machine {
getRasterY() {
return this.exports.machine_get_raster_line(this.sys);
}
getRasterCanvasPosition() {
return {
x: -1, // TODO?
y: this.getRasterY() - 14,
}
}
/*
z80_tick_t tick_cb; // 0
uint64_t bc_de_hl_fa; // 8
+6
View File
@@ -93,6 +93,12 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
getRasterY() {
return this.exports.machine_get_raster_line(this.sys);
}
getRasterCanvasPosition() {
return {
x: -1, // TODO?
y: this.getRasterY() - 14,
}
}
getCPUState() {
this.exports.machine_save_cpu_state(this.sys, this.cpustateptr);
var s = this.cpustatearr;
+7 -2
View File
@@ -13,15 +13,17 @@ const C64_PRESETS = [
{id:'joymove.c', name:'Sprite Movement'},
{id:'sprite_collision.c', name:'Sprite Collision'},
{id:'scroll1.c', name:'Scrolling (Single Buffer)'},
{id:'test_display_list.c', name:'Display List / Raster IRQ'},
{id:'scrolling_text.c', name:'Big Scrolling Text'},
{id:'side_scroller.c', name:'Side-Scrolling Game'},
{id:'scroll2.c', name:'Scrolling (Double Buffer)'},
{id:'scroll3.c', name:'Scrolling (Multidirectional)'},
{id:'scroll4.c', name:'Scrolling (Color RAM Buffering)'},
{id:'scroll5.c', name:'Scrolling (Camera Following)'},
{id:'side_scroller.c', name:'Side-Scrolling Game'},
{id:'scrollingmap1.c', name:'Scrolling Tile Map'},
{id:'fullscrollgame.c', name:'Full-Scrolling Game'},
{id:'test_multiplex.c', name:'Sprite Retriggering'},
{id:'test_multispritelib.c', name:'Sprite Multiplexing Library'},
{id:'scrolling_text.c', name:'Big Scrolling Text'},
{id:'mcbitmap.c', name:'Multicolor Bitmap Mode'},
{id:'testlz4.c', name:'LZ4 Bitmap Compression'},
//{id:'mandel.c', name:'Mandelbrot Fractal'},
@@ -29,6 +31,9 @@ const C64_PRESETS = [
//{id:'sidtune.dasm', name:'Tiny SID Tune (ASM)'},
{id:'siddemo.c', name:'SID Player Demo'},
{id:'climber.c', name:'Climber Game'},
{id:'test_border_sprites.c', name:'Sprites in the Borders'},
{id:'sprite_stretch.c', name:'Sprite Stretching'},
{id:'plasma.c', name:'Plasma Demo'},
{id:'hello.wiz', name:'Hello Wiz (Wiz)'},
];
+33 -7
View File
@@ -1,6 +1,6 @@
import { Platform, BasePlatform, cpuStateToLongString_6502, dumpStackToString, DisasmLine, CpuState, getToolForFilename_6502 } from "../common/baseplatform";
import { PLATFORMS, dumpRAM, EmuHalt, RasterVideo, __createCanvas } from "../common/emu";
import { PLATFORMS, dumpRAM, EmuHalt, RasterVideo, __createCanvas, drawCrosshair } from "../common/emu";
import { hex, loadScript, lpad, tobin } from "../common/util";
import { CodeAnalyzer_vcs } from "../common/analysis";
import { disassemble6502 } from "../common/cpu/disasm6502";
@@ -58,9 +58,11 @@ const VCS_PRESETS = [
];
function getToolForFilename_vcs(fn: string) {
if (fn.endsWith("-llvm.c")) return "remote:llvm-mos";
if (fn.endsWith(".wiz")) return "wiz";
if (fn.endsWith(".bb") || fn.endsWith(".bas")) return "bataribasic";
if (fn.endsWith(".ca65")) return "ca65";
if (fn.endsWith(".acme")) return "acme";
//if (fn.endsWith(".inc")) return "ca65";
if (fn.endsWith(".c")) return "cc65";
//if (fn.endsWith(".h")) return "cc65";
@@ -71,6 +73,7 @@ function getToolForFilename_vcs(fn: string) {
class VCSPlatform extends BasePlatform {
lastBreakState; // last breakpoint state
canvas : HTMLCanvasElement;
// TODO: super hack for ProbeBitmap view
machine = {
@@ -91,10 +94,10 @@ class VCSPlatform extends BasePlatform {
// show console div and start
$("#javatari-div").show();
Javatari.start();
var console = Javatari.room.console;
var jaconsole = Javatari.room.console;
// intercept clockPulse function
console.oldClockPulse = console.clockPulse;
console.clockPulse = function() {
jaconsole.oldClockPulse = jaconsole.clockPulse;
jaconsole.clockPulse = function() {
self.updateRecorder();
self.probe.logNewFrame();
this.oldClockPulse();
@@ -105,18 +108,19 @@ class VCSPlatform extends BasePlatform {
}
}
// intercept TIA end of line
var videoSignal = console.tia.getVideoOutput();
var videoSignal = jaconsole.tia.getVideoOutput();
videoSignal.oldNextLine = videoSignal.nextLine;
videoSignal.nextLine = function(pixels, vsync) {
self.probe.logNewScanline();
return this.oldNextLine(pixels, vsync);
}
// resize after added to dom tree
var jacanvas = $("#javatari-screen").find("canvas");
var jacanvas = $("#javatari-screen").find("canvas")[0];
const resizeObserver = new ResizeObserver(entries => {
this.resize();
});
resizeObserver.observe(jacanvas[0]);
resizeObserver.observe(jacanvas);
this.canvas = jacanvas;
}
loadROM(title, data) {
@@ -147,6 +151,16 @@ class VCSPlatform extends BasePlatform {
getRasterLineClock() : number {
return this.getRasterPosition().x;
}
getRasterCanvasPosition() : {x:number,y:number} {
let p = Javatari.room.console.tia.getVideoOutput().monitor.getDisplayParameters();
let {x,y} = this.getRasterPosition();
let canvasPos = {
x: (x - p.displayOriginX) * p.displayWidth * p.displayScaleX / (p.signalWidth - p.displayOriginX),
y: (y - p.displayOriginY) * p.displayHeight * p.displayScaleY / p.displayHeight
};
console.log(x,y,canvasPos,p);
return canvasPos;
}
// TODO: Clock changes this on event, so it may not be current
isRunning() {
@@ -193,6 +207,8 @@ class VCSPlatform extends BasePlatform {
Javatari.room.speaker.mute();
this.lastBreakState = state;
callback(state);
// TODO: we have to delay because javatari timer is still running
setTimeout(() => this.updateVideoDebugger(), 100);
}
Javatari.room.speaker.mute();
}
@@ -247,6 +263,7 @@ class VCSPlatform extends BasePlatform {
}
readAddress(addr) {
// TODO: shouldn't have to do this when debugging
// TODO: don't read bank switch addresses
if (this.lastBreakState && addr >= 0x80 && addr < 0x100)
return this.getRAMForState(this.lastBreakState)[addr & 0x7f];
else if ((addr & 0x1280) === 0x280)
@@ -418,6 +435,15 @@ class VCSPlatform extends BasePlatform {
var xt = (1 - scale) * 50;
$('#javatari-div').css('transform', `translateX(-${xt}%) translateY(-${xt}%) scale(${scale})`);
}
updateVideoDebugger() {
const {x,y} = this.getRasterCanvasPosition();
if (x >= 0 || y >= 0) {
const ctx = this.canvas?.getContext('2d');
if (ctx) {
drawCrosshair(ctx, x, y, 2);
}
}
}
};
// TODO: mixin for Base6502Platform?
+14 -11
View File
@@ -3,22 +3,22 @@ import fs from 'fs';
import path from 'path';
import { spawn } from 'child_process';
import { CodeListingMap, WorkerBuildStep, WorkerError, WorkerErrorResult, WorkerFileUpdate, WorkerResult, isOutputResult } from '../../common/workertypes';
import { getBasePlatform, getRootBasePlatform } from '../../common/util';
import { getBasePlatform, getRootBasePlatform, replaceAll } from '../../common/util';
import { BuildStep, makeErrorMatcher } from '../workermain';
import { parseObjDumpListing, parseObjDumpSymbolTable } from './clang';
import { parseObjDump } from './clang';
const LLVM_MOS_TOOL: ServerBuildTool = {
name: 'llvm-mos',
version: '',
extensions: ['.c', '.cpp', '.s'],
extensions: ['.c', '.cpp', '.s', '.S', '.C'],
archs: ['6502'],
platforms: ['atari8', 'c64', 'nes', 'pce', 'vcs'],
platform_configs: {
default: {
binpath: 'llvm-mos/bin',
command: 'mos-clang',
args: ['-Os', '-g', '-o', '$OUTFILE', '$INFILES'],
args: ['-Os', '-g', '-D', '__8BITWORKSHOP__', '-o', '$OUTFILE', '$INFILES'],
},
debug: { // TODO
binpath: 'llvm-mos/bin',
@@ -27,11 +27,9 @@ const LLVM_MOS_TOOL: ServerBuildTool = {
},
c64: {
command: 'mos-c64-clang',
libargs: ['-D', '__C64__']
},
atari8: {
command: 'mos-atari8-clang',
libargs: ['-D', '__ATARI__']
},
nes: {
command: 'mos-nes-nrom-clang', // TODO
@@ -39,7 +37,9 @@ const LLVM_MOS_TOOL: ServerBuildTool = {
},
pce: {
command: 'mos-pce-clang', // TODO
libargs: ['-D', '__PCE__']
},
vcs: {
command: 'mos-atari2600-3e-clang', // TODO
},
}
}
@@ -175,6 +175,10 @@ export class ServerBuildEnv {
resolve(this.processOutput(step));
}
} else {
errorData = replaceAll(errorData, this.sessionDir, '');
errorData = replaceAll(errorData, this.rootdir, '');
// remove folder paths
errorData = errorData.replace(/(\/var\/folders\/.+?\/).+?:/g, '');
let errorResult = await this.processErrors(step, errorData);
if (errorResult.errors.length === 0) {
errorResult.errors.push({ line: 0, msg: `Build failed.\n\n${errorData}` });
@@ -203,10 +207,9 @@ export class ServerBuildEnv {
async processDebugInfo(step: WorkerBuildStep): Promise<WorkerResult> {
let dbgfile = path.join(this.sessionDir, 'debug.out');
let dbglist = await fs.promises.readFile(dbgfile);
let listings = parseObjDumpListing(dbglist.toString());
let symbolmap = parseObjDumpSymbolTable(dbglist.toString());
return { output: [], listings, symbolmap };
let dbglist = (await fs.promises.readFile(dbgfile)).toString();
let { listings, symbolmap, segments } = parseObjDump(dbglist);
return { output: [], listings, symbolmap, segments };
}
async compileAndLink(step: WorkerBuildStep, updates: WorkerFileUpdate[]): Promise<WorkerResult> {
+29 -19
View File
@@ -1,27 +1,37 @@
import path from 'path';
import { CodeListing, CodeListingMap } from "../../common/workertypes";
import { CodeListing, CodeListingMap, Segment } from "../../common/workertypes";
export function parseObjDumpSymbolTable(symbolTable) {
const lines = symbolTable.split('\n');
const result = {};
export function parseObjDump(lst: string) {
// parse symbol map
const lines = lst.split('\n');
const symbolmap = {};
const segments : Segment[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith('00')) {
if (line.startsWith("Disassembly")) break;
if (line.startsWith('0')) {
const parts = line.split(/\s+/);
if (parts.length < 5) continue;
const symbol = parts[parts.length-1];
const address = parseInt(parts[0], 16);
result[symbol] = address;
const address = parseInt(parts[0], 16) & 0xffff; // TODO: 32-bit addresses
symbolmap[symbol] = address;
// is it a segment?
if (symbol.startsWith('__')) {
if (symbol.endsWith('_start')) {
let name = symbol.substring(2, symbol.length - 6);
let type = parts[2] == '.text' ? 'rom' : 'ram';
segments.push({ name, start: address, size: 0, type })
} else if (symbol.endsWith('_size')) {
let name = symbol.substring(2, symbol.length - 5);
let seg = segments.find(s => s.name === name);
if (seg) seg.size = address;
}
}
}
}
return result;
}
export function parseObjDumpListing(lst: string): CodeListingMap {
const lines = lst.split('\n');
const result: CodeListingMap = {};
// parse listings
const listings: CodeListingMap = {};
var lastListing : CodeListing = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith(';')) {
@@ -32,18 +42,18 @@ export function parseObjDumpListing(lst: string): CodeListingMap {
const file = path.basename(fileParts[0].trim()).split('.')[0] + '.lst';
const lineNumber = parseInt(fileParts[1], 10);
if (lineNumber > 0) {
if (!result[file]) result[file] = { lines: [], text: lst };
lastListing = result[file];
if (!listings[file]) listings[file] = { lines: [], text: lst };
lastListing = listings[file];
lastListing.lines.push({ line: lineNumber, offset: null });
}
}
} else if (lastListing && line.match(/^\s*[A-F0-9]+:.+/i)) {
const offsetIndex = line.indexOf(':');
if (offsetIndex !== -1) {
const offset = parseInt(line.substring(0, offsetIndex).trim(), 16);
const offset = parseInt(line.substring(0, offsetIndex).trim(), 16) & 0xffff; // TODO: 32-bit addresses;
lastListing.lines[lastListing.lines.length - 1].offset = offset;
}
}
}
return result;
}
return { listings, symbolmap, segments };
}
+96
View File
@@ -0,0 +1,96 @@
import { CodeListing, CodeListingMap } from "../../common/workertypes";
import { BuildStep, BuildStepResult, emglobal, execMain, fixParamsWithDefines, gatherFiles, loadNative, makeErrorMatcher, moduleInstFn, msvcErrorMatcher, populateFiles, print_fn, putWorkFile, setupFS, staleFiles } from "../workermain";
import { EmscriptenModule } from "../workermain"
function parseACMESymbolTable(text: string) {
var symbolmap = {};
var lines = text.split("\n");
for (var i = 0; i < lines.length; ++i) {
var line = lines[i].trim();
// init_text = $81b ; ?
var m = line.match(/(\w+)\s*=\s*[$]([0-9a-f]+)/i);
if (m) {
symbolmap[m[1]] = parseInt(m[2], 16);
}
}
return symbolmap;
}
function parseACMEReportFile(text: string) {
var listings : CodeListingMap = {};
var listing : CodeListing;
var lines = text.split("\n");
for (var i = 0; i < lines.length; ++i) {
var line = lines[i].trim();
// ; ******** Source: hello.acme
var m1 = line.match(/^;\s*[*]+\s*Source: (.+)$/);
if (m1) {
var file = m1[1];
listings[file] = listing = {
lines: [],
};
continue;
}
// 15 0815 201b08 jsr init_text ; write line of text
var m2 = line.match(/^(\d+)\s+([0-9a-f]+)\s+([0-9a-f]+)/i);
if (m2) {
if (listing) {
listing.lines.push({
line: parseInt(m2[1]),
offset: parseInt(m2[2], 16),
insns: m2[3],
});
}
}
}
return listings;
}
export function assembleACME(step: BuildStep): BuildStepResult {
loadNative("acme");
var errors = [];
gatherFiles(step, { mainFilePath: "main.acme" });
var binpath = step.prefix + ".bin";
var lstpath = step.prefix + ".lst";
var sympath = step.prefix + ".sym";
if (staleFiles(step, [binpath, lstpath])) {
var binout, lstout, symout;
var ACME: EmscriptenModule = emglobal.acme({
instantiateWasm: moduleInstFn('acme'),
noInitialRun: true,
print: print_fn,
printErr: msvcErrorMatcher(errors),
//printErr: makeErrorMatcher(errors, /(Error|Warning) - File (.+?), line (\d+)[^:]+: (.+)/, 3, 4, step.path, 2),
});
var FS = ACME.FS;
populateFiles(step, FS);
fixParamsWithDefines(step.path, step.params);
var args = ['--msvc', '--initmem', '0', '-o', binpath, '-r', lstpath, '-l', sympath, step.path];
if (step.params?.acmeargs) {
args.unshift.apply(args, step.params.acmeargs);
} else {
args.unshift.apply(args, ['-f', 'plain']);
}
args.unshift.apply(args, ["-D__8BITWORKSHOP__=1"]);
if (step.mainfile) {
args.unshift.apply(args, ["-D__MAIN__=1"]);
}
execMain(step, ACME, args);
if (errors.length) {
let listings: CodeListingMap = {};
return { errors, listings };
}
binout = FS.readFile(binpath, { encoding: 'binary' });
lstout = FS.readFile(lstpath, { encoding: 'utf8' });
symout = FS.readFile(sympath, { encoding: 'utf8' });
putWorkFile(binpath, binout);
putWorkFile(lstpath, lstout);
putWorkFile(sympath, symout);
return {
output: binout,
listings: parseACMEReportFile(lstout),
errors: errors,
symbolmap: parseACMESymbolTable(symout),
};
}
}
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
+6 -1
View File
@@ -252,12 +252,14 @@ var PLATFORM_PARAMS = {
libargs: [ '--lib-path', '/share/target/apple2/drv', '-D', '__EXEHDR__=0', 'apple2.lib'],
__CODE_RUN__: 16384,
code_start: 0x803,
acmeargs: ['-f', 'apple'],
},
'apple2-e': {
arch: '6502',
define: ['__APPLE2__'],
cfgfile: 'apple2.cfg',
libargs: ['apple2.lib'],
acmeargs: ['-f', 'apple'],
},
'atari8-800xl.disk': {
arch: '6502',
@@ -327,6 +329,7 @@ var PLATFORM_PARAMS = {
define: ['__CBM__', '__C64__'],
cfgfile: 'c64.cfg', // SYS 2061
libargs: ['c64.lib'],
acmeargs: ['-f', 'cbm'],
//extra_link_files: ['c64-cart.cfg'],
},
'vic20': {
@@ -334,6 +337,7 @@ var PLATFORM_PARAMS = {
define: ['__CBM__', '__VIC20__'],
cfgfile: 'vic20.cfg',
libargs: ['vic20.lib'],
acmeargs: ['-f', 'cbm'],
//extra_link_files: ['c64-cart.cfg'],
},
'kim1': {
@@ -1135,10 +1139,11 @@ import * as x86 from './tools/x86'
import * as arm from './tools/arm'
import * as ecs from './tools/ecs'
import * as remote from './tools/remote'
import * as acme from './tools/acme'
var TOOLS = {
'dasm': dasm.assembleDASM,
//'acme': assembleACME,
'acme': acme.assembleACME,
'cc65': cc65.compileCC65,
'ca65': cc65.assembleCA65,
'ld65': cc65.linkLD65,