atari8: fixes, tests

This commit is contained in:
Steven Hugg 2022-09-01 14:27:14 -05:00
parent 363691b964
commit c572834c8c
9 changed files with 123 additions and 51 deletions

View File

@ -171,6 +171,7 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
<li><a class="dropdown-item" href="?platform=zx">ZX Spectrum</a></li>
<li><a class="dropdown-item" href="?platform=x86">x86 (FreeDOS)</a></li>
<li><a class="dropdown-item" href="?platform=cpc.6128">Amstrad CPC6128</a></li>
<li><a class="dropdown-item" href="?platform=atari8-800">Atari 800</a></li>
</ul>
</li>
<li class="dropdown dropdown-submenu">

View File

@ -55,9 +55,6 @@ lower
iny ;Next character
bne prloop
wait
lda $d40b
lda $d20f
sta COLBK
nop
jmp wait
@ -81,12 +78,12 @@ dlist .byte $70,$70 ;16 blank scanlines
dlistend
;Text data
text1 .byte "Hejjo World! "
text1 .byte "Hello World! "
.byte $a1,$a2,$a3
.repeat 40
.repeat 16
.byte 32
.repend
.byte "oops!"
.byte "12345"
.byte 0
;Cartridge footer

View File

@ -400,7 +400,7 @@ export const Keys = {
VK_F12: {c: 123, n: "F12"},
VK_SCROLL_LOCK: {c: 145, n: "ScrLck"},
VK_PAUSE: {c: 19, n: "Pause"},
VK_QUOTE: {c: 192, n: "'"},
VK_QUOTE: {c: 222, n: "'"},
VK_1: {c: 49, n: "1"},
VK_2: {c: 50, n: "2"},
VK_3: {c: 51, n: "3"},
@ -428,8 +428,8 @@ export const Keys = {
VK_O: {c: 79, n: "O"},
VK_P: {c: 80, n: "P"},
VK_ACUTE: {c: 219, n: "´"},
VK_OPEN_BRACKET: {c: 221, n: "["},
VK_CLOSE_BRACKET: {c: 220, n: "]"},
VK_OPEN_BRACKET: {c: 219, n: "["},
VK_CLOSE_BRACKET: {c: 221, n: "]"},
VK_CAPS_LOCK: {c: 20, n: "CpsLck"},
VK_A: {c: 65, n: "A"},
VK_S: {c: 83, n: "S"},
@ -444,7 +444,7 @@ export const Keys = {
VK_TILDE: {c: 222, n: "~"},
VK_ENTER: {c: 13, n: "Enter"},
VK_SHIFT: {c: 16, n: "Shift"},
VK_BACK_SLASH: {c: 226, n: "\\"},
VK_BACK_SLASH: {c: 220, n: "\\"},
VK_Z: {c: 90, n: "Z"},
VK_X: {c: 88, n: "X"},
VK_C: {c: 67, n: "C"},
@ -454,8 +454,8 @@ export const Keys = {
VK_M: {c: 77, n: "M"},
VK_COMMA: {c: 188, n: "] ="},
VK_PERIOD: {c: 190, n: "."},
VK_SEMICOLON: {c: 191, n: ";"},
VK_SLASH: {c: 193, n: "/"},
VK_SEMICOLON: {c: 59, n: ";"},
VK_SLASH: {c: 191, n: "/"},
VK_CONTROL: {c: 17, n: "Ctrl"},
VK_ALT: {c: 18, n: "Alt"},
VK_SPACE: {c: 32, n: "Space"},

View File

@ -11,12 +11,12 @@ 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_O, null, Keys.VK_P, Keys.VK_U, Keys.VK_ENTER, Keys.VK_I, Keys.VK_MINUS, Keys.VK_EQUALS,
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_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,
Keys.VK_9, null, Keys.VK_0, Keys.VK_7, Keys.VK_BACK_SPACE, Keys.VK_8, Keys.VK_LEFT, Keys.VK_RIGHT,
Keys.VK_9, null, Keys.VK_0, Keys.VK_7, Keys.VK_BACK_SPACE, Keys.VK_8, null, null,
Keys.VK_F, Keys.VK_H, Keys.VK_D, null, Keys.VK_CAPS_LOCK, Keys.VK_G, Keys.VK_S, Keys.VK_A,
];
@ -34,24 +34,23 @@ var ATARI8_KEYCODE_MAP = makeKeycodeMap([
[Keys.P2_RIGHT, 0, 0x80],
[Keys.P2_A, 3, 0x1],
*/
[Keys.START, 3, 0x1],
[Keys.SELECT, 3, 0x2],
[Keys.VK_F6, 3, 0x4],
[Keys.START, 3, 0x1], // START
[Keys.SELECT, 3, 0x2], // SELECT
[Keys.OPTION, 3, 0x4], // OPTION
]);
export class Atari800 extends BasicScanlineMachine {
// http://www.ataripreservation.org/websites/freddy.offenga/megazine/ISSUE5-PALNTSC.html
cpuFrequency = 1789773;
numTotalScanlines = 262;
cpuCyclesPerLine = 114;
canvasWidth = 352; // TODO?
canvasWidth = 348; // TODO?
numVisibleScanlines = 224;
aspectRatio = 240 / 172;
firstVisibleClock = 36 * 2; // TODO?
numVisibleScanlines = 250;
firstVisibleScanline = 16;
firstVisibleClock = 44 * 2; // ... to 215 * 2
// TODO: for 400/800/5200
defaultROMSize = 0x8000;
overscan = true;
@ -167,9 +166,11 @@ export class Atari800 extends BasicScanlineMachine {
//if (this.cpu.isHalted()) throw new EmuHalt("CPU HALTED");
// set GTIA switch inputs
this.gtia.sync();
// TODO: trigger latching mode
for (let i = 0; i < 4; i++)
this.gtia.readregs[TRIG0 + i] = (~this.inputs[2] >> i) & 1;
this.gtia.readregs[CONSOL] = ~this.inputs[3] & this.gtia.regs[CONSOL];
// console switches
this.gtia.readregs[CONSOL] = ~this.inputs[3] & 0x7;
// advance POKEY audio
this.audio && this.audioadapter.generate(this.audio);
// advance POKEY IRQ timers
@ -178,8 +179,9 @@ export class Atari800 extends BasicScanlineMachine {
drawScanline() {
// TODO
if (this.antic.v < this.numVisibleScanlines) {
this.pixels.set(this.linergb, this.antic.v * this.canvasWidth);
let y = this.antic.v - this.firstVisibleScanline;
if (y >= 0 && y < this.numVisibleScanlines) {
this.pixels.set(this.linergb, y * this.canvasWidth);
}
}
@ -193,6 +195,11 @@ export class Atari800 extends BasicScanlineMachine {
super.advanceCPU();
}
// 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();
this.linergb[xofs++] = this.gtia.rgb;
@ -201,7 +208,7 @@ export class Atari800 extends BasicScanlineMachine {
this.gtia.clockPulse2();
this.linergb[xofs++] = this.gtia.rgb;
}
let xofs = this.antic.h * 4 - this.firstVisibleClock;
// tick 4 GTIA clocks for each CPU/ANTIC cycle
let bp = MODE_SHIFT[this.antic.mode];
if (bp < 8 || (xofs & 4) == 0) { this.gtia.an = this.antic.shiftout(); }
gtiatick1();
@ -263,6 +270,7 @@ export class Atari800 extends BasicScanlineMachine {
getKeyboardFunction() {
return (o, key, code, flags) => {
if (flags & (KeyFlags.KeyDown | KeyFlags.KeyUp)) {
//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
@ -276,7 +284,6 @@ export class Atari800 extends BasicScanlineMachine {
if (flags & KeyFlags.KeyDown) {
this.keycode |= 0x100;
this.irq_pokey.generateIRQ(0x40); // key pressed IRQ
console.log(o, key, code, flags, hex(this.keycode));
return true;
}
}
@ -294,6 +301,8 @@ export class Atari800 extends BasicScanlineMachine {
}
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.");
// TODO: support other than 8 KB carts
// support 4/8/16/32 KB carts
let rom2 = new Uint8Array(0x8000);

View File

@ -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, 26, 18, 10];
const PF_RIGHT = [0, 26 + 64, 18 + 80, 10 + 96];
const PF_LEFT = [0, 29, 21, 13];
const PF_RIGHT = [0, 29 + 64, 21 + 80, 13 + 96];
const DMACTL = 0;
const CHACTL = 1;
@ -38,6 +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 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?
const MODE_PERIOD = [0, 0, 2, 2, 2, 2, 4, 4, 8, 4, 4, 4, 4, 2, 2, 2];
@ -133,7 +136,7 @@ export class ANTIC {
}
processDLIEntry() {
if (this.mode == 0) { // N Blank Lines
this.linesleft = (this.dliop >> 4) + 1;
this.linesleft = ((this.dliop >> 4) & 7) + 1;
} else {
this.linesleft = MODE_LINES[this.mode];
this.period = MODE_PERIOD[this.mode];
@ -153,7 +156,6 @@ export class ANTIC {
this.startaddr = this.scanaddr;
}
// horiz scroll
// TODO: gtia fine scroll?
let effwidth = this.regs[DMACTL] & 3;
let hscroll = (this.dliop & 0x10) ? (this.regs[HSCROL] & 15) >> 1 : 0;
if ((this.dliop & 0x10) && effwidth < 3) effwidth++;
@ -299,28 +301,29 @@ export class ANTIC {
this.output = this.h >= this.left + 3 && this.h <= this.right + 2 ? 4 : 0;
}
}
if (this.h < 19 || this.h > 102) this.output = 2;
if (this.h < ANTIC_LEFT || this.h > ANTIC_RIGHT) this.output = 2;
this.incHorizCounter();
if (!did_dma && this.dramrefresh) {
this.read(0); // to log a VRAM_READ event
this.dramrefresh = false;
did_dma = true;
}
return did_dma;
}
incHorizCounter() {
++this.h;
switch (this.h) {
case 25: case 25 + 4 * 1: case 25 + 4 * 2: case 25 + 4 * 3: case 25 + 4 * 4:
case 25 + 4 * 5: case 25 + 4 * 6: case 25 + 4 * 7: case 25 + 4 * 8:
this.dramrefresh = true;
break;
case 103:
case 102:
this.regs[WSYNC] = 0; // TODO: dram refresh delay to 106?
break;
case 114:
case 113:
this.h = 0;
break;
return
}
++this.h;
}
doVBlank() {
this.linesleft = this.mode = this.period = 0;

View File

@ -21,7 +21,7 @@ const COLPF2 = 0x18;
const COLPF3 = 0x19;
const COLBK = 0x1a;
const PRIOR = 0x1b;
const VDELAY = 0x1c;
const VDELAY = 0x1c; // TODO
const GRACTL = 0x1d;
const HITCLR = 0x1e;
const CONSPK = 0x1f;
@ -33,10 +33,30 @@ const P0PL = 0xc;
export const TRIG0 = 0x10;
export const CONSOL = 0x1f;
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
0,1,6,7, 5,5,5,5, 8,8,8,8, 2,3,4,5, // 0010 - 2
0,1,6,7, 5,5,5,5, 8,8,8,8, 2,3,4,5, // 0010
4,5,6,7, 3,3,3,3, 8,8,8,8, 0,1,2,3, // 0100 - 4
4,5,6,7, 3,3,3,3, 8,8,8,8, 0,1,2,3, // 0100
4,5,6,7, 3,3,3,3, 8,8,8,8, 0,1,2,3, // 0100
4,5,6,7, 3,3,3,3, 8,8,8,8, 0,1,2,3, // 0100
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000 - 8
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000
2,3,4,5, 7,7,7,7, 8,8,8,8, 0,1,6,7, // 1000
];
export class GTIA {
regs = new Uint8Array(0x20);
readregs = new Uint8Array(0x20);
shiftregs = new Uint32Array(8);
priortab = new Uint8Array(12);
count = 0;
an = 0;
@ -57,18 +77,17 @@ export class GTIA {
}
setReg(a: number, v: number) {
switch (a) {
case CONSOL:
v = (v & 15) ^ 15; // 0 = input, 1 = pull down
break;
case HITCLR:
for (let i = 0; i < 16; i++) {
this.readregs[i] = 0;
}
this.readregs.fill(0, 0, 16);
return;
}
this.regs[a] = v;
}
readReg(a: number) {
switch (a) {
case CONSOL:
return this.readregs[a] & ~this.regs[CONSPK];
}
return this.readregs[a];
}
sync() {
@ -111,10 +130,27 @@ export class GTIA {
}
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]
| this.shiftregs[6] | this.shiftregs[7];
}
processPlayerMissile() {
let topobj = -1;
// no p/m gfx, no collisions in blank area, but shift and trigger anyway
if (this.an == 2 || !this.anySpriteActive()) {
for (let i = 0; i < 8; i++) {
this.shiftObject(i);
}
this.pmcol = -1;
return;
}
// compute gfx and collisions for players/missiles
let priobias = (this.regs[PRIOR] & 15) << 4; // TODO
let topprio = PRIOR_TABLE[(this.an & 7) + 8 + priobias];
let pfset = this.an - 4; // TODO?
let topobj = -1;
let ppmask = 0;
let ppcount = 0;
// players
for (let i = 0; i < 4; i++) {
let bit = this.shiftObject(i);
@ -123,13 +159,14 @@ export class GTIA {
this.readregs[P0PF + i] |= 1 << pfset;
}
ppmask |= 1 << i;
topobj = i;
ppcount++;
let prio = PRIOR_TABLE[i + priobias];
if (prio < topprio) {
topobj = i;
topprio = prio;
}
}
}
this.readregs[P0PL + 0] |= ppmask & ~1;
this.readregs[P0PL + 1] |= ppmask & ~2;
this.readregs[P0PL + 2] |= ppmask & ~4;
this.readregs[P0PL + 3] |= ppmask & ~8;
// missiles
for (let i = 0; i < 4; i++) {
let bit = this.shiftObject(i + 4);
@ -138,15 +175,26 @@ export class GTIA {
this.readregs[M0PF + i] |= 1 << pfset;
}
this.readregs[M0PL + i] |= ppmask;
topobj = i + 4;
let prio = PRIOR_TABLE[i + 4 + priobias];
if (prio < topprio) {
topobj = i + 4;
topprio = prio;
}
}
}
// set player-player collision flags
if (ppcount > 1) {
this.readregs[P0PL + 0] |= ppmask & ~1;
this.readregs[P0PL + 1] |= ppmask & ~2;
this.readregs[P0PL + 2] |= ppmask & ~4;
this.readregs[P0PL + 3] |= ppmask & ~8;
}
this.pmcol = topobj >= 0 ? this.getObjectColor(topobj) : -1;
}
shiftObject(i: number) {
let bit = (this.shiftregs[i] & 0x80000000) != 0;
this.shiftregs[i] <<= 1;
if (this.regs[HPOSP0 + i] - 7 == this.count) {
if (this.regs[HPOSP0 + i] - 1 == this.count) {
this.triggerObject(i);
}
return bit;

View File

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

Binary file not shown.

Binary file not shown.