mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-06-12 03:29:31 +00:00
analysis.ts for 6502 cycle counting (vcs, nes)
This commit is contained in:
parent
9d3e658a7b
commit
662f8a057d
|
@ -116,7 +116,7 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
||||||
<button id="dbg_fastest" type="submit" title="Faster"><span class="glyphicon glyphicon-fast-forward" aria-hidden="true"></span></button>
|
<button id="dbg_fastest" type="submit" title="Faster"><span class="glyphicon glyphicon-fast-forward" aria-hidden="true"></span></button>
|
||||||
</span>
|
</span>
|
||||||
<span class="btn_group view_group" id="extra_bar">
|
<span class="btn_group view_group" id="extra_bar">
|
||||||
<button id="dbg_timing" type="submit" title="See Timing" style="display:none"><span class="glyphicon glyphicon-time" aria-hidden="true"></span></button>
|
<button id="dbg_timing" type="submit" title="Analyze CPU Timing" style="display:none"><span class="glyphicon glyphicon-time" aria-hidden="true"></span></button>
|
||||||
<button id="dbg_disasm" type="submit" title="Show Disassembly" style="display:none"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></button>
|
<button id="dbg_disasm" type="submit" title="Show Disassembly" style="display:none"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></button>
|
||||||
<button id="dbg_memory" type="submit" title="Show Memory" style="display:none"><span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span></button>
|
<button id="dbg_memory" type="submit" title="Show Memory" style="display:none"><span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span></button>
|
||||||
<button id="dbg_profile" type="submit" title="Show Profile" style="display:none"><span class="glyphicon glyphicon-stats" aria-hidden="true"></span></button>
|
<button id="dbg_profile" type="submit" title="Show Profile" style="display:none"><span class="glyphicon glyphicon-stats" aria-hidden="true"></span></button>
|
||||||
|
@ -245,12 +245,13 @@ function require(modname) {
|
||||||
<script src="tss/js/tss/AudioLooper.js"></script>
|
<script src="tss/js/tss/AudioLooper.js"></script>
|
||||||
<script src="tss/js/Log.js"></script>
|
<script src="tss/js/Log.js"></script>
|
||||||
|
|
||||||
|
<script src="gen/util.js"></script>
|
||||||
<script src="gen/store.js"></script>
|
<script src="gen/store.js"></script>
|
||||||
<script src="src/vlist.js"></script>
|
<script src="src/vlist.js"></script>
|
||||||
<script src="gen/emu.js"></script>
|
<script src="gen/emu.js"></script>
|
||||||
<script src="gen/baseplatform.js"></script>
|
<script src="gen/baseplatform.js"></script>
|
||||||
|
<script src="gen/analysis.js"></script>
|
||||||
<script src="gen/audio.js"></script>
|
<script src="gen/audio.js"></script>
|
||||||
<script src="gen/util.js"></script>
|
|
||||||
<script src="gen/cpu/disasm6502.js"></script>
|
<script src="gen/cpu/disasm6502.js"></script>
|
||||||
<script src="gen/workertypes.js"></script>
|
<script src="gen/workertypes.js"></script>
|
||||||
<script src="gen/project.js"></script>
|
<script src="gen/project.js"></script>
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
|
||||||
include "nesdefs.asm"
|
include "nesdefs.asm"
|
||||||
|
|
||||||
;;;;; VARIABLES
|
;;;;; ZERO-PAGE VARIABLES
|
||||||
|
|
||||||
seg.u ZEROPAGE
|
seg.u ZEROPAGE
|
||||||
org $0
|
org $0
|
||||||
|
|
||||||
|
;;;;; OTHER VARIABLES
|
||||||
|
|
||||||
seg.u RAM
|
seg.u RAM
|
||||||
org $300
|
org $300
|
||||||
|
@ -34,7 +35,7 @@ Start:
|
||||||
sta PPU_SCROLL ; scroll = $0000
|
sta PPU_SCROLL ; scroll = $0000
|
||||||
lda #CTRL_NMI
|
lda #CTRL_NMI
|
||||||
sta PPU_CTRL ; enable NMI
|
sta PPU_CTRL ; enable NMI
|
||||||
lda #MASK_BG
|
lda #MASK_BG|MASK_SPR
|
||||||
sta PPU_MASK ; enable rendering
|
sta PPU_MASK ; enable rendering
|
||||||
.endless
|
.endless
|
||||||
jmp .endless ; endless loop
|
jmp .endless ; endless loop
|
||||||
|
@ -70,6 +71,16 @@ SetPalette: subroutine
|
||||||
bne .loop
|
bne .loop
|
||||||
rts
|
rts
|
||||||
|
|
||||||
|
; set sprite 0
|
||||||
|
SetSprite0: subroutine
|
||||||
|
sta $200 ;y
|
||||||
|
lda #1 ;code
|
||||||
|
sta $201
|
||||||
|
lda #0 ;flags
|
||||||
|
sta $202
|
||||||
|
lda #8 ;xpos
|
||||||
|
sta $203
|
||||||
|
rts
|
||||||
|
|
||||||
;;;;; COMMON SUBROUTINES
|
;;;;; COMMON SUBROUTINES
|
||||||
|
|
||||||
|
@ -79,6 +90,17 @@ SetPalette: subroutine
|
||||||
|
|
||||||
NMIHandler: subroutine
|
NMIHandler: subroutine
|
||||||
SAVE_REGS
|
SAVE_REGS
|
||||||
|
lda #112
|
||||||
|
jsr SetSprite0
|
||||||
|
; load sprites
|
||||||
|
lda #$02
|
||||||
|
sta PPU_OAM_DMA
|
||||||
|
; wait for sprite 0
|
||||||
|
.wait0 bit PPU_STATUS
|
||||||
|
bvs .wait0
|
||||||
|
.wait1 bit PPU_STATUS
|
||||||
|
bvc .wait1
|
||||||
|
; alter horiz. scroll position for each scanline
|
||||||
ldy #0
|
ldy #0
|
||||||
.loop
|
.loop
|
||||||
tya
|
tya
|
||||||
|
@ -91,10 +113,9 @@ NMIHandler: subroutine
|
||||||
sta PPU_SCROLL ; horiz byte
|
sta PPU_SCROLL ; horiz byte
|
||||||
lda #0
|
lda #0
|
||||||
sta PPU_SCROLL ; vert byte
|
sta PPU_SCROLL ; vert byte
|
||||||
ldx #15
|
REPEAT 25
|
||||||
.delay
|
bit $0000
|
||||||
dex
|
REPEND
|
||||||
bne .delay
|
|
||||||
iny
|
iny
|
||||||
cpy #224
|
cpy #224
|
||||||
bne .loop
|
bne .loop
|
||||||
|
|
196
src/analysis.ts
Normal file
196
src/analysis.ts
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
|
||||||
|
import { hex, byte2signed } from "./util";
|
||||||
|
import { Platform } from "./baseplatform";
|
||||||
|
|
||||||
|
export interface CodeAnalyzer {
|
||||||
|
showLoopTimingForPC(pc:number);
|
||||||
|
pc2minclocks : {[key:number]:number};
|
||||||
|
pc2maxclocks : {[key:number]:number};
|
||||||
|
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}]
|
||||||
|
];
|
||||||
|
|
||||||
|
function constraintEquals(a,b) {
|
||||||
|
if (a == null || b == null)
|
||||||
|
return null;
|
||||||
|
for (var n in a) {
|
||||||
|
if (b[n] !== 'undefined')
|
||||||
|
return a[n] == b[n];
|
||||||
|
}
|
||||||
|
for (var n in b) {
|
||||||
|
if (a[n] !== 'undefined')
|
||||||
|
return a[n] == b[n];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CodeAnalyzer6502 implements CodeAnalyzer {
|
||||||
|
pc2minclocks = {};
|
||||||
|
pc2maxclocks = {};
|
||||||
|
START_CLOCKS : number;
|
||||||
|
MAX_CLOCKS : number;
|
||||||
|
WRAP_CLOCKS : boolean;
|
||||||
|
jsrresult = {};
|
||||||
|
platform : Platform;
|
||||||
|
|
||||||
|
constructor(platform : Platform) {
|
||||||
|
this.platform = platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
getClockCountsAtPC(pc) {
|
||||||
|
var opcode = this.platform.readAddress(pc);
|
||||||
|
var meta = this.platform.getOpcodeMetadata(opcode, pc);
|
||||||
|
return meta; // minCycles, maxCycles
|
||||||
|
}
|
||||||
|
|
||||||
|
traceInstructions(pc:number, minclocks:number, maxclocks:number, subaddr:number, constraints) {
|
||||||
|
//console.log("trace", hex(pc), minclocks, maxclocks);
|
||||||
|
if (!minclocks) minclocks = 0;
|
||||||
|
if (!maxclocks) maxclocks = 0;
|
||||||
|
if (!constraints) constraints = {};
|
||||||
|
var modified = true;
|
||||||
|
var abort = false;
|
||||||
|
for (var i=0; i<1000 && modified && !abort; i++) {
|
||||||
|
modified = false;
|
||||||
|
var meta = this.getClockCountsAtPC(pc);
|
||||||
|
var lob = this.platform.readAddress(pc+1);
|
||||||
|
var hib = this.platform.readAddress(pc+2);
|
||||||
|
var addr = lob + (hib << 8);
|
||||||
|
var pc0 = pc;
|
||||||
|
if (!this.pc2minclocks[pc0] || minclocks < this.pc2minclocks[pc0]) {
|
||||||
|
this.pc2minclocks[pc0] = minclocks;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
if (!this.pc2maxclocks[pc0] || maxclocks > this.pc2maxclocks[pc0]) {
|
||||||
|
this.pc2maxclocks[pc0] = maxclocks;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
//console.log(hex(pc),minclocks,maxclocks,meta);
|
||||||
|
if (!meta.insnlength) {
|
||||||
|
console.log("Illegal instruction!", hex(pc), hex(meta.opcode), meta);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pc += meta.insnlength;
|
||||||
|
var oldconstraints = constraints;
|
||||||
|
constraints = null;
|
||||||
|
// TODO: if jump to zero-page, maybe assume RTS?
|
||||||
|
switch (meta.opcode) {
|
||||||
|
/*
|
||||||
|
case 0xb9: // TODO: hack for zero page,y
|
||||||
|
if (addr < 0x100)
|
||||||
|
meta.maxCycles -= 1;
|
||||||
|
break;
|
||||||
|
*/
|
||||||
|
// TODO: don't do in NES
|
||||||
|
case 0x85:
|
||||||
|
if (lob == 0x2) { // STA WSYNC
|
||||||
|
minclocks = maxclocks = 0;
|
||||||
|
meta.minCycles = meta.maxCycles = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x20: // JSR
|
||||||
|
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));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x4c: // JMP
|
||||||
|
pc = addr; // TODO: make sure in ROM space
|
||||||
|
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]);
|
||||||
|
if (cons0 !== false) {
|
||||||
|
this.traceInstructions(newpc, minclocks+meta.maxCycles, maxclocks+meta.maxCycles, subaddr, cons[0]);
|
||||||
|
}
|
||||||
|
if (cons1 === false) {
|
||||||
|
console.log("abort", 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;
|
||||||
|
}
|
||||||
|
minclocks = Math.min(this.MAX_CLOCKS, minclocks + meta.minCycles);
|
||||||
|
maxclocks = Math.min(this.MAX_CLOCKS, maxclocks + meta.maxCycles);
|
||||||
|
if (this.WRAP_CLOCKS && maxclocks >= this.MAX_CLOCKS) {
|
||||||
|
minclocks = minclocks % this.MAX_CLOCKS;
|
||||||
|
maxclocks = maxclocks % this.MAX_CLOCKS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoopTimingForPC(pc:number) {
|
||||||
|
this.pc2minclocks = {};
|
||||||
|
this.pc2maxclocks = {};
|
||||||
|
this.jsrresult = {};
|
||||||
|
// recurse through all traces
|
||||||
|
this.traceInstructions(pc | this.platform.getOriginPC(), this.START_CLOCKS, this.MAX_CLOCKS, 0, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 76 cycles * 2 (support two scanline kernels)
|
||||||
|
export class CodeAnalyzer_vcs extends CodeAnalyzer6502 {
|
||||||
|
constructor(platform : Platform) {
|
||||||
|
super(platform);
|
||||||
|
this.MAX_CLOCKS = this.START_CLOCKS = 76*2;
|
||||||
|
this.WRAP_CLOCKS = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
super(platform);
|
||||||
|
this.MAX_CLOCKS = 114; // ~341/3
|
||||||
|
this.START_CLOCKS = 0;
|
||||||
|
this.WRAP_CLOCKS = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
import { RAM, RasterVideo, dumpRAM, dumpStackToString } from "./emu";
|
import { RAM, RasterVideo, dumpRAM, dumpStackToString } from "./emu";
|
||||||
import { hex } from "./util";
|
import { hex } from "./util";
|
||||||
|
import { CodeAnalyzer } from "./analysis";
|
||||||
|
|
||||||
declare var Z80_fast, jt, CPU6809;
|
declare var Z80_fast, jt, CPU6809;
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ export interface Platform {
|
||||||
getDebugCallback?() : any; // TODO
|
getDebugCallback?() : any; // TODO
|
||||||
getSP?() : number;
|
getSP?() : number;
|
||||||
getOriginPC?() : number;
|
getOriginPC?() : number;
|
||||||
|
newCodeAnalyzer() : CodeAnalyzer;
|
||||||
|
|
||||||
getDebugCategories() : string[];
|
getDebugCategories() : string[];
|
||||||
getDebugInfo(category:string, state:EmuState) : string;
|
getDebugInfo(category:string, state:EmuState) : string;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502 } from "../baseplatform";
|
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502 } from "../baseplatform";
|
||||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, dumpStackToString } from "../emu";
|
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, dumpStackToString } from "../emu";
|
||||||
import { hex, lpad, lzgmini } from "../util";
|
import { hex, lpad, lzgmini } from "../util";
|
||||||
|
import { CodeAnalyzer_nes } from "../analysis";
|
||||||
|
|
||||||
declare var jsnes : any;
|
declare var jsnes : any;
|
||||||
|
|
||||||
|
@ -131,7 +132,12 @@ var JSNESPlatform = function(mainElement) {
|
||||||
nes.loadROM(romstr);
|
nes.loadROM(romstr);
|
||||||
frameindex = 0;
|
frameindex = 0;
|
||||||
}
|
}
|
||||||
|
this.newCodeAnalyzer = function() {
|
||||||
|
return new CodeAnalyzer_nes(this);
|
||||||
|
}
|
||||||
|
this.getOriginPC = function() { // TODO: is actually NMI
|
||||||
|
return (this.readAddress(0xfffa) | (this.readAddress(0xfffb) << 8)) & 0xffff;
|
||||||
|
}
|
||||||
this.getOpcodeMetadata = getOpcodeMetadata_6502;
|
this.getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||||
this.getDefaultExtension = function() { return ".c"; };
|
this.getDefaultExtension = function() { return ".c"; };
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { Platform, cpuStateToLongString_6502, BaseMAMEPlatform } from "../baseplatform";
|
import { Platform, cpuStateToLongString_6502, BaseMAMEPlatform } from "../baseplatform";
|
||||||
import { PLATFORMS, RAM, newAddressDecoder, dumpRAM } from "../emu";
|
import { PLATFORMS, RAM, newAddressDecoder, dumpRAM } from "../emu";
|
||||||
import { hex, lpad, tobin, byte2signed } from "../util";
|
import { hex, lpad, tobin, byte2signed } from "../util";
|
||||||
|
import { CodeAnalyzer_vcs } from "../analysis";
|
||||||
|
|
||||||
declare var platform : Platform; // global platform object
|
declare var platform : Platform; // global platform object
|
||||||
declare var Javatari : any;
|
declare var Javatari : any;
|
||||||
|
@ -99,6 +100,9 @@ var VCSPlatform = function() {
|
||||||
this.getOriginPC = function() {
|
this.getOriginPC = function() {
|
||||||
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
||||||
}
|
}
|
||||||
|
this.newCodeAnalyzer = function() {
|
||||||
|
return new CodeAnalyzer_vcs(this);
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
this.saveState = function() {
|
this.saveState = function() {
|
||||||
return Javatari.room.console.saveState(); // TODO
|
return Javatari.room.console.saveState(); // TODO
|
||||||
|
@ -184,175 +188,6 @@ function nonegstr(n) {
|
||||||
return n < 0 ? "-" : n.toString();
|
return n < 0 ? "-" : n.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// VCS TIMING ANALYSIS
|
|
||||||
|
|
||||||
var pc2minclocks = {};
|
|
||||||
var pc2maxclocks = {};
|
|
||||||
var jsrresult = {};
|
|
||||||
var MAX_CLOCKS = 76*2;
|
|
||||||
|
|
||||||
// [taken, not taken]
|
|
||||||
var 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}]
|
|
||||||
];
|
|
||||||
|
|
||||||
function constraintEquals(a,b) {
|
|
||||||
if (a == null || b == null)
|
|
||||||
return null;
|
|
||||||
for (var n in a) {
|
|
||||||
if (b[n] !== 'undefined')
|
|
||||||
return a[n] == b[n];
|
|
||||||
}
|
|
||||||
for (var n in b) {
|
|
||||||
if (a[n] !== 'undefined')
|
|
||||||
return a[n] == b[n];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getClockCountsAtPC(pc) {
|
|
||||||
var opcode = platform.readAddress(pc);
|
|
||||||
var meta = platform.getOpcodeMetadata(opcode, pc);
|
|
||||||
return meta; // minCycles, maxCycles
|
|
||||||
}
|
|
||||||
|
|
||||||
function _traceInstructions(pc:number, minclocks:number, maxclocks:number, subaddr:number, constraints) {
|
|
||||||
//console.log("trace", hex(pc), minclocks, maxclocks);
|
|
||||||
if (!minclocks) minclocks = 0;
|
|
||||||
if (!maxclocks) maxclocks = 0;
|
|
||||||
if (!constraints) constraints = {};
|
|
||||||
var modified = true;
|
|
||||||
var abort = false;
|
|
||||||
for (var i=0; i<1000 && modified && !abort; i++) {
|
|
||||||
modified = false;
|
|
||||||
var meta = getClockCountsAtPC(pc);
|
|
||||||
var lob = platform.readAddress(pc+1);
|
|
||||||
var hib = platform.readAddress(pc+2);
|
|
||||||
var addr = lob + (hib << 8);
|
|
||||||
var pc0 = pc;
|
|
||||||
if (!pc2minclocks[pc0] || minclocks < pc2minclocks[pc0]) {
|
|
||||||
pc2minclocks[pc0] = minclocks;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
if (!pc2maxclocks[pc0] || maxclocks > pc2maxclocks[pc0]) {
|
|
||||||
pc2maxclocks[pc0] = maxclocks;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
//console.log(hex(pc),minclocks,maxclocks,meta);
|
|
||||||
if (!meta.insnlength) {
|
|
||||||
console.log("Illegal instruction!", hex(pc), hex(meta.opcode), meta);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pc += meta.insnlength;
|
|
||||||
var oldconstraints = constraints;
|
|
||||||
constraints = null;
|
|
||||||
// TODO: if jump to zero-page, maybe assume RTS?
|
|
||||||
switch (meta.opcode) {
|
|
||||||
/*
|
|
||||||
case 0xb9: // TODO: hack for zero page,y
|
|
||||||
if (addr < 0x100)
|
|
||||||
meta.maxCycles -= 1;
|
|
||||||
break;
|
|
||||||
*/
|
|
||||||
case 0x85:
|
|
||||||
if (lob == 0x2) { // STA WSYNC
|
|
||||||
minclocks = maxclocks = 0;
|
|
||||||
meta.minCycles = meta.maxCycles = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x20: // JSR
|
|
||||||
_traceInstructions(addr, minclocks, maxclocks, addr, constraints);
|
|
||||||
var result = jsrresult[addr];
|
|
||||||
if (result) {
|
|
||||||
minclocks = result.minclocks;
|
|
||||||
maxclocks = result.maxclocks;
|
|
||||||
} else {
|
|
||||||
console.log("No JSR result!", hex(pc), hex(addr));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x4c: // JMP
|
|
||||||
pc = addr; // TODO: make sure in ROM space
|
|
||||||
break;
|
|
||||||
case 0x60: // RTS
|
|
||||||
if (subaddr) { // TODO: 0 doesn't work
|
|
||||||
// TODO: combine with previous result
|
|
||||||
var result = jsrresult[subaddr];
|
|
||||||
if (!result) {
|
|
||||||
result = {minclocks:minclocks, maxclocks:maxclocks};
|
|
||||||
} else {
|
|
||||||
result = {
|
|
||||||
minclocks:Math.min(minclocks,result.minclocks),
|
|
||||||
maxclocks:Math.max(maxclocks,result.maxclocks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsrresult[subaddr] = result;
|
|
||||||
console.log("RTS", hex(pc), hex(subaddr), 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]);
|
|
||||||
if (cons0 !== false) {
|
|
||||||
_traceInstructions(newpc, minclocks+meta.maxCycles, maxclocks+meta.maxCycles, subaddr, cons[0]);
|
|
||||||
}
|
|
||||||
if (cons1 === false) {
|
|
||||||
console.log("abort", 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;
|
|
||||||
}
|
|
||||||
// TODO: wraparound?
|
|
||||||
minclocks = Math.min(MAX_CLOCKS, minclocks + meta.minCycles);
|
|
||||||
maxclocks = Math.min(MAX_CLOCKS, maxclocks + meta.maxCycles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLoopTimingForPC(pc, sourcefile, ed) {
|
|
||||||
pc2minclocks = {};
|
|
||||||
pc2maxclocks = {};
|
|
||||||
jsrresult = {};
|
|
||||||
// recurse through all traces
|
|
||||||
_traceInstructions(pc | platform.getOriginPC(), MAX_CLOCKS, MAX_CLOCKS, 0, {});
|
|
||||||
ed.editor.clearGutter("gutter-bytes");
|
|
||||||
// show the lines
|
|
||||||
for (var line in sourcefile.line2offset) {
|
|
||||||
var pc = sourcefile.line2offset[line];
|
|
||||||
var minclocks = pc2minclocks[pc];
|
|
||||||
var maxclocks = pc2maxclocks[pc];
|
|
||||||
if (minclocks>=0 && maxclocks>=0) {
|
|
||||||
var s;
|
|
||||||
if (maxclocks == minclocks)
|
|
||||||
s = minclocks + "";
|
|
||||||
else
|
|
||||||
s = minclocks + "-" + maxclocks;
|
|
||||||
if (maxclocks == MAX_CLOCKS)
|
|
||||||
s += "+";
|
|
||||||
ed.setGutterBytes(line, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////
|
///////////////
|
||||||
|
|
||||||
var VCSMAMEPlatform = function(mainElement) {
|
var VCSMAMEPlatform = function(mainElement) {
|
||||||
|
|
|
@ -68,7 +68,7 @@ localforage.defineDriver(OldFileStoreDriver);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// copy localStorage to new driver
|
// copy localStorage to new driver
|
||||||
function copyFromOldStorageFormat(platformid:string, newstore, callback:()=>void) {
|
function copyFromOldStorageFormat(platformid:string, newstore, conversioncallback:()=>void) {
|
||||||
var alreadyMigratedKey = "__migrated_" + platformid;
|
var alreadyMigratedKey = "__migrated_" + platformid;
|
||||||
//localStorage.removeItem(alreadyMigratedKey);
|
//localStorage.removeItem(alreadyMigratedKey);
|
||||||
if (localStorage.getItem(alreadyMigratedKey))
|
if (localStorage.getItem(alreadyMigratedKey))
|
||||||
|
@ -97,8 +97,8 @@ function copyFromOldStorageFormat(platformid:string, newstore, callback:()=>void
|
||||||
console.log("Migrated " + len + " local files to new data store");
|
console.log("Migrated " + len + " local files to new data store");
|
||||||
if (len) {
|
if (len) {
|
||||||
localStorage.setItem(alreadyMigratedKey, 'true');
|
localStorage.setItem(alreadyMigratedKey, 'true');
|
||||||
if (callback)
|
if (conversioncallback)
|
||||||
callback();
|
conversioncallback();
|
||||||
else
|
else
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
33
src/ui.ts
33
src/ui.ts
|
@ -1,6 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { getFilenameForPath, getFilenamePrefix, highlightDifferences, invertMap } from "./util";
|
|
||||||
|
|
||||||
// 8bitworkshop IDE user interface
|
// 8bitworkshop IDE user interface
|
||||||
|
|
||||||
|
@ -12,11 +11,11 @@ import { ProjectWindows } from "./windows";
|
||||||
import { Platform, Preset } from "./baseplatform";
|
import { Platform, Preset } from "./baseplatform";
|
||||||
import { PLATFORMS } from "./emu";
|
import { PLATFORMS } from "./emu";
|
||||||
import * as Views from "./views";
|
import * as Views from "./views";
|
||||||
|
import { createNewPersistentStore } from "./store";
|
||||||
|
import { getFilenameForPath, getFilenamePrefix, highlightDifferences, invertMap } from "./util";
|
||||||
|
|
||||||
// external libs (TODO)
|
// external libs (TODO)
|
||||||
declare var Octokat, ga, Tour, GIF, saveAs;
|
declare var Octokat, ga, Tour, GIF, saveAs;
|
||||||
declare function createNewPersistentStore(platform_id : string);
|
|
||||||
declare function showLoopTimingForPC(pc:number, sourcefile:SourceFile, wnd:Views.ProjectView);
|
|
||||||
|
|
||||||
// make sure VCS doesn't start
|
// make sure VCS doesn't start
|
||||||
if (window['Javatari']) window['Javatari'].AUTO_START = false;
|
if (window['Javatari']) window['Javatari'].AUTO_START = false;
|
||||||
|
@ -669,8 +668,10 @@ function _openBitmapEditor() {
|
||||||
function traceTiming() {
|
function traceTiming() {
|
||||||
projectWindows.refresh(false);
|
projectWindows.refresh(false);
|
||||||
var wnd = projectWindows.getActive();
|
var wnd = projectWindows.getActive();
|
||||||
if (wnd.getSourceFile && wnd.setGutterBytes) { // is editor active?
|
if (wnd.getSourceFile && wnd.setTimingResult) { // is editor active?
|
||||||
showLoopTimingForPC(0, wnd.getSourceFile(), wnd);
|
var analyzer = platform.newCodeAnalyzer();
|
||||||
|
analyzer.showLoopTimingForPC(0);
|
||||||
|
wnd.setTimingResult(analyzer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -689,7 +690,7 @@ function setupDebugControls(){
|
||||||
$("#dbg_tovsync").hide();
|
$("#dbg_tovsync").hide();
|
||||||
if ((platform.runEval || platform.runToPC) && platform_id != 'verilog')
|
if ((platform.runEval || platform.runToPC) && platform_id != 'verilog')
|
||||||
$("#dbg_toline").click(runToCursor).show();
|
$("#dbg_toline").click(runToCursor).show();
|
||||||
else
|
else
|
||||||
$("#dbg_toline").hide();
|
$("#dbg_toline").hide();
|
||||||
if (platform.runUntilReturn)
|
if (platform.runUntilReturn)
|
||||||
$("#dbg_stepout").click(runUntilReturn).show();
|
$("#dbg_stepout").click(runUntilReturn).show();
|
||||||
|
@ -700,7 +701,7 @@ function setupDebugControls(){
|
||||||
else
|
else
|
||||||
$("#dbg_stepback").hide();
|
$("#dbg_stepback").hide();
|
||||||
|
|
||||||
if (window['showLoopTimingForPC']) { // VCS-only (TODO: put in platform)
|
if (platform.newCodeAnalyzer) {
|
||||||
$("#dbg_timing").click(traceTiming).show();
|
$("#dbg_timing").click(traceTiming).show();
|
||||||
}
|
}
|
||||||
$("#disassembly").hide();
|
$("#disassembly").hide();
|
||||||
|
@ -823,10 +824,6 @@ function gotoNewLocation() {
|
||||||
window.location.href = "?" + $.param(qs);
|
window.location.href = "?" + $.param(qs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPlatform() {
|
|
||||||
store = createNewPersistentStore(platform_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showBookLink() {
|
function showBookLink() {
|
||||||
if (platform_id == 'vcs')
|
if (platform_id == 'vcs')
|
||||||
$("#booklink_vcs").show();
|
$("#booklink_vcs").show();
|
||||||
|
@ -860,7 +857,6 @@ function addPageFocusHandlers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPlatform() {
|
function startPlatform() {
|
||||||
initPlatform();
|
|
||||||
if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'.");
|
if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'.");
|
||||||
platform = new PLATFORMS[platform_id]($("#emulator")[0]);
|
platform = new PLATFORMS[platform_id]($("#emulator")[0]);
|
||||||
PRESETS = platform.getPresets();
|
PRESETS = platform.getPresets();
|
||||||
|
@ -893,11 +889,12 @@ function loadSharedFile(sharekey : string) {
|
||||||
var json = JSON.parse(val.description.slice(val.description.indexOf(' ')+1));
|
var json = JSON.parse(val.description.slice(val.description.indexOf(' ')+1));
|
||||||
console.log("Fetched " + newid, json);
|
console.log("Fetched " + newid, json);
|
||||||
platform_id = json['platform'];
|
platform_id = json['platform'];
|
||||||
initPlatform();
|
store = createNewPersistentStore(platform_id, () => {
|
||||||
current_project.updateFile(newid, val.files[filename].content);
|
current_project.updateFile(newid, val.files[filename].content);
|
||||||
reloadPresetNamed(newid);
|
reloadPresetNamed(newid);
|
||||||
delete qs['sharekey'];
|
delete qs['sharekey'];
|
||||||
gotoNewLocation();
|
gotoNewLocation();
|
||||||
|
});
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
alert("Error loading share file: " + err.message);
|
alert("Error loading share file: " + err.message);
|
||||||
});
|
});
|
||||||
|
@ -918,9 +915,9 @@ function startUI(loadplatform : boolean) {
|
||||||
if (qs['sharekey']) {
|
if (qs['sharekey']) {
|
||||||
loadSharedFile(qs['sharekey']);
|
loadSharedFile(qs['sharekey']);
|
||||||
} else {
|
} else {
|
||||||
|
store = createNewPersistentStore(platform_id, null);
|
||||||
// reset file?
|
// reset file?
|
||||||
if (qs['file'] && qs['reset']) {
|
if (qs['file'] && qs['reset']) {
|
||||||
initPlatform();
|
|
||||||
store.removeItem(qs['fileview'] || qs['file']);
|
store.removeItem(qs['fileview'] || qs['file']);
|
||||||
qs['reset'] = '';
|
qs['reset'] = '';
|
||||||
gotoNewLocation();
|
gotoNewLocation();
|
||||||
|
|
22
src/views.ts
22
src/views.ts
|
@ -5,6 +5,7 @@ import { CodeProject } from "./project";
|
||||||
import { SourceFile, WorkerError } from "./workertypes";
|
import { SourceFile, WorkerError } from "./workertypes";
|
||||||
import { Platform } from "./baseplatform";
|
import { Platform } from "./baseplatform";
|
||||||
import { hex } from "./util";
|
import { hex } from "./util";
|
||||||
|
import { CodeAnalyzer } from "./analysis";
|
||||||
|
|
||||||
export interface ProjectView {
|
export interface ProjectView {
|
||||||
createDiv(parent:HTMLElement, text:string) : HTMLElement;
|
createDiv(parent:HTMLElement, text:string) : HTMLElement;
|
||||||
|
@ -17,6 +18,7 @@ export interface ProjectView {
|
||||||
openBitmapEditorAtCursor?() : void;
|
openBitmapEditorAtCursor?() : void;
|
||||||
markErrors?(errors:WorkerError[]) : void;
|
markErrors?(errors:WorkerError[]) : void;
|
||||||
clearErrors?() : void;
|
clearErrors?() : void;
|
||||||
|
setTimingResult?(result:CodeAnalyzer) : void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: move to different namespace
|
// TODO: move to different namespace
|
||||||
|
@ -211,6 +213,26 @@ export class SourceEditor implements ProjectView {
|
||||||
this.setGutter("gutter-bytes", line-1, s);
|
this.setGutter("gutter-bytes", line-1, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimingResult(result:CodeAnalyzer) : void {
|
||||||
|
this.editor.clearGutter("gutter-bytes");
|
||||||
|
// show the lines
|
||||||
|
for (const line in Object.keys(this.sourcefile.line2offset)) {
|
||||||
|
var pc = this.sourcefile.line2offset[line];
|
||||||
|
var minclocks = result.pc2minclocks[pc];
|
||||||
|
var maxclocks = result.pc2maxclocks[pc];
|
||||||
|
if (minclocks>=0 && maxclocks>=0) {
|
||||||
|
var s;
|
||||||
|
if (maxclocks == minclocks)
|
||||||
|
s = minclocks + "";
|
||||||
|
else
|
||||||
|
s = minclocks + "-" + maxclocks;
|
||||||
|
if (maxclocks == result.MAX_CLOCKS)
|
||||||
|
s += "+";
|
||||||
|
this.setGutterBytes(parseInt(line), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setCurrentLine(line:number, moveCursor:boolean) {
|
setCurrentLine(line:number, moveCursor:boolean) {
|
||||||
|
|
||||||
var addCurrentMarker = (line:number) => {
|
var addCurrentMarker = (line:number) => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user