From d31a8c4efe86c59ac184ce3da4cfd5ad0c626885 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Mon, 6 Nov 2023 10:35:50 -0600 Subject: [PATCH] tweaks to analysis, fixed verilog ball reset bug --- presets/verilog/ball_absolute.v | 16 +++--- src/common/analysis.ts | 89 ++++++++++++++++++++------------- src/common/cpu/MOS6502.ts | 7 +-- src/ide/views/editors.ts | 7 +-- src/platform/vcs.ts | 2 +- src/test/testanalysis.ts | 72 +++++++++++++++++++++++--- src/test/testutil.ts | 17 ++++++- test/verilog/testverilog.js | 2 +- 8 files changed, 154 insertions(+), 58 deletions(-) diff --git a/presets/verilog/ball_absolute.v b/presets/verilog/ball_absolute.v index 8a2b9d9e..f0b423d2 100644 --- a/presets/verilog/ball_absolute.v +++ b/presets/verilog/ball_absolute.v @@ -3,6 +3,10 @@ /* A bouncing ball using absolute coordinates. + +Note: This module uses different clock domains +and thus may be unstable on a FPGA. +See: https://github.com/sehugg/8bitworkshop/issues/23 */ module ball_absolute_top(clk, reset, hsync, vsync, rgb); @@ -18,8 +22,8 @@ module ball_absolute_top(clk, reset, hsync, vsync, rgb); reg [8:0] ball_hpos; // ball current X position reg [8:0] ball_vpos; // ball current Y position - reg [8:0] ball_horiz_move = -2; // ball current X velocity - reg [8:0] ball_vert_move = 2; // ball current Y velocity + reg [8:0] ball_horiz_move; // ball current X velocity + reg [8:0] ball_vert_move; // ball current Y velocity localparam ball_horiz_initial = 128; // ball initial X position localparam ball_vert_initial = 128; // ball initial Y position @@ -52,15 +56,15 @@ module ball_absolute_top(clk, reset, hsync, vsync, rgb); end // vertical bounce - always @(posedge ball_vert_collide) + always @(posedge ball_vert_collide or posedge reset) begin - ball_vert_move <= -ball_vert_move; + ball_vert_move <= reset ? 2 : -ball_vert_move; end // horizontal bounce - always @(posedge ball_horiz_collide) + always @(posedge ball_horiz_collide or posedge reset) begin - ball_horiz_move <= -ball_horiz_move; + ball_horiz_move <= reset ? -2 : -ball_horiz_move; end // offset of ball position from video beam diff --git a/src/common/analysis.ts b/src/common/analysis.ts index 09690c3e..cd0d46ab 100644 --- a/src/common/analysis.ts +++ b/src/common/analysis.ts @@ -2,10 +2,11 @@ import { hex, byte2signed } from "./util"; import { Platform } from "./baseplatform"; +const debug = false; + export interface CodeAnalyzer { showLoopTimingForPC(pc:number); - pc2minclocks : {[key:number]:number}; - pc2maxclocks : {[key:number]:number}; + pc2clockrange : {[key:number]:ClockRange}; MAX_CLOCKS : number; } @@ -37,13 +38,17 @@ function constraintEquals(a,b) { return null; } +interface ClockRange { + minclocks: number; + maxclocks: number; +} + abstract class CodeAnalyzer6502 implements CodeAnalyzer { - pc2minclocks = {}; - pc2maxclocks = {}; + pc2clockrange : {[key:number]:ClockRange} = {}; + jsrresult : {[key:number]:ClockRange} = {}; START_CLOCKS : number; MAX_CLOCKS : number; WRAP_CLOCKS : boolean; - jsrresult = {}; platform : Platform; MAX_CYCLES : number = 2000; @@ -58,45 +63,60 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer { } traceInstructions(pc:number, minclocks:number, maxclocks:number, subaddr:number, constraints) { - if (this.WRAP_CLOCKS) { - if (this.pc2minclocks[pc] !== undefined) - minclocks = Math.min(minclocks, this.pc2minclocks[pc]); - if (this.pc2maxclocks[pc] !== undefined) - maxclocks = Math.max(maxclocks, this.pc2maxclocks[pc]); - } - //console.log("trace", hex(pc), minclocks, maxclocks); + if (debug) console.log("trace", hex(pc), minclocks, maxclocks); if (!constraints) constraints = {}; var modified = true; var abort = false; - for (var 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; } modified = false; - if (this.WRAP_CLOCKS && minclocks >= this.MAX_CLOCKS) { + if (this.WRAP_CLOCKS) { // wrap clocks minclocks = minclocks % this.MAX_CLOCKS; maxclocks = maxclocks % this.MAX_CLOCKS; + if (maxclocks == minclocks-1) { + if (debug) console.log("0-75", hex(pc), minclocks, maxclocks); + minclocks = 0; + maxclocks = this.MAX_CLOCKS-1; + } } else { // truncate clocks minclocks = Math.min(this.MAX_CLOCKS, minclocks); maxclocks = Math.min(this.MAX_CLOCKS, maxclocks); } - 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 (!(minclocks >= this.pc2minclocks[pc0])) { - this.pc2minclocks[pc0] = minclocks; + let meta = this.getClockCountsAtPC(pc); + 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}; + if (debug) console.log("new", hex(pc), hex(pc0), hex(subaddr), minclocks, maxclocks); modified = true; } - if (!(maxclocks <= this.pc2maxclocks[pc0])) { - this.pc2maxclocks[pc0] = maxclocks; - modified = true; + //console.log(hex(pc),minclocks,maxclocks, pcrange); + if (pcrange.minclocks != minclocks || pcrange.maxclocks != maxclocks) { + 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; + modified = true; + } + if (minclocks < pcrange.minclocks) { + if (debug) console.log("min", hex(pc), hex(pc0), hex(subaddr), minclocks, maxclocks, pcrange); + pcrange.minclocks = minclocks; + modified = true; + } + if (maxclocks > pcrange.maxclocks) { + if (debug) console.log("max", hex(pc), hex(pc0), hex(subaddr), minclocks, maxclocks, pcrange); + pcrange.maxclocks = maxclocks; + modified = true; + } } - //console.log(hex(pc),minclocks,maxclocks,modified,meta,constraints); if (!meta.insnlength) { console.log("Illegal instruction!", hex(pc), hex(meta.opcode), meta); break; @@ -110,13 +130,11 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer { case 0x39: case 0x3d: case 0x59: case 0x5d: case 0x79: case 0x7d: - case 0x99: case 0x9d: - case 0xa9: case 0xad: - case 0xb9: case 0xbd: case 0xbc: case 0xbe: + 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 + if (lob == 0) meta.maxCycles -= 1; // no page boundary crossed break; // TODO: only VCS case 0x85: @@ -208,26 +226,27 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer { return; } // add min/max instruction time to min/max clocks bound + if (debug) console.log("add", hex(pc), meta.minCycles, meta.maxCycles); minclocks += meta.minCycles; maxclocks += meta.maxCycles; } } showLoopTimingForPC(pc:number) { - this.pc2minclocks = {}; - this.pc2maxclocks = {}; + this.pc2clockrange = {}; 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) +// 76 cycles export class CodeAnalyzer_vcs extends CodeAnalyzer6502 { constructor(platform : Platform) { super(platform); - this.MAX_CLOCKS = this.START_CLOCKS = 76*4; // 4 scanlines - this.WRAP_CLOCKS = false; + this.MAX_CLOCKS = 76; // 1 scanline + this.START_CLOCKS = 0; // TODO? + this.WRAP_CLOCKS = true; } } diff --git a/src/common/cpu/MOS6502.ts b/src/common/cpu/MOS6502.ts index 6774f0c4..684806ba 100644 --- a/src/common/cpu/MOS6502.ts +++ b/src/common/cpu/MOS6502.ts @@ -1805,7 +1805,7 @@ export var _MOS6502 = function() { 2, 6, 0, 0, 4, 4, 4, 4, 2, 5, 2, 0, 0, 5, 0, 0, 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 0, 4, 4, 4, 4, 2, 5, 0, 5, 4, 4, 4, 4, 2, 4, 2, 0, 4, 4, 4, 4, - 2, 6, 0, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 3, 6, + 2, 6, 0, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 2, 6, 0, 8, 3, 3, 5, 5, 2, 2, 2, 0, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7 @@ -1989,6 +1989,7 @@ export class MOS6502 implements CPU, ClockBased, SavesState, Inter isStable() : boolean { return this.cpu.isPCStable(); } - // TODO: metadata - // TODO: disassembler + getOpcodeMetadata(op: number) { + return this.cpu.getOpcodeMetadata(op); + } } diff --git a/src/ide/views/editors.ts b/src/ide/views/editors.ts index 8cbcf401..92ba16c1 100644 --- a/src/ide/views/editors.ts +++ b/src/ide/views/editors.ts @@ -325,9 +325,10 @@ export class SourceEditor implements ProjectView { if (this.sourcefile == null) return; // show the lines for (const line of Object.keys(this.sourcefile.line2offset)) { - var pc = this.sourcefile.line2offset[line]; - var minclocks = result.pc2minclocks[pc]; - var maxclocks = result.pc2maxclocks[pc]; + let pc = this.sourcefile.line2offset[line]; + let clocks = result.pc2clockrange[pc]; + var minclocks = clocks && clocks.minclocks; + var maxclocks = clocks && clocks.maxclocks; if (minclocks>=0 && maxclocks>=0) { var s; if (maxclocks == minclocks) diff --git a/src/platform/vcs.ts b/src/platform/vcs.ts index 4a849680..88f8ffd3 100644 --- a/src/platform/vcs.ts +++ b/src/platform/vcs.ts @@ -375,7 +375,7 @@ class VCSPlatform extends BasePlatform { bus.oldRead = bus.read; bus.read = function(a) { var v = this.oldRead(a); - if (a < 0x80) probe.logIORead(a,v); + if (a > 0 && a < 0x80) probe.logIORead(a,v); // (00),x reads $00? else if (a > 0x280 && a < 0x300) probe.logIORead(a,v); else probe.logRead(a,v); return v; diff --git a/src/test/testanalysis.ts b/src/test/testanalysis.ts index 29dc8e1e..576a2262 100644 --- a/src/test/testanalysis.ts +++ b/src/test/testanalysis.ts @@ -44,17 +44,73 @@ class Test6502Platform implements Platform { } describe('6502 analysis', function () { - it('Should analyze 6502', function () { + it('Should analyze WSYNC', 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); + console.log(analysis.pc2clockrange); + assert.equal(analysis.pc2clockrange[0x0].minclocks, 0); + assert.equal(analysis.pc2clockrange[0x0].maxclocks, 0); + assert.equal(analysis.pc2clockrange[0x1].minclocks, 2); + assert.equal(analysis.pc2clockrange[0x1].maxclocks, 19); + assert.equal(analysis.pc2clockrange[0x3].minclocks, 0); + assert.equal(analysis.pc2clockrange[0x3].maxclocks, 0); + }); + it('Should analyze loop', function () { + let platform = new Test6502Platform(); + platform.ram.set([0xea,0x4c,0x00,0x00]); // 5 cycles + let analysis = new CodeAnalyzer_vcs(platform); + analysis.showLoopTimingForPC(0x0); + console.log(analysis.pc2clockrange); + assert.equal(analysis.pc2clockrange[0x0].minclocks, 0); + assert.equal(analysis.pc2clockrange[0x0].maxclocks, 75); + // TODO: should be 0-75 + /* + assert.equal(analysis.pc2clockrange[0x1].minclocks, 1); + assert.equal(analysis.pc2clockrange[0x1].maxclocks, 72); + */ + }); + it('Should wrap clocks', function () { + let platform = new Test6502Platform(); + platform.ram.set([0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xb1,0,0xea,0x4c,0,0]); + let analysis = new CodeAnalyzer_vcs(platform); + analysis.showLoopTimingForPC(0x0); + console.log(analysis.pc2clockrange); + // TODO: should be 0-75 + assert.equal(analysis.pc2clockrange[0x0].minclocks, 0); + assert.equal(analysis.pc2clockrange[0x0].maxclocks, 75); + assert.equal(analysis.pc2clockrange[0x2].minclocks, 5); + assert.equal(analysis.pc2clockrange[0x2].maxclocks, 6); + }); + it('Should wrap RTS', function () { + let platform = new Test6502Platform(); + platform.ram.set([0xb1,0x60,0x20,1,0,0x4c,0,0]); + let analysis = new CodeAnalyzer_vcs(platform); + analysis.showLoopTimingForPC(0x0); + console.log(analysis.pc2clockrange); + // TODO: should be 0-75 + assert.equal(analysis.pc2clockrange[0x0].minclocks, 0); + assert.equal(analysis.pc2clockrange[0x0].maxclocks, 75); + }); + it('Should not recurse', function () { + let platform = new Test6502Platform(); + platform.ram.set([0xea,0x20,0x07,0x00,0x4c,0x00,0x00, 0xa4,0x88,0xea,0xd0,0xfb,0x60]); + let analysis = new CodeAnalyzer_vcs(platform); + analysis.showLoopTimingForPC(0x0); + console.log(analysis.pc2clockrange); + // TODO: should be 0-75 + assert.equal(analysis.pc2clockrange[0x0].minclocks, 0); + assert.equal(analysis.pc2clockrange[0x0].maxclocks, 75); + }); + it('Should not break', function () { + let platform = new Test6502Platform(); + platform.ram.set([0x85,0x02,0x85,0x2a,0xa9,0x00,0x85,0x26,0x85,0x1b,0x85,0x1c,0x4c,0x00,0x00]); + let analysis = new CodeAnalyzer_vcs(platform); + analysis.showLoopTimingForPC(0x0); + console.log(analysis.pc2clockrange); + // TODO: should be 0-75 + assert.equal(analysis.pc2clockrange[0x0].minclocks, 0); + assert.equal(analysis.pc2clockrange[0x0].maxclocks, 17); }); }); diff --git a/src/test/testutil.ts b/src/test/testutil.ts index d4e2b250..4a4d91d6 100644 --- a/src/test/testutil.ts +++ b/src/test/testutil.ts @@ -2,8 +2,10 @@ import assert from "assert"; import { describe } from "mocha"; import { EmuHalt } from "../common/emu" -import { lzgmini, isProbablyBinary } from "../common/util"; +import { lzgmini, isProbablyBinary, hex } from "../common/util"; import { Tokenizer, TokenType } from "../common/tokenizer"; +import { OPS_6502 } from "../common/cpu/disasm6502"; +import { MOS6502 } from "../common/cpu/MOS6502"; var NES_CONIO_ROM_LZG = [ 76, 90, 71, 0, 0, 160, 16, 0, 0, 11, 158, 107, 131, 223, 83, 1, 9, 17, 21, 22, 78, 69, 83, 26, 2, 1, 3, 0, 22, 6, 120, 216, @@ -184,3 +186,16 @@ describe('EmuHalt', function () { assert.ok(err.squelchError); } }); + +describe('CPU metadata', function () { + let cpu = new MOS6502(); + for (let i=0; i<256; i++) { + let meta1 = OPS_6502[i]; + let meta2 = cpu.getOpcodeMetadata(i); + if (meta1.il > 0) continue; +// assert.strictEqual(meta1.mn, meta2.mnenomic, `mnemonic ${hex(i)}: ${meta1.mn} != ${meta2.mnenomic}`); + assert.strictEqual(meta1.nb, meta2.insnlength, `insnLength ${hex(i)}: ${meta1.nb} != ${meta2.insnlength}`); + assert.strictEqual(meta1.c1, meta2.minCycles, `minCycles ${hex(i)}: ${meta1.c1} != ${meta2.minCycles}`); + assert.strictEqual(meta1.c1+meta1.c2, meta2.maxCycles, `maxCycles ${hex(i)}: ${meta1.c1+meta1.c2} != ${meta2.maxCycles}`); + } +}); diff --git a/test/verilog/testverilog.js b/test/verilog/testverilog.js index be80dc66..1fb4d154 100644 --- a/test/verilog/testverilog.js +++ b/test/verilog/testverilog.js @@ -4,7 +4,7 @@ var _fs = require('fs'); var _path = require('path') var _cproc = require('child_process'); var fs = require('fs'); -var wtu = require('./workertestutils.js'); +var wtu = require('../cli/workertestutils.js'); //var heapdump = require("heapdump"); var commandExists = require('command-exists');