atari8: wait/dma events, new kbd

This commit is contained in:
Steven Hugg 2022-09-03 15:00:43 -05:00
parent 56e8fca270
commit 5735135add
13 changed files with 87 additions and 39 deletions

View File

@ -591,6 +591,8 @@ div.asset_toolbar {
}
.control-def {
color: #ccc;
white-space: nowrap;
line-height:2em;
}
.book-title {
font-size:12pt;

View File

@ -373,8 +373,9 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
<div class="emucontrols-atari8 text-center small control-insns" style="display:none">
<span class="control-def"><span class="control-key">&#x2190;&#x2191;&#x2193;&#x2192;</span> Joystick</span>
<span class="control-def"><span class="control-key">Shift</span> Button</span>
<span class="control-def"><span class="control-key">Enter</span> Start</span>
<span class="control-def"><span class="control-key">\</span> Select</span>
<span class="control-def"><span class="control-key">F1</span> Start</span>
<span class="control-def"><span class="control-key">F2</span> Select</span>
<span class="control-def"><span class="control-key">F3</span> Option</span>
</div>
<!-- -->
<div id="emuoverlay" class="emuoverlay" style="display:none">

View File

@ -17,8 +17,6 @@ import { _MOS6502 } from "./cpu/MOS6502";
///
declare var jt;
export interface OpcodeMetadata {
minCycles: number;
maxCycles: number;

View File

@ -156,11 +156,14 @@ export interface ProbeCPU {
logExecute(address:number, SP:number);
logInterrupt(type:number);
logIllegal(address:number);
logWait(address:number);
}
export interface ProbeBus {
logRead(address:number, value:number);
logWrite(address:number, value:number);
logDMARead(address:number, value:number);
logDMAWrite(address:number, value:number);
}
export interface ProbeIO {
@ -191,6 +194,9 @@ export class NullProbe implements ProbeAll {
logVRAMRead() {}
logVRAMWrite() {}
logIllegal() {}
logWait() {}
logDMARead() {}
logDMAWrite() {}
logData() {}
addLogBuffer(src: Uint32Array) {}
}
@ -296,6 +302,19 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
}
};
}
probeDMABus(iobus:Bus) : Bus {
return {
read: (a) => {
let val = iobus.read(a);
this.probe.logDMARead(a,val);
return val;
},
write: (a,v) => {
this.probe.logDMAWrite(a,v);
iobus.write(a,v);
}
};
}
connectCPUIOBus(iobus:Bus) : void {
this.cpu['connectIOBus'](this.probeIOBus(iobus));
}

View File

