mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-02-21 06:29:02 +00:00
atari8: fixes, tests
This commit is contained in:
parent
363691b964
commit
c572834c8c
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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"},
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
BIN
test/roms/atari8-5200/acid5200.rom
Normal file
BIN
test/roms/atari8-5200/acid5200.rom
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user