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:
+137
-131
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 + ")");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() }; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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?
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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
Executable
BIN
Binary file not shown.
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user