diff --git a/src/ide/pixeleditor.ts b/src/ide/pixeleditor.ts index 037f6f29..fc8d6420 100644 --- a/src/ide/pixeleditor.ts +++ b/src/ide/pixeleditor.ts @@ -837,7 +837,7 @@ export class CharmapEditor extends PixNode { chooser.width = this.fmt.w || 1; chooser.height = this.fmt.h || 1; chooser.recreate(agrid, (index, viewer) => { - var yscale = Math.ceil(192 / this.fmt.w); + var yscale = Math.ceil(256 / this.fmt.w); // TODO: variable scale? var xscale = yscale * (this.fmt.aspect || 1.0); var editview = this.createEditor(aeditor, viewer, xscale, yscale); this.context.setCurrentEditor(aeditor, $(viewer.canvas), this); diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 8a653b6e..cb821431 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -2442,8 +2442,9 @@ export function getPlatformAndRepo() { qs.repo = repo_id; if (repo.platform_id && !qs.platform) qs.platform = platform_id = repo.platform_id; - if (!qs.file && repo.mainPath) + if (repo.mainPath && !qs.file) qs.file = repo.mainPath; + // TODO: update repo definition if new main file compiles successfully //requestPersistPermission(true, true); } } else { diff --git a/src/platform/vcs.ts b/src/platform/vcs.ts index 91c6e0d2..d325f682 100644 --- a/src/platform/vcs.ts +++ b/src/platform/vcs.ts @@ -60,7 +60,9 @@ function getToolForFilename_vcs(fn: string) { if (fn.endsWith(".wiz")) return "wiz"; if (fn.endsWith(".bb") || fn.endsWith(".bas")) return "bataribasic"; if (fn.endsWith(".ca65")) return "ca65"; + //if (fn.endsWith(".inc")) return "ca65"; if (fn.endsWith(".c")) return "cc65"; + //if (fn.endsWith(".h")) return "cc65"; if (fn.endsWith(".ecs")) return "ecs"; return "dasm"; } @@ -222,11 +224,13 @@ class VCSPlatform extends BasePlatform { return state; } fixState(state) { - var ofs = (state.ca && state.ca.bo) || 0; - if (state.ca && state.ca.fo && (state.c.PC & 0xfff) >= 2048) - ofs = state.ca.fo; // 3E/3F fixed-slice formats - // TODO: for batari BASIC - state.c.EPC = state.c.PC + ofs; // EPC = effective PC for ROM + // TODO: DASM listing prevents us from using RORG offset + // TODO: how to handle 1000/3000/etc vs overlapping addresses? + if (state.ca.f != '3E' && state.ca.f != '3F') { + var ofs = (state.ca && state.ca.bo) || 0; + // TODO: for batari BASIC + state.c.EPC = state.c.PC + ofs; // EPC = effective PC for ROM + } } loadState(state) { return Javatari.room.console.loadState(state); @@ -294,7 +298,8 @@ class VCSPlatform extends BasePlatform { } } bankSwitchStateToString(state) { - return (state.ca && state.ca.bo !== undefined) ? "BankOffset "+hex(state.ca.bo,4)+"\n" : ""; + if (state.ca?.ro >= 0) return "RAMOffset "+hex(state.ca.ro,4)+"\n"; + return (state.ca?.bo >= 0) ? "BankOffset "+hex(state.ca.bo,4)+"\n" : ""; } piaStateToLongString(p) { return "Timer " + p.t + "/" + p.c + "\nINTIM $" + hex(p.IT,2) + " (" + p.IT + ")\nINSTAT $" + hex(p.IS,2) + "\n"; diff --git a/src/test/testanalysis.ts b/src/test/testanalysis.ts new file mode 100644 index 00000000..29dc8e1e --- /dev/null +++ b/src/test/testanalysis.ts @@ -0,0 +1,60 @@ +import { describe } from "mocha"; +import { OpcodeMetadata, Platform } from "../common/baseplatform"; +import { CodeAnalyzer_vcs } from "../common/analysis"; +import { MOS6502 } from "../common/cpu/MOS6502"; +import assert from "assert"; + +class Test6502Platform implements Platform { + cpu = new MOS6502(); + ram = new Uint8Array(65536); + + start(): void | Promise { + throw new Error("Method not implemented."); + } + reset(): void { + throw new Error("Method not implemented."); + } + isRunning(): boolean { + throw new Error("Method not implemented."); + } + getToolForFilename(s: string): string { + throw new Error("Method not implemented."); + } + getDefaultExtension(): string { + throw new Error("Method not implemented."); + } + pause(): void { + throw new Error("Method not implemented."); + } + resume(): void { + throw new Error("Method not implemented."); + } + loadROM(title: string, rom: any) { + throw new Error("Method not implemented."); + } + getOpcodeMetadata?(opcode: number, offset: number): OpcodeMetadata { + return this.cpu.cpu.getOpcodeMetadata(opcode, offset); + } + getOriginPC(): number { + return 0; + } + readAddress?(addr:number) : number { + return this.ram[addr] || 0; + } +} + +describe('6502 analysis', function () { + it('Should analyze 6502', function () { + let platform = new Test6502Platform(); + platform.ram.set([0xea,0x85,0x02,0xa9,0x60,0x20,0x04,0x00,0xea,0x4c,0x01,0x00]); + let analysis = new CodeAnalyzer_vcs(platform); + analysis.showLoopTimingForPC(0x0); + console.log(analysis); + assert.equal(analysis.pc2minclocks[0x0], 304); + assert.equal(analysis.pc2maxclocks[0x0], 304); + assert.equal(analysis.pc2minclocks[0x1], 19); + assert.equal(analysis.pc2maxclocks[0x0], 304); + assert.equal(analysis.pc2minclocks[0x3], 0); + assert.equal(analysis.pc2maxclocks[0x3], 0); + }); +}); diff --git a/src/worker/lib/vcs/atari2600.cfg b/src/worker/lib/vcs/atari2600.cfg index 8439b4f1..e2acccda 100644 --- a/src/worker/lib/vcs/atari2600.cfg +++ b/src/worker/lib/vcs/atari2600.cfg @@ -1,20 +1,41 @@ # Linker config file for targeting the Atari 2600. # From http://wiki.cc65.org/doku.php?id=cc65:atari_2600 +# Modified for TigerVision (3E) mapper MEMORY { - RAM: start = $80, size=$80, type = rw, define = yes; - ROM: start = $F000, size=$1000, type = ro, file = %O, define = yes; + RAM: start = $80, size=$70, type = rw, define = yes; TIA: start = $00, size=$40, type = rw, define = yes; RIOT: start = $280, size=$20, type = rw, define = yes; + ROM0: start = $1000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=0; + ROM1: start = $3000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=1; + ROM2: start = $5000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=2; + ROM3: start = $7000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=3; + ROM4: start = $9000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=4; + ROM5: start = $b000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=5; + ROM6: start = $d000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=6; + ROM7: start = $f000, size=$800, type=ro, file=%O, define=yes, fill=yes, bank=7; + RAM0: start = $f000, size=$400, type=rw, define=yes; + PERM: start = $f800, size=$800, type=ro, file=%O, define=yes, fill=yes; } SEGMENTS { - RODATA: load=ROM, type=ro, align=$100; - STARTUP: load=ROM, type=ro, optional=yes; - CODE: load=ROM, type=ro, define=yes; - DATA: load=ROM, run=RAM, type=rw, define=yes; + ROM0: load=ROM0, type=ro, align=$100, optional=yes; + ROM1: load=ROM1, type=ro, align=$100, optional=yes; + ROM2: load=ROM2, type=ro, align=$100, optional=yes; + ROM3: load=ROM3, type=ro, align=$100, optional=yes; + ROM4: load=ROM4, type=ro, align=$100, optional=yes; + ROM5: load=ROM5, type=ro, align=$100, optional=yes; + ROM6: load=ROM6, type=ro, align=$100, optional=yes; + ROM7: load=ROM7, type=ro, align=$100, optional=yes; + + RODATA: load=PERM, type=ro, align=$100; + STARTUP: load=PERM, type=ro, optional=yes; + CODE: load=PERM, type=ro, define=yes; + DATA: load=ROM0, run=RAM, type=rw, define=yes; BSS: load=RAM, type=bss, define=yes; - VECTORS: load=ROM, type=ro, start=$FFFA; + VECTORS: load=PERM, type=ro, start=$FFFA; + XDATA: load=ROM0, run=RAM0, type=rw, define=yes, optional=yes; + ZEROPAGE: load=RAM, type=zp; TIA: load=TIA, type=rw, define = yes, optional = yes; RIOT: load=RIOT, type=rw, define = yes, optional = yes; diff --git a/src/worker/tools/cc65.ts b/src/worker/tools/cc65.ts index 7b2f94ab..a04af488 100644 --- a/src/worker/tools/cc65.ts +++ b/src/worker/tools/cc65.ts @@ -62,30 +62,41 @@ function parseCA65Listing(asmfn: string, code: string, symbols, segments, params insns: null }); } - var linem = insnLineMatch.exec(line); - var topfile = linem && linem[3] == '1'; - if (topfile) linenum++; - if (topfile && linem[1]) { - var offset = parseInt(linem[1], 16); - var insns = linem[4].trim(); - if (insns.length) { - //console.log(curpath, linenum, offset, segofs, insns); - if (!dbg) { - lines.push({ - path: curpath, - line: linenum, - offset: offset + segofs, - insns: insns, - iscode: true // TODO: can't really tell unless we parse it - }); - } - } else { - var sym = linem[5]; - if (sym.endsWith(':') && !sym.startsWith('@')) { - var symofs = symbols[sym.substring(0, sym.length - 1)]; - if (typeof symofs === 'number') { - segofs = symofs - offset; - //console.log(sym, segofs, symofs, '-', offset); + let linem = insnLineMatch.exec(line); + let topfile = linem && linem[3] == '1'; + if (topfile) { + let insns = linem[4]?.trim() || ''; + // skip extra insns for macro expansions + if (!(insns != '' && linem[5] == '')) { + linenum++; + } + if (linem[1]) { + var offset = parseInt(linem[1], 16); + if (insns.length) { + //console.log(dbg, curpath, linenum, offset, segofs, insns); + if (!dbg) { + lines.push({ + path: curpath, + line: linenum, + offset: offset + segofs, + insns: insns, + iscode: true // TODO: can't really tell unless we parse it + }); + } + } else { + var sym = null; + var label = linem[5]; + if (label?.endsWith(':')) { + sym = label.substring(0, label.length-1); + } else if (label?.toLowerCase().startsWith('.proc')) { + sym = label.split(' ')[1]; + } + if (sym && !sym.startsWith('@')) { + var symofs = symbols[sym]; + if (typeof symofs === 'number') { + segofs = symofs - offset; + //console.log(sym, segofs, symofs, '-', offset); + } } } } @@ -224,7 +235,7 @@ export function linkLD65(step: BuildStep): BuildStepResult { lstout = lstout.split('\n\n')[1] || lstout; // remove header putWorkFile(fn, lstout); //const asmpath = fn.replace(/\.lst$/, '.ca65'); // TODO! could be .s - let isECS = step.debuginfo?.entities != null; // TODO + let isECS = step.debuginfo?.systems?.Init != null; // TODO if (isECS) { var asmlines = []; var srclines = parseCA65Listing(fn, lstout, symbolmap, segments, params, true, listings);