From 33b2e92396c9a3a0e4de055787cb9368e37d3c74 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Fri, 2 Sep 2022 11:05:03 -0500 Subject: [PATCH] atari8: fixes, faked .xex loading --- src/common/baseplatform.ts | 2 +- src/common/devices.ts | 2 +- src/common/emu.ts | 5 +- src/ide/views/debugviews.ts | 10 ++- src/machine/atari8.ts | 119 ++++++++++++++++++++++++++++++------ src/machine/chips/antic.ts | 32 +++++++--- src/machine/chips/gtia.ts | 94 ++++++++++++++++++++-------- src/platform/atari8.ts | 1 + test/cli/testplatforms.js | 2 +- 9 files changed, 207 insertions(+), 60 deletions(-) diff --git a/src/common/baseplatform.ts b/src/common/baseplatform.ts index 12acf75c..4de11e48 100644 --- a/src/common/baseplatform.ts +++ b/src/common/baseplatform.ts @@ -850,7 +850,7 @@ export abstract class BaseMachinePlatform extends BaseDebugPl } loadROM(title, data) { - this.machine.loadROM(data); + this.machine.loadROM(data, title); this.reset(); } diff --git a/src/common/devices.ts b/src/common/devices.ts index fb9c9e24..3f32c279 100644 --- a/src/common/devices.ts +++ b/src/common/devices.ts @@ -308,7 +308,7 @@ export abstract class BasicMachine extends BasicHeadlessMachine implements Sampl abstract sampleRate : number; overscan : boolean = false; rotate : number = 0; - aspectRatio : number = 1.0; + aspectRatio : number; pixels : Uint32Array; audio : SampledAudioSink; diff --git a/src/common/emu.ts b/src/common/emu.ts index d2b950e5..a1d1128e 100644 --- a/src/common/emu.ts +++ b/src/common/emu.ts @@ -746,8 +746,9 @@ export function gtia_ntsc_to_rgb(val: number) { let cr = (val >> 4) & 15; let lm = val & 15; let crlv = cr ? color : 0; - let phase = ((cr - 1) * 25 - 38) * (2 * Math.PI / 360); - let y = 256 * bright * Math.pow((lm + 1) / 16, gamma); + if (cr) lm += 1; + let phase = ((cr - 1) * 25 - 25) * (2 * Math.PI / 360); + let y = 256 * bright * Math.pow(lm / 16, gamma); let i = crlv * Math.cos(phase); let q = crlv * Math.sin(phase); var r = y + 0.956 * i + 0.621 * q; diff --git a/src/ide/views/debugviews.ts b/src/ide/views/debugviews.ts index 2fa5a50f..fcd42e27 100644 --- a/src/ide/views/debugviews.ts +++ b/src/ide/views/debugviews.ts @@ -563,7 +563,15 @@ export class AddressHeatMapView extends ProbeBitmapViewBase implements ProjectVi this.canvas.onclick = (e) => { var pos = getMousePos(this.canvas, e); var opaddr = Math.floor(pos.x) + Math.floor(pos.y) * 256; - runToPC(opaddr & 0xffff); + var lastpc = -1; + var runpc = -1; + this.redraw( (op,addr) => { + if (runpc < 0 && lastpc >= 0 && addr == opaddr) { + runpc = lastpc; + } + if (op == ProbeFlags.EXECUTE) lastpc = addr; + }); + if (runpc >= 0) runToPC(runpc); } } diff --git a/src/machine/atari8.ts b/src/machine/atari8.ts index 18273c7d..1f688d43 100644 --- a/src/machine/atari8.ts +++ b/src/machine/atari8.ts @@ -1,9 +1,9 @@ import { newPOKEYAudio, TssChannelAdapter } from "../common/audio"; -import { EmuState, Machine } from "../common/baseplatform"; +import { Machine } from "../common/baseplatform"; import { MOS6502 } from "../common/cpu/MOS6502"; -import { AcceptsKeyInput, AcceptsPaddleInput, AcceptsROM, BasicScanlineMachine, FrameBased, Probeable, RasterFrameBased, TrapCondition, VideoSource } from "../common/devices"; -import { dumpRAM, EmuHalt, KeyFlags, Keys, makeKeycodeMap, newAddressDecoder, newKeyboardHandler } from "../common/emu"; -import { hex, lpad, lzgmini, rgb2bgr, safe_extend, stringToByteArray } from "../common/util"; +import { AcceptsKeyInput, AcceptsPaddleInput, AcceptsROM, BasicScanlineMachine, FrameBased, Probeable, TrapCondition, VideoSource } from "../common/devices"; +import { KeyFlags, Keys, makeKeycodeMap, newAddressDecoder, newKeyboardHandler } from "../common/emu"; +import { hex } from "../common/util"; import { BaseWASIMachine } from "../common/wasmplatform"; import { ANTIC, MODE_SHIFT } from "./chips/antic"; import { CONSOL, GTIA, TRIG0 } from "./chips/gtia"; @@ -46,12 +46,11 @@ export class Atari800 extends BasicScanlineMachine { cpuFrequency = 1789773; numTotalScanlines = 262; cpuCyclesPerLine = 114; - canvasWidth = 348; // TODO? + canvasWidth = 336; numVisibleScanlines = 224; - aspectRatio = 240 / 172; + aspectRatio = this.canvasWidth / this.numVisibleScanlines * 0.857; firstVisibleScanline = 16; - firstVisibleClock = 44 * 2; // ... to 215 * 2 - // TODO: for 400/800/5200 + firstVisibleClock = (44 - 6) * 2; // ... to 215 * 2 defaultROMSize = 0x8000; overscan = true; audioOversample = 4; @@ -59,7 +58,6 @@ export class Atari800 extends BasicScanlineMachine { cpu: MOS6502; ram: Uint8Array; - rom: Uint8Array; bios: Uint8Array; bus; audio_pokey; @@ -73,6 +71,7 @@ export class Atari800 extends BasicScanlineMachine { keycode = 0; cart_80 = false; cart_a0 = false; + xexdata = null; // TODO: save/load vars constructor() { @@ -106,10 +105,12 @@ export class Atari800 extends BasicScanlineMachine { [0xd800, 0xffff, 0xffff, (a) => { return this.bios[a - 0xd800]; }], ]), write: newAddressDecoder([ - [0x0000, 0xbfff, 0xffff, (a, v) => { this.ram[a] = v; }], + [0x0000, 0xbffa, 0xffff, (a, v) => { this.ram[a] = v; }], + [0xbffb, 0xbfff, 0xffff, (a, v) => { this.ram[a] = v; this.initCartA(); }], [0xd000, 0xd0ff, 0x1f, (a, v) => { this.gtia.setReg(a, v); }], [0xd200, 0xd2ff, 0xf, (a, v) => { this.writePokey(a, v); }], [0xd400, 0xd4ff, 0xf, (a, v) => { this.antic.setReg(a, v); }], + [0xd500, 0xd5ff, 0xff, (a, v) => { this.writeMapper(a, v); }], ]), }; } @@ -119,11 +120,11 @@ export class Atari800 extends BasicScanlineMachine { } reset() { - console.log(this.saveState()); super.reset(); this.antic.reset(); this.gtia.reset(); this.keycode = 0; + if (this.xexdata) this.cart_a0 = true; // TODO } read(a) { @@ -197,8 +198,6 @@ export class Atari800 extends BasicScanlineMachine { // update GTIA // get X coordinate within scanline let xofs = this.antic.h * 4 - this.firstVisibleClock; - // correct for HSCROL - if (this.antic.dliop & 0x10) xofs += (this.antic.regs[4] & 1) << 1; // GTIA tick functions let gtiatick1 = () => { this.gtia.clockPulse1(); @@ -209,8 +208,17 @@ export class Atari800 extends BasicScanlineMachine { this.linergb[xofs++] = this.gtia.rgb; } // tick 4 GTIA clocks for each CPU/ANTIC cycle + this.gtia.clockPulse4(); + // correct for HSCROL -- bias antic +2, bias gtia -1 + if ((this.antic.dliop & 0x10) && (this.antic.regs[4] & 1)) { + xofs += 2; + this.gtia.setBias(-1); + } else { + this.gtia.setBias(0); + } let bp = MODE_SHIFT[this.antic.mode]; - if (bp < 8 || (xofs & 4) == 0) { this.gtia.an = this.antic.shiftout(); } + let odd = this.antic.h & 1; + if (bp < 8 || odd) { this.gtia.an = this.antic.shiftout(); } gtiatick1(); if (bp == 1) { this.gtia.an = this.antic.shiftout(); } gtiatick2(); @@ -222,12 +230,12 @@ export class Atari800 extends BasicScanlineMachine { } loadState(state: any) { + this.loadControlsState(state); this.cpu.loadState(state.c); this.ram.set(state.ram); this.antic.loadState(state.antic); this.gtia.loadState(state.gtia); this.irq_pokey.loadState(state.pokey); - this.loadControlsState(state); this.lastdmabyte = state.lastdmabyte; this.keycode = state.keycode; } @@ -240,7 +248,7 @@ export class Atari800 extends BasicScanlineMachine { pokey: this.irq_pokey.saveState(), inputs: this.inputs.slice(0), lastdmabyte: this.lastdmabyte, - keycode: this.keycode, // TODO: inputs? + keycode: this.keycode, }; } loadControlsState(state) { @@ -300,9 +308,26 @@ export class Atari800 extends BasicScanlineMachine { this.probe.logInterrupt(1); } - loadROM(rom: Uint8Array) { - if (rom.length != 0x2000 && rom.length != 0x4000 && rom.length != 0x8000) - throw new Error("Sorry, this platform can only load 8/16/32 KB cartridges at the moment."); + loadROM(rom: Uint8Array, title: string) { + // XEX file? + if (title && title.toLowerCase().endsWith('.xex') && rom[0] == 0xff && rom[1] == 0xff) { + // TODO: we fake a cartridge + this.xexdata = rom; + let cart = new Uint8Array(0x1000); + cart.set([0x00, 0x01, 0x00, 0x04, 0x00, 0x01], 0xffa); + this.loadCartridge(cart); + } else { + this.loadCartridge(rom); + } + } + + loadCartridge(rom: Uint8Array) { + // strip off header + if (rom[0] == 0x43 && rom[1] == 0x41 && rom[2] == 0x52 && rom[3] == 0x54) { + rom = rom.slice(16); + } + if (rom.length != 0x1000 && rom.length != 0x2000 && rom.length != 0x4000 && rom.length != 0x8000) + throw new Error("Sorry, this platform can only load 4/8/16/32 KB cartridges at the moment."); // TODO: support other than 8 KB carts // support 4/8/16/32 KB carts let rom2 = new Uint8Array(0x8000); @@ -310,9 +335,63 @@ export class Atari800 extends BasicScanlineMachine { rom2.set(rom, i); } this.cart_a0 = true; // TODO - if (rom.length == 0x4000) { this.cart_80 = true; } + this.cart_80 = rom.length == 0x4000; super.loadROM(rom2); } + + writeMapper(addr:number, value:number) { + if (addr == 0xff) { + if (value == 0x80) this.cart_80 = false; + if (value == 0xa0) this.cart_a0 = false; + } + } + + // TODO + loadXEX(rom: Uint8Array) { + let ofs = 2; + let cart = this.ram; + let cartofs = 0x100; // stub routine in stack page + while (ofs < rom.length) { + let start = rom[ofs+0] + rom[ofs+1] * 256; + let end = rom[ofs+2] + rom[ofs+3] * 256; + console.log('XEX', ofs, hex(start), hex(end)); + ofs += 4; + for (let i=start; i<=end; i++) { + this.ram[i] = rom[ofs++]; + } + var runaddr = this.ram[0x2e0] + this.ram[0x2e1]*256; + var initaddr = this.ram[0x2e2] + this.ram[0x2e3]*256; + console.log('XEX run', hex(runaddr), 'init', hex(initaddr)); + if (initaddr) { + cart[cartofs++] = 0x20; + cart[cartofs++] = initaddr & 0xff; + cart[cartofs++] = initaddr >> 8; + } + if (ofs > rom.length) throw new Error("Bad .XEX file format"); + } + if (runaddr) { + cart[cartofs++] = 0xa9; // lda #$a0 + cart[cartofs++] = 0xa0; + cart[cartofs++] = 0x8d; // sta $d5ff (disable cart) + cart[cartofs++] = 0xff; + cart[cartofs++] = 0xd5; + cart[cartofs++] = 0x4c; // jmp runaddr + cart[cartofs++] = runaddr & 0xff; + cart[cartofs++] = runaddr >> 8; + } +} + + initCartA() { + //console.log('init', hex(this.cpu.getPC())); + // disable cartridges and load XEX + if (this.cpu.getPC() == 0xf17f) { + if (this.xexdata) { + this.loadXEX(this.xexdata); + } + //this.cart_80 = this.cart_a0 = false; + } + } + } export class Atari5200 extends Atari800 { diff --git a/src/machine/chips/antic.ts b/src/machine/chips/antic.ts index 5765847d..28ea72ce 100644 --- a/src/machine/chips/antic.ts +++ b/src/machine/chips/antic.ts @@ -10,8 +10,8 @@ import { hex, lpad, safe_extend } from "../../common/util"; // http://www.atarimuseum.com/videogames/consoles/5200/conv_to_5200.html // https://www.virtualdub.org/downloads/Altirra%20Hardware%20Reference%20Manual.pdf -const PF_LEFT = [0, 29, 21, 13]; -const PF_RIGHT = [0, 29 + 64, 21 + 80, 13 + 96]; +const PF_LEFT = [0, 25, 17, 9]; +const PF_RIGHT = [0, 25 + 64, 17 + 80, 9 + 96]; const DMACTL = 0; const CHACTL = 1; @@ -38,8 +38,9 @@ const NMIST_CYCLE = 12; const NMI_CYCLE = 24; const WSYNC_CYCLE = 212; -const ANTIC_LEFT = 17; // gtia 34 -const ANTIC_RIGHT = 110; // gtia 221 +const ANTIC_LEFT = 17 - 4; // gtia 34, 4 cycle delay +const ANTIC_RIGHT = 110 - 4; // gtia 221, 4 cycle delay +const LAST_DMA_H = 105; // last DMA cycle const MODE_LINES = [0, 0, 8, 10, 8, 16, 8, 16, 8, 4, 4, 2, 1, 2, 1, 1]; // how many bits before DMA clock repeats? @@ -203,9 +204,12 @@ export class ANTIC { nextScreen(): number { let b = this.read(this.scanaddr); - this.scanaddr = ((this.scanaddr + 1) & 0xfff) | (this.scanaddr & ~0xfff); + this.incScanAddr(); return b; } + incScanAddr() { + this.scanaddr = ((this.scanaddr + 1) & 0xfff) | (this.scanaddr & ~0xfff); + } dlDMAEnabled() { return this.regs[DMACTL] & 0b100000; } @@ -216,10 +220,10 @@ export class ANTIC { return this.dma_enabled && !this.linesleft; } isPlayerDMAEnabled() { - return this.dma_enabled && this.regs[DMACTL] & 0b1000; + return this.regs[DMACTL] & 0b1000; } isMissileDMAEnabled() { - return this.dma_enabled && this.regs[DMACTL] & 0b1100; + return this.regs[DMACTL] & 0b1100; } clockPulse(): boolean { @@ -280,7 +284,7 @@ export class ANTIC { } this.output = 0; // background color (TODO: only for blank lines) if (this.mode >= 2 && this.period) { - let candma = this.h < 106; + let candma = this.h <= LAST_DMA_H; this.dmaclock <<= 1; if (this.dmaclock & (1 << this.period)) { this.dmaclock |= 1; @@ -289,13 +293,21 @@ export class ANTIC { if (this.h == this.right) { this.dmaclock &= ~1; this.dmaidx++; } if (this.dmaclock & 1) { if (this.mode < 8 && this.yofs == 0) { // only read chars on 1st line - this.linebuf[this.dmaidx] = this.nextScreen(); // read char name + if (candma) { + this.linebuf[this.dmaidx] = this.nextScreen(); // read char name + } else { + this.incScanAddr(); + } did_dma = candma; } this.dmaidx++; } else if (this.dmaclock & 8) { this.ch = this.linebuf[this.dmaidx - 4 / this.period]; // latch char - this.readBitmapData(); // read bitmap + if (candma) { + this.readBitmapData(); // read bitmap + } else { + if (this.mode >= 8) this.incScanAddr(); + } did_dma = candma; } this.output = this.h >= this.left + 3 && this.h <= this.right + 2 ? 4 : 0; diff --git a/src/machine/chips/gtia.ts b/src/machine/chips/gtia.ts index 3d7f593f..81de62ba 100644 --- a/src/machine/chips/gtia.ts +++ b/src/machine/chips/gtia.ts @@ -33,6 +33,8 @@ const P0PL = 0xc; export const TRIG0 = 0x10; export const CONSOL = 0x1f; +const HOFFSET = -9; // bias to account for antic->gtia delay + const PRIOR_TABLE : number[] = [ 0,1,2,3, 7,7,7,7, 8,8,8,8, 4,5,6,7, // 0001 - 0 0,1,2,3, 7,7,7,7, 8,8,8,8, 4,5,6,7, // 0001 @@ -52,6 +54,13 @@ const PRIOR_TABLE : number[] = [ 2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000 ]; +const MODE_9_LOOKUP = [ + COLPM0+0, COLPM0+1, COLPM0+2, COLPM0+3, + COLPF0+0, COLPF0+1, COLPF0+2, COLPF0+3, + COLBK, COLBK, COLBK, COLBK, + COLPF0+0, COLPF0+1, COLPF0+2, COLPF0+3, +] + export class GTIA { regs = new Uint8Array(0x20); readregs = new Uint8Array(0x20); @@ -62,6 +71,9 @@ export class GTIA { an = 0; rgb = 0; pmcol = 0; + gtiacol = 0; + gtiacol2 = 0; + hbias = HOFFSET; reset() { this.regs.fill(0); @@ -77,6 +89,11 @@ export class GTIA { } setReg(a: number, v: number) { switch (a) { + case COLPM0: case COLPM0+1: case COLPM0+2: case COLPM0+3: + case COLPF0: case COLPF0+1: case COLPF0+2: case COLPF0+3: + case COLBK: + v &= 0xfe; // bit 0 unused in color regs + break; case HITCLR: this.readregs.fill(0, 0, 16); return; @@ -93,6 +110,9 @@ export class GTIA { sync() { this.count = 0; } + setBias(b: number) { + this.hbias = HOFFSET + b; + } updateGfx(h: number, data: number) { switch (h) { case 0: @@ -104,32 +124,32 @@ export class GTIA { } } getPlayfieldColor(): number { - switch (this.an) { + // which GTIA mode? + switch (this.regs[PRIOR] >> 6) { + // normal mode case 0: - return COLBK; - case 4: case 5: case 6: case 7: - return COLPF0 + this.an - 4; - case 8: - // combine PF2 hue and PF1 luminance - return (this.regs[COLPF2] & 0xf0) | (this.regs[COLPF1] & 0x0f) | 0x100; + switch (this.an) { + case 0: + return COLBK; + case 4: case 5: case 6: case 7: + return COLPF0 + this.an - 4; + case 8: + // combine PF2 hue and PF1 luminance + return (this.regs[COLPF2] & 0xf0) | (this.regs[COLPF1] & 0x0f) | 0x100; + } + break; + // mode 9 -- 16 luminances + case 1: + return (this.regs[COLBK] & 0xf0) | (this.gtiacol & 0xf) | 0x100; + // mode 10 -- 9 colors from registers + case 2: + return MODE_9_LOOKUP[this.gtiacol]; + // mode 11 -- 16 hues + case 3: + return (this.regs[COLBK] & 0xf) | (this.gtiacol << 4) | 0x100; } return 0x100; // black } - clockPulse1(): void { - this.processPlayerMissile(); - this.clockPulse2(); - this.count++; - } - clockPulse2(): void { - var col: number; - if (this.pmcol >= 0) { - col = this.pmcol; - } else { - let pf = this.getPlayfieldColor(); - col = pf & 0x100 ? pf & 0xff : this.regs[pf]; - } - this.rgb = COLORS_RGBA[col]; - } anySpriteActive() { return this.shiftregs[0] | this.shiftregs[1] | this.shiftregs[2] | this.shiftregs[3] | this.shiftregs[4] | this.shiftregs[5] @@ -144,6 +164,8 @@ export class GTIA { this.pmcol = -1; return; } + // TODO: multiple color player enable + // TODO: gtia, hi-res mode collisions // compute gfx and collisions for players/missiles let priobias = (this.regs[PRIOR] & 15) << 4; // TODO let topprio = PRIOR_TABLE[(this.an & 7) + 8 + priobias]; @@ -175,7 +197,7 @@ export class GTIA { this.readregs[M0PF + i] |= 1 << pfset; } this.readregs[M0PL + i] |= ppmask; - let prio = PRIOR_TABLE[i + 4 + priobias]; + let prio = PRIOR_TABLE[i + priobias]; if (prio < topprio) { topobj = i + 4; topprio = prio; @@ -194,7 +216,7 @@ export class GTIA { shiftObject(i: number) { let bit = (this.shiftregs[i] & 0x80000000) != 0; this.shiftregs[i] <<= 1; - if (this.regs[HPOSP0 + i] - 1 == this.count) { + if (this.regs[HPOSP0 + i] + this.hbias == this.count) { this.triggerObject(i); } return bit; @@ -221,6 +243,30 @@ export class GTIA { this.shiftregs[i] = data; } + clockPulse1(): void { + this.processPlayerMissile(); + this.clockPulse2(); + this.count++; + } + + clockPulse2(): void { + var col: number; + if (this.pmcol >= 0) { + col = this.pmcol; + } else { + let pf = this.getPlayfieldColor(); + col = pf & 0x100 ? pf & 0xff : this.regs[pf]; + } + this.rgb = COLORS_RGBA[col]; + // TODO: hires modes return 8, so other modes wont work + this.gtiacol2 = (this.gtiacol2 << 1) | (this.an >> 3); + } + + clockPulse4() { + // latch GTIA buffer + this.gtiacol = this.gtiacol2 & 15; + } + static stateToLongString(state): string { let s = '' s += `X: ${lpad(state.count, 3)} ANTIC: ${hex(state.an, 1)} PM: ${hex(state.pmcol, 3)}\n`; diff --git a/src/platform/atari8.ts b/src/platform/atari8.ts index 59ca0afb..93978113 100644 --- a/src/platform/atari8.ts +++ b/src/platform/atari8.ts @@ -195,6 +195,7 @@ class Atari5200Platform extends Atari800Platform { /// PLATFORMS['atari8-800.xlmame'] = Atari800MAMEPlatform +PLATFORMS['atari8-800xl.mame'] = Atari800MAMEPlatform // for dithertron PLATFORMS['atari8-5200.mame'] = Atari5200MAMEPlatform PLATFORMS['atari8-800.xlwasm'] = Atari800WASMPlatform PLATFORMS['atari8-800'] = Atari800Platform diff --git a/test/cli/testplatforms.js b/test/cli/testplatforms.js index 662c1dee..23108c61 100644 --- a/test/cli/testplatforms.js +++ b/test/cli/testplatforms.js @@ -367,7 +367,7 @@ describe('Platform Replay', () => { }); }); it('Should run atari5200', async () => { - await testPlatform('atari8-5200', 'acid5200.rom', 1000, (platform, frameno) => { + await testPlatform('atari8-5200', 'acid5200.rom', 1100, (platform, frameno) => { if (frameno == 999) { let s = ''; for (let i=0; i<40; i++) {