mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
6923d43873
* Update jest to v29.5.0 This updates jest to the latest version (29.5.0) and fixes everything that the upgrade breaks. One of the biggest differences is that the mock types changed and I'm once again confused as to the "proper" way to create mocks in jest. Whatever. * Fix issue #187 were bank writing was not working correctly Before, `mmu.ts` would deactivate writing to the language card if `prewrite` was reset. However, it is totally possible to reset `prewrite` and leave writing enabled, as shown my Sather in _Understanding the Apple IIe_, table 5.5, p. 5-24. For example: ```assembly_x86 sta $c08a ; WRITE DISABLE; READ DISABLE lda $c08b ; PRE-WRITE set lda $c08b ; WRITE enabled sta $c08a ; PRE-WRITE reset; WRITE still enabled! lda $c08b ; PRE-WRITE set; WRITE still enabled! ``` would not work correctly because the last line would clear `_writebsr` before setting `_prewrite`, which is incorrect. Now, `_writebsr` is only set when `_prewrite` is set and thus only cleared when `writeSwitch` is false. This matches Table 5.5. * Fix pre-write for the language card This is the same issue as the `MMU`, namely that `langcard.ts` would deactivate writing to the language card if `prewrite` was reset. However, it is totally possible to reset `prewrite` and leave writing enabled, as shown my Sather in _Understanding the Apple II_, table 5.4, p. 5-30. See the previous commit for an example. This change also adds a test for the `LanguageCard` class.
922 lines
27 KiB
TypeScript
922 lines
27 KiB
TypeScript
import CPU6502 from './cpu6502';
|
|
import RAM, { RAMState } from './ram';
|
|
import ROM, { ROMState } from './roms/rom';
|
|
import { debug } from './util';
|
|
import { byte, Memory, Restorable } from './types';
|
|
import Apple2IO from './apple2io';
|
|
import { HiresPage, LoresPage, VideoModes } from './videomodes';
|
|
|
|
/*
|
|
* I/O Switch locations
|
|
*/
|
|
const LOC = {
|
|
// 80 Column memory
|
|
_80STOREOFF: 0x00,
|
|
_80STOREON: 0x01,
|
|
|
|
// Aux RAM
|
|
RAMRDOFF: 0x02,
|
|
RAMRDON: 0x03,
|
|
|
|
RAMWROFF: 0x04,
|
|
RAMWRON: 0x05,
|
|
|
|
// Bank switched ROM
|
|
INTCXROMOFF: 0x06,
|
|
INTCXROMON: 0x07,
|
|
ALTZPOFF: 0x08,
|
|
ALTZPON: 0x09,
|
|
SLOTC3ROMOFF: 0x0A,
|
|
SLOTC3ROMON: 0x0B,
|
|
|
|
// 80 Column video
|
|
CLR80VID: 0x0C, // clear 80 column mode
|
|
SET80VID: 0x0D, // set 80 column mode
|
|
CLRALTCH: 0x0E, // clear mousetext
|
|
SETALTCH: 0x0F, // set mousetext
|
|
|
|
// Status
|
|
BSRBANK2: 0x11,
|
|
BSRREADRAM: 0x12,
|
|
RAMRD: 0x13,
|
|
RAMWRT: 0x14,
|
|
INTCXROM: 0x15,
|
|
ALTZP: 0x16,
|
|
SLOTC3ROM: 0x17,
|
|
_80STORE: 0x18,
|
|
VERTBLANK: 0x19,
|
|
|
|
RDTEXT: 0x1A, // using text mode
|
|
RDMIXED: 0x1B, // using mixed mode
|
|
RDPAGE2: 0x1C, // using text/graphics page2
|
|
RDHIRES: 0x1D, // using Hi-res graphics mode
|
|
RDALTCH: 0x1E, // using alternate character set
|
|
RD80VID: 0x1F, // using 80-column display mode
|
|
|
|
// Graphics
|
|
PAGE1: 0x54, // select text/graphics page1 main/aux
|
|
PAGE2: 0x55, // select text/graphics page2 main/aux
|
|
RESET_HIRES: 0x56,
|
|
SET_HIRES: 0x57,
|
|
|
|
DHIRESON: 0x5E, // Enable double hires (CLRAN3)
|
|
DHIRESOFF: 0x5F, // Disable double hires (SETAN3)
|
|
|
|
// Misc
|
|
BANK: 0x73, // Back switched RAM card bank
|
|
|
|
IOUDISON: 0x7E, // W IOU Disable on / R7 IOU Disable
|
|
IOUDISOFF: 0x7F, // W IOU Disable off / R7 Double Hires
|
|
|
|
// Language Card
|
|
|
|
// Bank 2
|
|
READBSR2: 0x80,
|
|
WRITEBSR2: 0x81,
|
|
OFFBSR2: 0x82,
|
|
READWRBSR2: 0x83,
|
|
|
|
// Bank 1
|
|
READBSR1: 0x88,
|
|
WRITEBSR1: 0x89,
|
|
OFFBSR1: 0x8a,
|
|
READWRBSR1: 0x8b,
|
|
};
|
|
|
|
class Switches implements Memory {
|
|
constructor(private mmu: MMU) {}
|
|
|
|
read(_page: byte, off: byte) {
|
|
return this.mmu._access(off) || 0;
|
|
}
|
|
|
|
write(_page: byte, off: byte, val: byte) {
|
|
this.mmu._access(off, val);
|
|
}
|
|
}
|
|
|
|
class AuxRom implements Memory {
|
|
constructor(
|
|
private readonly mmu: MMU,
|
|
private readonly rom: ROM) { }
|
|
|
|
_access(page: byte, off: byte) {
|
|
if (page === 0xc3) {
|
|
this.mmu._setIntc8rom(true);
|
|
this.mmu._updateBanks();
|
|
}
|
|
if (page === 0xcf && off === 0xff) {
|
|
this.mmu._setIntc8rom(false);
|
|
this.mmu._updateBanks();
|
|
}
|
|
}
|
|
|
|
read(page: byte, off: byte) {
|
|
this._access(page, off);
|
|
return this.rom.read(page, off);
|
|
}
|
|
|
|
write(page: byte, off: byte, _val: byte) {
|
|
this._access(page, off);
|
|
}
|
|
}
|
|
|
|
export interface MMUState {
|
|
bank1: boolean;
|
|
readbsr: boolean;
|
|
writebsr: boolean;
|
|
prewrite: boolean;
|
|
|
|
intcxrom: boolean;
|
|
slot3rom: boolean;
|
|
intc8rom: boolean;
|
|
|
|
auxRamRead: boolean;
|
|
auxRamWrite: boolean;
|
|
altzp: boolean;
|
|
|
|
_80store: boolean;
|
|
page2: boolean;
|
|
hires: boolean;
|
|
|
|
mem00_01: [RAMState, RAMState];
|
|
mem02_03: [RAMState, RAMState];
|
|
mem0C_1F: [RAMState, RAMState];
|
|
mem60_BF: [RAMState, RAMState];
|
|
memD0_DF: [ROMState, RAMState, RAMState, RAMState, RAMState];
|
|
memE0_FF: [ROMState, RAMState, RAMState];
|
|
}
|
|
|
|
export default class MMU implements Memory, Restorable<MMUState> {
|
|
private _readPages = new Array<Memory>(0x100);
|
|
private _writePages = new Array<Memory>(0x100);
|
|
private _pages = new Array<Memory[]>(0x100);
|
|
|
|
// Language Card RAM Softswitches
|
|
private _bank1: boolean;
|
|
private _readbsr: boolean;
|
|
private _writebsr: boolean;
|
|
private _prewrite: boolean;
|
|
|
|
// Auxillary ROM
|
|
private _intcxrom: boolean;
|
|
private _slot3rom: boolean;
|
|
private _intc8rom: boolean;
|
|
|
|
// Auxillary RAM
|
|
private _auxRamRead: boolean;
|
|
private _auxRamWrite: boolean;
|
|
private _altzp: boolean;
|
|
|
|
// Video
|
|
private __80store: boolean;
|
|
private _page2: boolean;
|
|
private _hires: boolean;
|
|
|
|
private _iouDisable: boolean;
|
|
|
|
private _vbEnd = 0;
|
|
|
|
private switches = new Switches(this);
|
|
private auxRom = new AuxRom(this, this.rom);
|
|
|
|
// These fields represent the bank-switched memory ranges.
|
|
private mem00_01 = [new RAM(0x0, 0x1), new RAM(0x0, 0x1)];
|
|
private mem02_03 = [new RAM(0x2, 0x3), new RAM(0x2, 0x3)];
|
|
private mem04_07 = [this.lores1.bank0(), this.lores1.bank1()];
|
|
private mem08_0B = [this.lores2.bank0(), this.lores2.bank1()];
|
|
private mem0C_1F = [new RAM(0xC, 0x1F), new RAM(0xC, 0x1F)];
|
|
private mem20_3F = [this.hires1.bank0(), this.hires1.bank1()];
|
|
private mem40_5F = [this.hires2.bank0(), this.hires2.bank1()];
|
|
private mem60_BF = [new RAM(0x60, 0xBF), new RAM(0x60, 0xBF)];
|
|
private memC0_C0 = [this.switches];
|
|
private memC1_CF = [this.io, this.auxRom];
|
|
private memD0_DF: [ROM, RAM, RAM, RAM, RAM] = [
|
|
this.rom,
|
|
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF),
|
|
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF)
|
|
];
|
|
private memE0_FF: [ROM, RAM, RAM] = [
|
|
this.rom,
|
|
new RAM(0xE0, 0xFF), new RAM(0xE0, 0xFF)
|
|
];
|
|
|
|
constructor(
|
|
private readonly cpu: CPU6502,
|
|
private readonly vm: VideoModes,
|
|
private readonly lores1: LoresPage,
|
|
private readonly lores2: LoresPage,
|
|
private readonly hires1: HiresPage,
|
|
private readonly hires2: HiresPage,
|
|
private readonly io: Apple2IO,
|
|
private readonly rom: ROM) {
|
|
/*
|
|
* Initialize read/write banks
|
|
*/
|
|
|
|
// Zero Page/Stack
|
|
for (let idx = 0x0; idx < 0x2; idx++) {
|
|
this._pages[idx] = this.mem00_01;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// 0x300-0x400
|
|
for (let idx = 0x2; idx < 0x4; idx++) {
|
|
this._pages[idx] = this.mem02_03;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// Text Page 1
|
|
for (let idx = 0x4; idx < 0x8; idx++) {
|
|
this._pages[idx] = this.mem04_07;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// Text Page 2
|
|
for (let idx = 0x8; idx < 0xC; idx++) {
|
|
this._pages[idx] = this.mem08_0B;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// 0xC00-0x2000
|
|
for (let idx = 0xC; idx < 0x20; idx++) {
|
|
this._pages[idx] = this.mem0C_1F;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// Hires Page 1
|
|
for (let idx = 0x20; idx < 0x40; idx++) {
|
|
this._pages[idx] = this.mem20_3F;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// Hires Page 2
|
|
for (let idx = 0x40; idx < 0x60; idx++) {
|
|
this._pages[idx] = this.mem40_5F;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// 0x6000-0xc000
|
|
for (let idx = 0x60; idx < 0xc0; idx++) {
|
|
this._pages[idx] = this.mem60_BF;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// I/O Switches
|
|
{
|
|
const idx = 0xc0;
|
|
this._pages[idx] = this.memC0_C0;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// Slots
|
|
for (let idx = 0xc1; idx < 0xd0; idx++) {
|
|
this._pages[idx] = this.memC1_CF;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// Basic ROM
|
|
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
|
this._pages[idx] = this.memD0_DF;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
// Monitor ROM
|
|
for (let idx = 0xe0; idx < 0x100; idx++) {
|
|
this._pages[idx] = this.memE0_FF;
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
}
|
|
|
|
private _initSwitches() {
|
|
this._bank1 = false;
|
|
this._readbsr = false;
|
|
this._writebsr = false;
|
|
this._prewrite = false;
|
|
|
|
this._auxRamRead = false;
|
|
this._auxRamWrite = false;
|
|
this._altzp = false;
|
|
|
|
this._intcxrom = false;
|
|
this._slot3rom = false;
|
|
this._intc8rom = false;
|
|
|
|
this.__80store = false;
|
|
this._page2 = false;
|
|
this._hires = false;
|
|
|
|
this._iouDisable = true;
|
|
}
|
|
|
|
private _debug(..._args: unknown[]) {
|
|
// debug.apply(this, _args);
|
|
}
|
|
|
|
_setIntc8rom(on: boolean) {
|
|
this._intc8rom = on;
|
|
}
|
|
|
|
_updateBanks() {
|
|
if (this._auxRamRead) {
|
|
for (let idx = 0x02; idx < 0xC0; idx++) {
|
|
this._readPages[idx] = this._pages[idx][1];
|
|
}
|
|
} else {
|
|
for (let idx = 0x02; idx < 0xC0; idx++) {
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
}
|
|
}
|
|
|
|
if (this._auxRamWrite) {
|
|
for (let idx = 0x02; idx < 0xC0; idx++) {
|
|
this._writePages[idx] = this._pages[idx][1];
|
|
}
|
|
} else {
|
|
for (let idx = 0x02; idx < 0xC0; idx++) {
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
}
|
|
|
|
if (this.__80store) {
|
|
if (this._page2) {
|
|
for (let idx = 0x4; idx < 0x8; idx++) {
|
|
this._readPages[idx] = this._pages[idx][1];
|
|
this._writePages[idx] = this._pages[idx][1];
|
|
}
|
|
if (this._hires) {
|
|
for (let idx = 0x20; idx < 0x40; idx++) {
|
|
this._readPages[idx] = this._pages[idx][1];
|
|
this._writePages[idx] = this._pages[idx][1];
|
|
}
|
|
}
|
|
} else {
|
|
for (let idx = 0x4; idx < 0x8; idx++) {
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
if (this._hires) {
|
|
for (let idx = 0x20; idx < 0x40; idx++) {
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this._intcxrom) {
|
|
for (let idx = 0xc1; idx < 0xd0; idx++) {
|
|
this._readPages[idx] = this._pages[idx][1];
|
|
this._writePages[idx] = this._pages[idx][1];
|
|
}
|
|
} else {
|
|
for (let idx = 0xc1; idx < 0xd0; idx++) {
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
if (!this._slot3rom) {
|
|
this._readPages[0xc3] = this._pages[0xc3][1];
|
|
this._writePages[0xc3] = this._pages[0xc3][1];
|
|
}
|
|
if (this._intc8rom) {
|
|
for (let idx = 0xc8; idx < 0xd0; idx++) {
|
|
this._readPages[idx] = this._pages[idx][1];
|
|
this._writePages[idx] = this._pages[idx][1];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this._altzp) {
|
|
for (let idx = 0x0; idx < 0x2; idx++) {
|
|
this._readPages[idx] = this._pages[idx][1];
|
|
this._writePages[idx] = this._pages[idx][1];
|
|
}
|
|
} else {
|
|
for (let idx = 0x0; idx < 0x2; idx++) {
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
}
|
|
|
|
if (this._readbsr) {
|
|
if (this._bank1) {
|
|
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
|
this._readPages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
|
}
|
|
} else {
|
|
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
|
this._readPages[idx] = this._pages[idx][this._altzp ? 4 : 3];
|
|
}
|
|
}
|
|
for (let idx = 0xe0; idx < 0x100; idx++) {
|
|
this._readPages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
|
}
|
|
} else {
|
|
for (let idx = 0xd0; idx < 0x100; idx++) {
|
|
this._readPages[idx] = this._pages[idx][0];
|
|
}
|
|
}
|
|
|
|
if (this._writebsr) {
|
|
if (this._bank1) {
|
|
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
|
this._writePages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
|
}
|
|
} else {
|
|
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
|
this._writePages[idx] = this._pages[idx][this._altzp ? 4 : 3];
|
|
}
|
|
}
|
|
for (let idx = 0xe0; idx < 0x100; idx++) {
|
|
this._writePages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
|
}
|
|
} else {
|
|
for (let idx = 0xd0; idx < 0x100; idx++) {
|
|
this._writePages[idx] = this._pages[idx][0];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apple //e memory management
|
|
|
|
private _accessMMUSet(off: byte, _val?: byte) {
|
|
switch (off) {
|
|
case LOC._80STOREOFF:
|
|
this.__80store = false;
|
|
this._debug('80 Store Off', _val);
|
|
this.vm.page(this._page2 ? 2 : 1);
|
|
break;
|
|
case LOC._80STOREON:
|
|
this.__80store = true;
|
|
this._debug('80 Store On', _val);
|
|
break;
|
|
case LOC.RAMRDOFF:
|
|
this._auxRamRead = false;
|
|
this._debug('Aux RAM Read Off');
|
|
break;
|
|
case LOC.RAMRDON:
|
|
this._auxRamRead = true;
|
|
this._debug('Aux RAM Read On');
|
|
break;
|
|
case LOC.RAMWROFF:
|
|
this._auxRamWrite = false;
|
|
this._debug('Aux RAM Write Off');
|
|
break;
|
|
case LOC.RAMWRON:
|
|
this._auxRamWrite = true;
|
|
this._debug('Aux RAM Write On');
|
|
break;
|
|
|
|
case LOC.INTCXROMOFF:
|
|
this._intcxrom = false;
|
|
if (this._slot3rom) {
|
|
this._intc8rom = false;
|
|
}
|
|
this._debug('Int CX ROM Off');
|
|
break;
|
|
case LOC.INTCXROMON:
|
|
this._intcxrom = true;
|
|
this._debug('Int CX ROM On');
|
|
break;
|
|
case LOC.ALTZPOFF: // 0x08
|
|
this._altzp = false;
|
|
this._debug('Alt ZP Off');
|
|
break;
|
|
case LOC.ALTZPON: // 0x09
|
|
this._altzp = true;
|
|
this._debug('Alt ZP On');
|
|
break;
|
|
case LOC.SLOTC3ROMOFF: // 0x0A
|
|
this._slot3rom = false;
|
|
this._debug('Slot 3 ROM Off');
|
|
break;
|
|
case LOC.SLOTC3ROMON: // 0x0B
|
|
this._slot3rom = true;
|
|
this._debug('Slot 3 ROM On');
|
|
break;
|
|
|
|
// Graphics Switches
|
|
|
|
case LOC.CLR80VID:
|
|
this._debug('80 Column Mode off');
|
|
this.vm._80col(false);
|
|
break;
|
|
case LOC.SET80VID:
|
|
this._debug('80 Column Mode on');
|
|
this.vm._80col(true);
|
|
break;
|
|
case LOC.CLRALTCH:
|
|
this._debug('Alt Char off');
|
|
this.vm.altChar(false);
|
|
break;
|
|
case LOC.SETALTCH:
|
|
this._debug('Alt Char on');
|
|
this.vm.altChar(true);
|
|
break;
|
|
}
|
|
this._updateBanks();
|
|
}
|
|
|
|
// Status registers
|
|
|
|
private _accessStatus(off: byte, val?: byte) {
|
|
let result = undefined;
|
|
|
|
switch(off) {
|
|
case LOC.BSRBANK2:
|
|
this._debug(`Bank 2 Read ${!this._bank1 ? 'true' : 'false'}`);
|
|
result = !this._bank1 ? 0x80 : 0x00;
|
|
break;
|
|
case LOC.BSRREADRAM:
|
|
this._debug(`Bank SW RAM Read ${this._readbsr ? 'true' : 'false'}`);
|
|
result = this._readbsr ? 0x80 : 0x00;
|
|
break;
|
|
case LOC.RAMRD: // 0xC013
|
|
this._debug(`Aux RAM Read ${this._auxRamRead ? 'true' : 'false'}`);
|
|
result = this._auxRamRead ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.RAMWRT: // 0xC014
|
|
this._debug(`Aux RAM Write ${this._auxRamWrite ? 'true' : 'false'}`);
|
|
result = this._auxRamWrite ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.INTCXROM: // 0xC015
|
|
// _debug('Int CX ROM ' + _intcxrom);
|
|
result = this._intcxrom ? 0x80 : 0x00;
|
|
break;
|
|
case LOC.ALTZP: // 0xC016
|
|
this._debug(`Alt ZP ${this._altzp ? 'true' : 'false'}`);
|
|
result = this._altzp ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.SLOTC3ROM: // 0xC017
|
|
this._debug(`Slot C3 ROM ${this._slot3rom ? 'true' : 'false'}`);
|
|
result = this._slot3rom ? 0x80 : 0x00;
|
|
break;
|
|
case LOC._80STORE: // 0xC018
|
|
this._debug(`80 Store ${this.__80store ? 'true' : 'false'}`);
|
|
result = this.__80store ? 0x80 : 0x00;
|
|
break;
|
|
case LOC.VERTBLANK: // 0xC019
|
|
// result = cpu.getCycles() % 20 < 5 ? 0x80 : 0x00;
|
|
result = (this.cpu.getCycles() < this._vbEnd) ? 0x80 : 0x00;
|
|
break;
|
|
case LOC.RDTEXT:
|
|
result = this.vm.isText() ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.RDMIXED:
|
|
result = this.vm.isMixed() ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.RDPAGE2:
|
|
result = this._page2 ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.RDHIRES:
|
|
result = this.vm.isHires() ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.RD80VID:
|
|
result = this.vm.is80Col() ? 0x80 : 0x0;
|
|
break;
|
|
case LOC.RDALTCH:
|
|
result = this.vm.isAltChar() ? 0x80 : 0x0;
|
|
break;
|
|
default:
|
|
result = this.io.ioSwitch(off, val);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private _accessIOUDisable(off: byte, val?: byte) {
|
|
const writeMode = val !== undefined;
|
|
let result;
|
|
|
|
switch (off) {
|
|
case LOC.IOUDISON:
|
|
if (writeMode) {
|
|
this._iouDisable = true;
|
|
} else {
|
|
result = this._iouDisable ? 0x00 : 0x80;
|
|
}
|
|
break;
|
|
|
|
case LOC.IOUDISOFF:
|
|
if (writeMode) {
|
|
this._iouDisable = false;
|
|
} else {
|
|
result = this.vm.isDoubleHires() ? 0x80 : 0x00;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
result = this.io.ioSwitch(off, val);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
private _accessGraphics(off: byte, val?: byte) {
|
|
let result: byte | undefined = 0;
|
|
|
|
switch (off) {
|
|
case LOC.PAGE1:
|
|
this._page2 = false;
|
|
if (!this.__80store) {
|
|
result = this.io.ioSwitch(off, val);
|
|
}
|
|
this._debug('Page 2 off');
|
|
break;
|
|
|
|
case LOC.PAGE2:
|
|
this._page2 = true;
|
|
if (!this.__80store) {
|
|
result = this.io.ioSwitch(off, val);
|
|
}
|
|
this._debug('Page 2 on');
|
|
break;
|
|
|
|
case LOC.RESET_HIRES:
|
|
this._hires = false;
|
|
result = this.io.ioSwitch(off, val);
|
|
this._debug('Hires off');
|
|
break;
|
|
|
|
case LOC.DHIRESON:
|
|
if (this._iouDisable) {
|
|
this.vm.doubleHires(true);
|
|
} else {
|
|
result = this.io.ioSwitch(off, val); // an3
|
|
}
|
|
break;
|
|
|
|
case LOC.DHIRESOFF:
|
|
if (this._iouDisable) {
|
|
this.vm.doubleHires(false);
|
|
} else {
|
|
result = this.io.ioSwitch(off, val); // an3
|
|
}
|
|
break;
|
|
|
|
case LOC.SET_HIRES:
|
|
this._hires = true;
|
|
result = this.io.ioSwitch(off, val);
|
|
this._debug('Hires on');
|
|
break;
|
|
|
|
default:
|
|
result = this.io.ioSwitch(off, val);
|
|
break;
|
|
}
|
|
this._updateBanks();
|
|
|
|
return result;
|
|
}
|
|
|
|
private _accessLangCard(off: byte, val?: byte) {
|
|
const readMode = val === undefined;
|
|
const result = readMode ? 0 : undefined;
|
|
|
|
const writeSwitch = off & 0x01;
|
|
const offSwitch = off & 0x02;
|
|
const bank1Switch = off & 0x08;
|
|
|
|
let bankStr;
|
|
let rwStr;
|
|
|
|
if (writeSwitch) { // 0xC081, 0xC083
|
|
if (readMode) {
|
|
if (this._prewrite) {
|
|
this._writebsr = true;
|
|
}
|
|
}
|
|
this._prewrite = readMode;
|
|
|
|
if (offSwitch) { // 0xC08B
|
|
this._readbsr = true;
|
|
rwStr = 'Read/Write';
|
|
} else {
|
|
this._readbsr = false;
|
|
rwStr = 'Write';
|
|
}
|
|
} else { // 0xC080, 0xC082
|
|
this._writebsr = false;
|
|
this._prewrite = false;
|
|
|
|
if (offSwitch) { // 0xC082
|
|
this._readbsr = false;
|
|
rwStr = 'Off';
|
|
} else { // 0xC080
|
|
this._readbsr = true;
|
|
rwStr = 'Read';
|
|
}
|
|
}
|
|
|
|
if (bank1Switch) {
|
|
this._bank1 = true;
|
|
bankStr = 'Bank 1';
|
|
} else {
|
|
this._bank1 = false;
|
|
bankStr = 'Bank 2';
|
|
}
|
|
|
|
this._debug(bankStr, rwStr);
|
|
this._updateBanks();
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* The Big Switch
|
|
*/
|
|
|
|
_access(off: byte, val?: byte) {
|
|
let result;
|
|
const writeMode = val !== undefined;
|
|
const highNibble = off >> 4;
|
|
|
|
switch (highNibble) {
|
|
case 0x0:
|
|
if (writeMode) {
|
|
this._accessMMUSet(off, val);
|
|
} else {
|
|
result = this.io.ioSwitch(off);
|
|
}
|
|
break;
|
|
|
|
case 0x1:
|
|
if (writeMode) {
|
|
this.io.ioSwitch(off, val);
|
|
} else {
|
|
result = this._accessStatus(off, val);
|
|
}
|
|
break;
|
|
|
|
case 0x5:
|
|
result = this._accessGraphics(off, val);
|
|
break;
|
|
|
|
case 0x7:
|
|
result = this._accessIOUDisable(off, val);
|
|
break;
|
|
|
|
case 0x8:
|
|
result = this._accessLangCard(off, val);
|
|
break;
|
|
|
|
default:
|
|
result = this.io.ioSwitch(off, val);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public start() {
|
|
this.lores1.start();
|
|
this.lores2.start();
|
|
return 0x00;
|
|
}
|
|
|
|
public end() {
|
|
return 0xff;
|
|
}
|
|
|
|
public reset() {
|
|
debug('reset');
|
|
this._initSwitches();
|
|
this._updateBanks();
|
|
this.vm.reset();
|
|
this.io.reset();
|
|
}
|
|
|
|
public read(page: byte, off: byte) {
|
|
return this._readPages[page].read(page, off);
|
|
}
|
|
|
|
public write(page: byte, off: byte, val: byte) {
|
|
this._writePages[page].write(page, off, val);
|
|
}
|
|
|
|
public writeBank(bank: number,page: byte, off: byte, val: byte) {
|
|
this._pages[page][bank].write(page, off, val);
|
|
}
|
|
|
|
public resetVB() {
|
|
this._vbEnd = this.cpu.getCycles() + 1000;
|
|
}
|
|
|
|
public get bank1() {
|
|
return this._bank1;
|
|
}
|
|
|
|
public get readbsr() {
|
|
return this._readbsr;
|
|
}
|
|
|
|
public get writebsr() {
|
|
return this._writebsr;
|
|
}
|
|
|
|
public get auxread() {
|
|
return this._auxRamRead;
|
|
}
|
|
|
|
public get auxwrite() {
|
|
return this._auxRamWrite;
|
|
}
|
|
|
|
public get altzp() {
|
|
return this._altzp;
|
|
}
|
|
|
|
public get _80store() {
|
|
return this.__80store;
|
|
}
|
|
|
|
public get page2() {
|
|
return this._page2;
|
|
}
|
|
|
|
public get hires() {
|
|
return this._hires;
|
|
}
|
|
|
|
public get intcxrom() {
|
|
return this._intcxrom;
|
|
}
|
|
|
|
public getState(): MMUState {
|
|
return {
|
|
bank1: this._bank1,
|
|
readbsr: this._readbsr,
|
|
writebsr: this._writebsr,
|
|
prewrite: this._prewrite,
|
|
|
|
intcxrom: this._intcxrom,
|
|
slot3rom: this._slot3rom,
|
|
intc8rom: this._intc8rom,
|
|
|
|
auxRamRead: this._auxRamRead,
|
|
auxRamWrite: this._auxRamWrite,
|
|
altzp: this._altzp,
|
|
|
|
_80store: this.__80store,
|
|
page2: this._page2,
|
|
hires: this._hires,
|
|
|
|
mem00_01: [this.mem00_01[0].getState(), this.mem00_01[1].getState()],
|
|
mem02_03: [this.mem02_03[0].getState(), this.mem02_03[1].getState()],
|
|
mem0C_1F: [this.mem0C_1F[0].getState(), this.mem0C_1F[1].getState()],
|
|
mem60_BF: [this.mem60_BF[0].getState(), this.mem60_BF[1].getState()],
|
|
memD0_DF: [
|
|
this.memD0_DF[0].getState(),
|
|
this.memD0_DF[1].getState(),
|
|
this.memD0_DF[2].getState(),
|
|
this.memD0_DF[3].getState(),
|
|
this.memD0_DF[4].getState()
|
|
],
|
|
memE0_FF: [
|
|
this.memE0_FF[0].getState(),
|
|
this.memE0_FF[1].getState(),
|
|
this.memE0_FF[2].getState()
|
|
]
|
|
};
|
|
}
|
|
|
|
public setState(state: MMUState) {
|
|
this._readbsr = state.readbsr;
|
|
this._writebsr = state.writebsr;
|
|
this._bank1 = state.bank1;
|
|
this._prewrite = state.prewrite;
|
|
|
|
this._intcxrom = state.intcxrom;
|
|
this._slot3rom = state.slot3rom;
|
|
this._intc8rom = state.intc8rom;
|
|
|
|
this._auxRamRead = state.auxRamRead;
|
|
this._auxRamWrite = state.auxRamWrite;
|
|
this._altzp = state.altzp;
|
|
|
|
this.__80store = state._80store;
|
|
this._page2 = state.page2;
|
|
this._hires = state.hires;
|
|
|
|
this.mem00_01[0].setState(state.mem00_01[0]);
|
|
this.mem00_01[1].setState(state.mem00_01[1]);
|
|
this.mem02_03[0].setState(state.mem02_03[0]);
|
|
this.mem02_03[1].setState(state.mem02_03[1]);
|
|
this.mem0C_1F[0].setState(state.mem0C_1F[0]);
|
|
this.mem0C_1F[1].setState(state.mem0C_1F[1]);
|
|
this.mem60_BF[0].setState(state.mem60_BF[0]);
|
|
this.mem60_BF[1].setState(state.mem60_BF[1]);
|
|
this.memD0_DF[0].setState(state.memD0_DF[0]);
|
|
this.memD0_DF[1].setState(state.memD0_DF[1]);
|
|
this.memD0_DF[2].setState(state.memD0_DF[2]);
|
|
this.memD0_DF[3].setState(state.memD0_DF[3]);
|
|
this.memD0_DF[4].setState(state.memD0_DF[4]);
|
|
this.memE0_FF[0].setState(state.memE0_FF[0]);
|
|
this.memE0_FF[1].setState(state.memE0_FF[1]);
|
|
this.memE0_FF[2].setState(state.memE0_FF[2]);
|
|
|
|
this._updateBanks();
|
|
}
|
|
}
|