tweaks to analysis, fixed verilog ball reset bug

This commit is contained in:
Steven Hugg 2023-11-06 10:35:50 -06:00
parent 3a08c24869
commit d31a8c4efe
8 changed files with 154 additions and 58 deletions

View File

@ -3,6 +3,10 @@
/* /*
A bouncing ball using absolute coordinates. 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); 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_hpos; // ball current X position
reg [8:0] ball_vpos; // ball current Y 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_horiz_move; // ball current X velocity
reg [8:0] ball_vert_move = 2; // ball current Y velocity reg [8:0] ball_vert_move; // ball current Y velocity
localparam ball_horiz_initial = 128; // ball initial X position localparam ball_horiz_initial = 128; // ball initial X position
localparam ball_vert_initial = 128; // ball initial Y position localparam ball_vert_initial = 128; // ball initial Y position
@ -52,15 +56,15 @@ module ball_absolute_top(clk, reset, hsync, vsync, rgb);
end end
// vertical bounce // vertical bounce
always @(posedge ball_vert_collide) always @(posedge ball_vert_collide or posedge reset)
begin begin
ball_vert_move <= -ball_vert_move; ball_vert_move <= reset ? 2 : -ball_vert_move;
end end
// horizontal bounce // horizontal bounce
always @(posedge ball_horiz_collide) always @(posedge ball_horiz_collide or posedge reset)
begin begin
ball_horiz_move <= -ball_horiz_move; ball_horiz_move <= reset ? -2 : -ball_horiz_move;
end end
// offset of ball position from video beam // offset of ball position from video beam

View File

@ -2,10 +2,11 @@
import { hex, byte2signed } from "./util"; import { hex, byte2signed } from "./util";
import { Platform } from "./baseplatform"; import { Platform } from "./baseplatform";
const debug = false;
export interface CodeAnalyzer { export interface CodeAnalyzer {
showLoopTimingForPC(pc:number); showLoopTimingForPC(pc:number);
pc2minclocks : {[key:number]:number}; pc2clockrange : {[key:number]:ClockRange};
pc2maxclocks : {[key:number]:number};
MAX_CLOCKS : number; MAX_CLOCKS : number;
} }
@ -37,13 +38,17 @@ function constraintEquals(a,b) {
return null; return null;
} }
interface ClockRange {
minclocks: number;
maxclocks: number;
}
abstract class CodeAnalyzer6502 implements CodeAnalyzer { abstract class CodeAnalyzer6502 implements CodeAnalyzer {
pc2minclocks = {}; pc2clockrange : {[key:number]:ClockRange} = {};
pc2maxclocks = {}; jsrresult : {[key:number]:ClockRange} = {};
START_CLOCKS : number; START_CLOCKS : number;
MAX_CLOCKS : number; MAX_CLOCKS : number;
WRAP_CLOCKS : boolean; WRAP_CLOCKS : boolean;
jsrresult = {};
platform : Platform; platform : Platform;
MAX_CYCLES : number = 2000; MAX_CYCLES : number = 2000;
@ -58,45 +63,60 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
} }
traceInstructions(pc:number, minclocks:number, maxclocks:number, subaddr:number, constraints) { traceInstructions(pc:number, minclocks:number, maxclocks:number, subaddr:number, constraints) {
if (this.WRAP_CLOCKS) { if (debug) console.log("trace", hex(pc), minclocks, maxclocks);
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 (!constraints) constraints = {}; if (!constraints) constraints = {};
var modified = true; var modified = true;
var abort = false; var abort = false;
for (var i=0; modified && !abort; i++) { for (let i=0; modified && !abort; i++) {
if (i >= this.MAX_CYCLES) { if (i >= this.MAX_CYCLES) {
console.log("too many cycles @", hex(pc), "routine", hex(subaddr)); console.log("too many cycles @", hex(pc), "routine", hex(subaddr));
break; break;
} }
modified = false; modified = false;
if (this.WRAP_CLOCKS && minclocks >= this.MAX_CLOCKS) { if (this.WRAP_CLOCKS) {
// wrap clocks // wrap clocks
minclocks = minclocks % this.MAX_CLOCKS; minclocks = minclocks % this.MAX_CLOCKS;
maxclocks = maxclocks % 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 { } else {
// truncate clocks // truncate clocks
minclocks = Math.min(this.MAX_CLOCKS, minclocks); minclocks = Math.min(this.MAX_CLOCKS, minclocks);
maxclocks = Math.min(this.MAX_CLOCKS, maxclocks); maxclocks = Math.min(this.MAX_CLOCKS, maxclocks);
} }
var meta = this.getClockCountsAtPC(pc); let meta = this.getClockCountsAtPC(pc);
var lob = this.platform.readAddress(pc+1); let lob = this.platform.readAddress(pc+1);
var hib = this.platform.readAddress(pc+2); let hib = this.platform.readAddress(pc+2);
var addr = lob + (hib << 8); let addr = lob + (hib << 8);
var pc0 = pc; let pc0 = pc;
if (!(minclocks >= this.pc2minclocks[pc0])) { let pcrange = this.pc2clockrange[pc0];
this.pc2minclocks[pc0] = minclocks; 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; modified = true;
} }
if (!(maxclocks <= this.pc2maxclocks[pc0])) { //console.log(hex(pc),minclocks,maxclocks, pcrange);
this.pc2maxclocks[pc0] = maxclocks; if (pcrange.minclocks != minclocks || pcrange.maxclocks != maxclocks) {
modified = true; 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) { if (!meta.insnlength) {
console.log("Illegal instruction!", hex(pc), hex(meta.opcode), meta); console.log("Illegal instruction!", hex(pc), hex(meta.opcode), meta);
break; break;
@ -110,13 +130,11 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
case 0x39: case 0x3d: case 0x39: case 0x3d:
case 0x59: case 0x5d: case 0x59: case 0x5d:
case 0x79: case 0x7d: case 0x79: case 0x7d:
case 0x99: case 0x9d: case 0xb9: case 0xbb:
case 0xa9: case 0xad: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
case 0xb9: case 0xbd: case 0xbc: case 0xbe:
case 0xd9: case 0xdd: case 0xd9: case 0xdd:
case 0xf9: case 0xfd: case 0xf9: case 0xfd:
if (lob == 0) if (lob == 0) meta.maxCycles -= 1; // no page boundary crossed
meta.maxCycles -= 1; // no page boundary crossed
break; break;
// TODO: only VCS // TODO: only VCS
case 0x85: case 0x85:
@ -208,26 +226,27 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
return; return;
} }
// add min/max instruction time to min/max clocks bound // add min/max instruction time to min/max clocks bound
if (debug) console.log("add", hex(pc), meta.minCycles, meta.maxCycles);
minclocks += meta.minCycles; minclocks += meta.minCycles;
maxclocks += meta.maxCycles; maxclocks += meta.maxCycles;
} }
} }
showLoopTimingForPC(pc:number) { showLoopTimingForPC(pc:number) {
this.pc2minclocks = {}; this.pc2clockrange = {};
this.pc2maxclocks = {};
this.jsrresult = {}; this.jsrresult = {};
// recurse through all traces // recurse through all traces
this.traceInstructions(pc | this.platform.getOriginPC(), this.START_CLOCKS, this.MAX_CLOCKS, 0, {}); 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 { export class CodeAnalyzer_vcs extends CodeAnalyzer6502 {
constructor(platform : Platform) { constructor(platform : Platform) {
super(platform); super(platform);
this.MAX_CLOCKS = this.START_CLOCKS = 76*4; // 4 scanlines this.MAX_CLOCKS = 76; // 1 scanline
this.WRAP_CLOCKS = false; this.START_CLOCKS = 0; // TODO?
this.WRAP_CLOCKS = true;
} }
} }

View File

@ -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, 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, 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, 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, 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, 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 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<MOS6502State>, Inter
isStable() : boolean { isStable() : boolean {
return this.cpu.isPCStable(); return this.cpu.isPCStable();
} }
// TODO: metadata getOpcodeMetadata(op: number) {
// TODO: disassembler return this.cpu.getOpcodeMetadata(op);
}
} }

View File

@ -325,9 +325,10 @@ export class SourceEditor implements ProjectView {
if (this.sourcefile == null) return; if (this.sourcefile == null) return;
// show the lines // show the lines
for (const line of Object.keys(this.sourcefile.line2offset)) { for (const line of Object.keys(this.sourcefile.line2offset)) {
var pc = this.sourcefile.line2offset[line]; let pc = this.sourcefile.line2offset[line];
var minclocks = result.pc2minclocks[pc]; let clocks = result.pc2clockrange[pc];
var maxclocks = result.pc2maxclocks[pc]; var minclocks = clocks && clocks.minclocks;
var maxclocks = clocks && clocks.maxclocks;
if (minclocks>=0 && maxclocks>=0) { if (minclocks>=0 && maxclocks>=0) {
var s; var s;
if (maxclocks == minclocks) if (maxclocks == minclocks)

View File

@ -375,7 +375,7 @@ class VCSPlatform extends BasePlatform {
bus.oldRead = bus.read; bus.oldRead = bus.read;
bus.read = function(a) { bus.read = function(a) {
var v = this.oldRead(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 if (a > 0x280 && a < 0x300) probe.logIORead(a,v);
else probe.logRead(a,v); else probe.logRead(a,v);
return v; return v;

View File

@ -44,17 +44,73 @@ class Test6502Platform implements Platform {
} }
describe('6502 analysis', function () { describe('6502 analysis', function () {
it('Should analyze 6502', function () { it('Should analyze WSYNC', function () {
let platform = new Test6502Platform(); let platform = new Test6502Platform();
platform.ram.set([0xea,0x85,0x02,0xa9,0x60,0x20,0x04,0x00,0xea,0x4c,0x01,0x00]); platform.ram.set([0xea,0x85,0x02,0xa9,0x60,0x20,0x04,0x00,0xea,0x4c,0x01,0x00]);
let analysis = new CodeAnalyzer_vcs(platform); let analysis = new CodeAnalyzer_vcs(platform);
analysis.showLoopTimingForPC(0x0); analysis.showLoopTimingForPC(0x0);
console.log(analysis); console.log(analysis.pc2clockrange);
assert.equal(analysis.pc2minclocks[0x0], 304); assert.equal(analysis.pc2clockrange[0x0].minclocks, 0);
assert.equal(analysis.pc2maxclocks[0x0], 304); assert.equal(analysis.pc2clockrange[0x0].maxclocks, 0);
assert.equal(analysis.pc2minclocks[0x1], 19); assert.equal(analysis.pc2clockrange[0x1].minclocks, 2);
assert.equal(analysis.pc2maxclocks[0x0], 304); assert.equal(analysis.pc2clockrange[0x1].maxclocks, 19);
assert.equal(analysis.pc2minclocks[0x3], 0); assert.equal(analysis.pc2clockrange[0x3].minclocks, 0);
assert.equal(analysis.pc2maxclocks[0x3], 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);
}); });
}); });

View File

@ -2,8 +2,10 @@
import assert from "assert"; import assert from "assert";
import { describe } from "mocha"; import { describe } from "mocha";
import { EmuHalt } from "../common/emu" 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 { Tokenizer, TokenType } from "../common/tokenizer";
import { OPS_6502 } from "../common/cpu/disasm6502";
import { MOS6502 } from "../common/cpu/MOS6502";
var NES_CONIO_ROM_LZG = [ 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, 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); 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}`);
}
});

View File

@ -4,7 +4,7 @@ var _fs = require('fs');
var _path = require('path') var _path = require('path')
var _cproc = require('child_process'); var _cproc = require('child_process');
var fs = require('fs'); var fs = require('fs');
var wtu = require('./workertestutils.js'); var wtu = require('../cli/workertestutils.js');
//var heapdump = require("heapdump"); //var heapdump = require("heapdump");
var commandExists = require('command-exists'); var commandExists = require('command-exists');