@ -3,9 +3,6 @@ import { hex, clamp, lpad, RGBA } from "./util";
import { SourceLocation } from "./workertypes";
import { VirtualList } from "./vlist"
// external modules
declare var jt, Javatari;
// Emulator classes
export var PLATFORMS = {};
@ -52,12 +49,9 @@ export enum KeyFlags {
export function _setKeyboardEvents(canvas:HTMLElement, callback:KeyboardCallback) {
canvas.onkeydown = (e) => {
callback(e.which, 0, KeyFlags.KeyDown|_metakeyflags(e));
// eat backspace, tab, escape, slash, ' keys
if (e.ctrlKey || e.which == 8 || e.which == 9 || e.which == 27
|| e.which == 191 || e.which == 191 || e.which == 222) {
e.preventDefault();
}
let flags = _metakeyflags(e);
callback(e.which, 0, KeyFlags.KeyDown|flags);
if (!flags) e.preventDefault(); // eat all keys that don't have a modifier
};
canvas.onkeyup = (e) => {
callback(e.which, 0, KeyFlags.KeyUp|_metakeyflags(e));
@ -370,7 +364,7 @@ export const Keys = {
GP_D: {c: 67, n: "C", plyr:0, button:3},
SELECT: {c: 220, n: "\\", plyr:0, button:8},
START: {c: 13, n: "Enter", plyr:0, button:9},
OPTION: {c: 9, n: "Tab", plyr:0, button:10},
OPTION: {c: 8, n: "Bcksp", plyr:0, button:10},
// gamepad and keyboard (player 1)
P2_UP: {c: 87, n: "W", plyr:1, yaxis:-1},
P2_DOWN: {c: 83, n: "S", plyr:1, yaxis:1},
@ -458,6 +452,7 @@ export const Keys = {
VK_SLASH: {c: 191, n: "/"},
VK_CONTROL: {c: 17, n: "Ctrl"},
VK_ALT: {c: 18, n: "Alt"},
VK_COMMAND: {c: 224, n: "Cmd"},
VK_SPACE: {c: 32, n: "Space"},
VK_INSERT: {c: 45, n: "Ins"},
VK_DELETE: {c: 46, n: "Del"},

View File

@ -4,6 +4,10 @@ import { Probeable, ProbeAll } from "./devices";
export enum ProbeFlags {
CLOCKS = 0x00000000,
EXECUTE = 0x01000000,
INTERRUPT = 0x08000000,
ILLEGAL = 0x09000000,
SP_PUSH = 0x0a000000,
SP_POP = 0x0b000000,
HAS_VALUE = 0x10000000,
MEM_READ = 0x12000000,
MEM_WRITE = 0x13000000,
@ -11,10 +15,9 @@ export enum ProbeFlags {
IO_WRITE = 0x15000000,
VRAM_READ = 0x16000000,
VRAM_WRITE= 0x17000000,
INTERRUPT = 0x08000000,
ILLEGAL = 0x09000000,
SP_PUSH = 0x0a000000,
SP_POP = 0x0b000000,
DMA_READ = 0x18000000,
DMA_WRITE = 0x19000000,
WAIT = 0x1f000000,
SCANLINE = 0x7e000000,
FRAME = 0x7f000000,
}
@ -140,6 +143,15 @@ export class ProbeRecorder implements ProbeAll {
logIllegal(address:number) {
this.log(address | ProbeFlags.ILLEGAL);
}
logWait(address:number) {
this.log(address | ProbeFlags.WAIT);
}
logDMARead(address:number, value:number) {
this.logValue(address, value, ProbeFlags.DMA_READ);
}
logDMAWrite(address:number, value:number) {
this.logValue(address, value, ProbeFlags.DMA_WRITE);
}
countEvents(op : number) : number {
var count = 0;
for (var i=0; i<this.idx; i++) {

View File

@ -402,8 +402,11 @@ export abstract class ProbeViewBaseBase {
case ProbeFlags.IO_WRITE: s = "IO Write"; break;
case ProbeFlags.VRAM_READ: s = "VRAM Read"; break;
case ProbeFlags.VRAM_WRITE: s = "VRAM Write"; break;
case ProbeFlags.DMA_READ: s = "DMA Read"; break;
case ProbeFlags.DMA_WRITE: s = "DMA Write"; break;
case ProbeFlags.INTERRUPT: s = "Interrupt"; break;
case ProbeFlags.ILLEGAL: s = "Error"; break;
case ProbeFlags.WAIT: s = "Wait"; break;
case ProbeFlags.SP_PUSH: s = "Stack Push"; break;
case ProbeFlags.SP_POP: s = "Stack Pop"; break;
default: return "";
@ -420,10 +423,13 @@ export abstract class ProbeViewBaseBase {
case ProbeFlags.MEM_WRITE: return 0x010180;
case ProbeFlags.IO_READ: return 0x018080;
case ProbeFlags.IO_WRITE: return 0xc00180;
case ProbeFlags.DMA_READ:
case ProbeFlags.VRAM_READ: return 0x808001;
case ProbeFlags.DMA_WRITE:
case ProbeFlags.VRAM_WRITE: return 0x4080c0;
case ProbeFlags.INTERRUPT: return 0x3fbf3f;
case ProbeFlags.ILLEGAL: return 0x3f3fff;
case ProbeFlags.WAIT: return 0xff3f3f;
default: return 0;
}
}
@ -688,8 +694,9 @@ export class RasterStackMapView extends RasterPCHeatMapView implements ProjectVi
if (op == ProbeFlags.VRAM_WRITE) { this.rgb |= 0x003f80; }
if (op == ProbeFlags.IO_WRITE) { this.rgb |= 0x1f3f80; }
if (op == ProbeFlags.IO_READ) { this.rgb |= 0x001f00; }
if (op == ProbeFlags.WAIT) { this.rgb = 0x008000; }
// draw pixels?
if (op == ProbeFlags.ILLEGAL || op == ProbeFlags.VRAM_READ) {
if (op == ProbeFlags.ILLEGAL || op == ProbeFlags.DMA_READ) {
this.datau32[iofs] = 0xff0f0f0f;
} else {
let data = this.rgb;

View File

@ -323,7 +323,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
read : (a:number) => number;
write : (a:number, v:number) => void;
probeDMABus : Bus; // to pass to MARIA
dmaBus : Bus; // to pass to MARIA
constructor() {
super();
@ -355,7 +355,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
[0x0000, 0xffff, 0xffff, (a,v) => { this.probe && this.probe.logIllegal(a); }],
]);
this.connectCPUMemoryBus(this);
this.probeDMABus = this.probeIOBus(this);
this.dmaBus = this.probeDMABus(this);
this.handler = newKeyboardHandler(this.inputs, Atari7800_KEYCODE_MAP);
this.pokey1 = new POKEYDeviceChannel();
this.audioadapter = new TssChannelAdapter(this.pokey1, audioOversample, audioSampleRate);
@ -417,7 +417,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
// is this scanline visible?
if (visible) {
// do DMA for scanline?
let dmaClocks = this.maria.doDMA(this.probeDMABus);
let dmaClocks = this.maria.doDMA(this.dmaBus);
this.probe.logClocks(dmaClocks >> 2); // TODO: logDMA
mc += dmaClocks;
// copy line to frame buffer
@ -435,6 +435,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
// post-DMA clocks
while (mc < colorClocksPerLine) {
if (this.maria.WSYNC) {
this.probe.logWait(0);
this.probe.logClocks((colorClocksPerLine - mc) >> 2);
mc = colorClocksPerLine;
break;

View File

@ -10,9 +10,9 @@ import { CONSOL, GTIA, TRIG0 } from "./chips/gtia";
import { POKEY } from "./chips/pokey";
const ATARI8_KEYMATRIX_INTL_NOSHIFT = [
Keys.VK_L, Keys.VK_J, Keys.VK_SEMICOLON, Keys.VK_F1, Keys.VK_F2, Keys.VK_K, Keys.VK_BACK_SLASH, Keys.VK_TILDE,
Keys.VK_L, Keys.VK_J, Keys.VK_SEMICOLON, Keys.VK_F4, Keys.VK_F5, Keys.VK_K, Keys.VK_BACK_SLASH, Keys.VK_TILDE,
Keys.VK_O, null, Keys.VK_P, Keys.VK_U, Keys.VK_ENTER, Keys.VK_I, Keys.VK_MINUS2, Keys.VK_EQUALS2,
Keys.VK_V, Keys.VK_F8, Keys.VK_C, Keys.VK_F3, Keys.VK_F4, Keys.VK_B, Keys.VK_X, Keys.VK_Z,
Keys.VK_V, Keys.VK_F7, Keys.VK_C, Keys.VK_F6, Keys.VK_F4, Keys.VK_B, Keys.VK_X, Keys.VK_Z,
Keys.VK_4, null, Keys.VK_3, Keys.VK_6, Keys.VK_ESCAPE, Keys.VK_5, Keys.VK_2, Keys.VK_1,
Keys.VK_COMMA, Keys.VK_SPACE, Keys.VK_PERIOD, Keys.VK_N, null, Keys.VK_M, Keys.VK_SLASH, null/*invert*/,
Keys.VK_R, null, Keys.VK_E, Keys.VK_Y, Keys.VK_TAB, Keys.VK_T, Keys.VK_W, Keys.VK_Q,
@ -26,7 +26,7 @@ var ATARI8_KEYCODE_MAP = makeKeycodeMap([
[Keys.DOWN, 0, 0x2],
[Keys.LEFT, 0, 0x4],
[Keys.RIGHT, 0, 0x8],
[Keys.VK_SHIFT, 2, 0x1],
[{c: 16, n: "Shift", plyr:0, button:0}, 2, 0x1],
/*
[Keys.P2_UP, 0, 0x10],
[Keys.P2_DOWN, 0, 0x20],
@ -34,9 +34,9 @@ var ATARI8_KEYCODE_MAP = makeKeycodeMap([
[Keys.P2_RIGHT, 0, 0x80],
[Keys.P2_A, 3, 0x1],
*/
[Keys.START, 3, 0x1], // START
[Keys.SELECT, 3, 0x2], // SELECT
[Keys.OPTION, 3, 0x4], // OPTION
[Keys.VK_F1, 3, 0x1], // START
[Keys.VK_F2, 3, 0x2], // SELECT
[Keys.VK_F3, 3, 0x4], // OPTION
]);
@ -72,6 +72,7 @@ export class Atari800 extends BasicScanlineMachine {
cart_80 = false;
cart_a0 = false;
xexdata = null;
keyboard_active = true;
// TODO: save/load vars
constructor() {
@ -89,7 +90,7 @@ export class Atari800 extends BasicScanlineMachine {
this.audioadapter = new TssChannelAdapter(this.audio_pokey.pokey1, this.audioOversample, this.sampleRate);
this.handler = newKeyboardHandler(
this.inputs, ATARI8_KEYCODE_MAP, this.getKeyboardFunction(), true);
}
}
newBus() {
return {
@ -134,7 +135,7 @@ export class Atari800 extends BasicScanlineMachine {
// used by ANTIC
readDMA(a) {
let v = this.bus.read(a);
this.probe.logVRAMRead(a, v);
this.probe.logDMARead(a, v);
this.lastdmabyte = v;
return v;
}
@ -192,6 +193,8 @@ export class Atari800 extends BasicScanlineMachine {
// ANTIC DMA cycle, update GTIA
if (this.antic.h < 8)
this.gtia.updateGfx(this.antic.h - 1, this.antic.v, this.lastdmabyte); // HALT pin
if (this.antic.isWSYNC())
this.probe.logWait(0);
this.probe.logClocks(1);
} else {
super.advanceCPU();
@ -278,8 +281,9 @@ export class Atari800 extends BasicScanlineMachine {
}
getKeyboardFunction() {
return (o, key, code, flags) => {
if (!this.keyboard_active) return false;
if (flags & (KeyFlags.KeyDown | KeyFlags.KeyUp)) {
//console.log(o, key, code, flags, hex(this.keycode));
console.log(o, key, code, flags, hex(this.keycode));
var keymap = ATARI8_KEYMATRIX_INTL_NOSHIFT;
if (key == Keys.VK_F9.c) {
this.irq_pokey.generateIRQ(0x80); // break IRQ

View File

@ -109,7 +109,9 @@ export class ANTIC {
let s = "";
s += "H: " + lpad(state.h, 3) + " V: " + lpad(state.v, 3) + "\n";
s += "DLIOp: " + hex(state.dliop, 2) + " Lines: " + state.yofs + "/" + state.linesleft;
s += " DMA " + (state.dma_enabled ? "ON " : "off") + "\n";
s += " DMA " + (state.dma_enabled ? "ON " : "off")
if (state.dma_enabled) s += " idx " + state.dmaidx + " clk " + hex(state.dmaclock)
s += "\n"
s += "Addr: " + hex(state.scanaddr, 4) + "\n";
s += dumpRAM(state.regs, 0, 16).replace('$00', 'Regs');
return s;
@ -138,6 +140,7 @@ export class ANTIC {
processDLIEntry() {
if (this.mode == 0) { // N Blank Lines
this.linesleft = ((this.dliop >> 4) & 7) + 1;
this.dmaclock = 0;
} else {
this.linesleft = MODE_LINES[this.mode];
this.period = MODE_PERIOD[this.mode];
@ -150,6 +153,7 @@ export class ANTIC {
this.linesleft = 1; //(248 - this.v) & 0xff; // TODO?
this.dma_enabled = false;
}
this.dmaclock = 0;
} else if (this.lms) {
this.scanaddr = this.dlarg_lo + (this.dlarg_hi << 8);
//console.log('scanaddr', hex(this.scanaddr));
@ -225,9 +229,12 @@ export class ANTIC {
isMissileDMAEnabled() {
return this.regs[DMACTL] & 0b1100;
}
isWSYNC() {
return this.regs[WSYNC] != 0;
}
clockPulse(): boolean {
let did_dma = this.regs[WSYNC] != 0;
let did_dma = this.isWSYNC();
if (!this.isVisibleScanline()) {
this.doVBlank();
} else {
@ -285,7 +292,7 @@ export class ANTIC {
this.output = 0; // background color (TODO: only for blank lines)
if (this.mode >= 2 && this.period) {
let candma = this.h <= LAST_DMA_H;
this.dmaclock <<= 1;
this.dmaclock = (this.dmaclock << 1) & 0x1ff;
if (this.dmaclock & (1 << this.period)) {
this.dmaclock |= 1;
}
@ -346,6 +353,7 @@ export class ANTIC {
this.dma_enabled = this.dlDMAEnabled() != 0;
}
this.output = 2; // blank
this.dmaclock = 0;
}
doPlayerMissileDMA(section: number) {

View File

@ -80,6 +80,7 @@ export class GTIA {
this.regs.fill(0);
this.readregs.fill(0); // TODO?
this.readregs[0x14] = 0xf; // NTSC
this.readregs.fill(0xf, 0x15); // default value for write-only regs
this.count = 0;
}
saveState() {

View File

@ -397,7 +397,7 @@ class VCSPlatform extends BasePlatform {
bus.oldWrite = bus.write;
bus.write = function(a,v) {
this.oldWrite(a,v);
if (a == 0x02) probe.logIllegal(a); // WSYNC
if (a == 0x02) probe.logWait(a); // WSYNC
else if (a < 0x80) probe.logIOWrite(a,v);
else if (a > 0x280 && a < 0x300) probe.logIOWrite(a,v);
else probe.logWrite(a,v);

View File

@ -367,16 +367,16 @@ describe('Platform Replay', () => {
});
});
it('Should run atari5200', async () => {
await testPlatform('atari8-5200', 'acid5200.rom', 1100, (platform, frameno) => {
if (frameno == 999) {
await testPlatform('atari8-5200', 'acid5200.rom', 1200, (platform, frameno) => {
if (frameno == 1199) {
let s = '';
for (let i=0; i<40; i++) {
let c = platform.readAddress(0x722+i) & 0x7f;
let c = platform.readAddress(0x722+i-40) & 0x7f;
if (c < 0x40) c += 0x20;
s += String.fromCharCode(c);
}
s = s.trim();
assert.equal(s, "Passed: 12 Failed: 34 Skipped: 1");
assert.equal(s, "Passed: 13 Failed: 33 Skipped: 1");
}
});
});