// // mmu.cpp: Memory management // // by James Hammons // (C) 2013-2018 Underground Software // // JLH = James Hammons // // WHO WHEN WHAT // --- ---------- ----------------------------------------------------------- // JLH 09/27/2013 Created this file #include "mmu.h" #include "apple2.h" #include "firmware/firmware.h" #include "log.h" #include "mockingboard.h" #include "sound.h" #include "video.h" // Debug defines //#define LC_DEBUG // Address Map enumeration enum { AM_RAM, AM_ROM, AM_BANKED, AM_READ, AM_WRITE, AM_READ_WRITE, AM_END_OF_LIST }; // Internal vars uint8_t ** addrPtrRead[0x10000]; uint8_t ** addrPtrWrite[0x10000]; uint16_t addrOffset[0x10000]; READFUNC(funcMapRead[0x10000]); WRITEFUNC(funcMapWrite[0x10000]); READFUNC(slotHandlerR[8]); WRITEFUNC(slotHandlerW[8]); READFUNC(slotHandler2KR[8]); WRITEFUNC(slotHandler2KW[8]); uint8_t enabledSlot; struct AddressMap { uint16_t start; uint16_t end; int type; uint8_t ** memory; uint8_t ** altMemory; READFUNC(read); WRITEFUNC(write); }; #define ADDRESS_MAP_END { 0x0000, 0x0000, AM_END_OF_LIST, 0, 0, 0, 0 } // Dunno if I like this approach or not... //ADDRESS_MAP_START() // AM_RANGE(0x0000, 0xBFFF) AM_RAM AM_BASE(ram) AM_SHARE(1) // AM_RANGE(0xC000, 0xC001) AM_READWRITE(readFunc, writeFunc) //ADDRESS_MAP_END // Would need a pointer for 80STORE as well... uint8_t * pageZeroMemory = &ram[0x0000]; // $0000 - $01FF uint8_t * mainMemoryR = &ram[0x0200]; // $0200 - $BFFF (read) uint8_t * mainMemoryW = &ram[0x0200]; // $0200 - $BFFF (write) uint8_t * mainMemoryTextR = &ram[0x0400]; // $0400 - $07FF (read) uint8_t * mainMemoryTextW = &ram[0x0400]; // $0400 - $07FF (write) uint8_t * mainMemoryHGRR = &ram[0x2000]; // $2000 - $3FFF (read) uint8_t * mainMemoryHGRW = &ram[0x2000]; // $2000 - $3FFF (write) uint8_t * slotMemory = &rom[0xC100]; // $C100 - $C7FF uint8_t * peripheralMemory= &rom[0xC800]; // $C800 - $CFFF uint8_t * slot3Memory = &rom[0xC300]; // $C300 - $C3FF uint8_t * slot4Memory = &rom[0xC400]; // $C400 - $C4FF uint8_t * slot6Memory = &diskROM[0]; // $C600 - $C6FF uint8_t * lcBankMemoryR = &ram[0xD000]; // $D000 - $DFFF (read) uint8_t * lcBankMemoryW = &ram[0xD000]; // $D000 - $DFFF (write) uint8_t * upperMemoryR = &ram[0xE000]; // $E000 - $FFFF (read) uint8_t * upperMemoryW = &ram[0xE000]; // $E000 - $FFFF (write) // Function prototypes uint8_t ReadNOP(uint16_t); void WriteNOP(uint16_t, uint8_t); uint8_t ReadMemory(uint16_t); void WriteMemory(uint16_t, uint8_t); uint8_t SlotR(uint16_t address); void SlotW(uint16_t address, uint8_t byte); uint8_t Slot2KR(uint16_t address); void Slot2KW(uint16_t address, uint8_t byte); uint8_t ReadKeyboard(uint16_t); void Switch80STORE(uint16_t, uint8_t); void SwitchRAMRD(uint16_t, uint8_t); void SwitchRAMWRT(uint16_t, uint8_t); void SwitchSLOTCXROM(uint16_t, uint8_t); void SwitchALTZP(uint16_t, uint8_t); void SwitchSLOTC3ROM(uint16_t, uint8_t); uint8_t SwitchINTC8ROMR(uint16_t); void SwitchINTC8ROMW(uint16_t, uint8_t); void Switch80COL(uint16_t, uint8_t); void SwitchALTCHARSET(uint16_t, uint8_t); uint8_t ReadKeyStrobe(uint16_t); uint8_t ReadBANK2(uint16_t); uint8_t ReadLCRAM(uint16_t); uint8_t ReadRAMRD(uint16_t); uint8_t ReadRAMWRT(uint16_t); uint8_t ReadSLOTCXROM(uint16_t); uint8_t ReadALTZP(uint16_t); uint8_t ReadSLOTC3ROM(uint16_t); uint8_t Read80STORE(uint16_t); uint8_t ReadVBL(uint16_t); uint8_t ReadTEXT(uint16_t); uint8_t ReadMIXED(uint16_t); uint8_t ReadPAGE2(uint16_t); uint8_t ReadHIRES(uint16_t); uint8_t ReadALTCHARSET(uint16_t); uint8_t Read80COL(uint16_t); void WriteKeyStrobe(uint16_t, uint8_t); uint8_t ReadSpeaker(uint16_t); void WriteSpeaker(uint16_t, uint8_t); uint8_t SwitchLCR(uint16_t); void SwitchLCW(uint16_t, uint8_t); void SwitchLC(void); uint8_t SwitchTEXTR(uint16_t); void SwitchTEXTW(uint16_t, uint8_t); uint8_t SwitchMIXEDR(uint16_t); void SwitchMIXEDW(uint16_t, uint8_t); uint8_t SwitchPAGE2R(uint16_t); void SwitchPAGE2W(uint16_t, uint8_t); uint8_t SwitchHIRESR(uint16_t); void SwitchHIRESW(uint16_t, uint8_t); uint8_t SwitchDHIRESR(uint16_t); void SwitchDHIRESW(uint16_t, uint8_t); void SwitchIOUDIS(uint16_t, uint8_t); uint8_t ReadButton0(uint16_t); uint8_t ReadButton1(uint16_t); uint8_t ReadPaddle0(uint16_t); uint8_t ReadIOUDIS(uint16_t); uint8_t ReadDHIRES(uint16_t); // The main Apple //e memory map AddressMap memoryMap[] = { { 0x0000, 0x01FF, AM_RAM, &pageZeroMemory, 0, 0, 0 }, { 0x0200, 0xBFFF, AM_BANKED, &mainMemoryR, &mainMemoryW, 0, 0 }, // These will overlay over the previously written memory accessors { 0x0400, 0x07FF, AM_BANKED, &mainMemoryTextR, &mainMemoryTextW, 0, 0 }, { 0x2000, 0x3FFF, AM_BANKED, &mainMemoryHGRR, &mainMemoryHGRW, 0, 0 }, { 0xC000, 0xC001, AM_READ_WRITE, 0, 0, ReadKeyboard, Switch80STORE }, { 0xC002, 0xC003, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchRAMRD }, { 0xC004, 0xC005, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchRAMWRT }, { 0xC006, 0xC007, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchSLOTCXROM }, { 0xC008, 0xC009, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchALTZP }, { 0xC00A, 0xC00B, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchSLOTC3ROM }, { 0xC00C, 0xC00D, AM_READ_WRITE, 0, 0, ReadKeyboard, Switch80COL }, { 0xC00E, 0xC00F, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchALTCHARSET }, { 0xC010, 0xC010, AM_READ_WRITE, 0, 0, ReadKeyStrobe, WriteKeyStrobe }, { 0xC011, 0xC011, AM_READ_WRITE, 0, 0, ReadBANK2, WriteKeyStrobe }, { 0xC012, 0xC012, AM_READ_WRITE, 0, 0, ReadLCRAM, WriteKeyStrobe }, { 0xC013, 0xC013, AM_READ_WRITE, 0, 0, ReadRAMRD, WriteKeyStrobe }, { 0xC014, 0xC014, AM_READ_WRITE, 0, 0, ReadRAMWRT, WriteKeyStrobe }, { 0xC015, 0xC015, AM_READ_WRITE, 0, 0, ReadSLOTCXROM, WriteKeyStrobe }, { 0xC016, 0xC016, AM_READ_WRITE, 0, 0, ReadALTZP, WriteKeyStrobe }, { 0xC017, 0xC017, AM_READ_WRITE, 0, 0, ReadSLOTC3ROM, WriteKeyStrobe }, { 0xC018, 0xC018, AM_READ_WRITE, 0, 0, Read80STORE, WriteKeyStrobe }, { 0xC019, 0xC019, AM_READ_WRITE, 0, 0, ReadVBL, WriteKeyStrobe }, { 0xC01A, 0xC01A, AM_READ_WRITE, 0, 0, ReadTEXT, WriteKeyStrobe }, { 0xC01B, 0xC01B, AM_READ_WRITE, 0, 0, ReadMIXED, WriteKeyStrobe }, { 0xC01C, 0xC01C, AM_READ_WRITE, 0, 0, ReadPAGE2, WriteKeyStrobe }, { 0xC01D, 0xC01D, AM_READ_WRITE, 0, 0, ReadHIRES, WriteKeyStrobe }, { 0xC01E, 0xC01E, AM_READ_WRITE, 0, 0, ReadALTCHARSET, WriteKeyStrobe }, { 0xC01F, 0xC01F, AM_READ_WRITE, 0, 0, Read80COL, WriteKeyStrobe }, // $C020 is "Cassette Out (RO)" { 0xC020, 0xC02F, AM_READ, 0, 0, ReadFloatingBus, 0 }, // May have to put a "floating bus" read there... :-/ // Apparently, video RAM is put on 'non-responding address'. So will // need to time those out. // So... $C020-$C08F, when read, return video data. // $C090-$C7FF do also, as long as the slot the range refers to is empty // and last and least is $CFFF, which is the Expansion ROM disable. { 0xC030, 0xC03F, AM_READ_WRITE, 0, 0, ReadSpeaker, WriteSpeaker }, { 0xC050, 0xC051, AM_READ_WRITE, 0, 0, SwitchTEXTR, SwitchTEXTW }, { 0xC052, 0xC053, AM_READ_WRITE, 0, 0, SwitchMIXEDR, SwitchMIXEDW }, { 0xC054, 0xC055, AM_READ_WRITE, 0, 0, SwitchPAGE2R, SwitchPAGE2W }, { 0xC056, 0xC057, AM_READ_WRITE, 0, 0, SwitchHIRESR, SwitchHIRESW }, { 0xC05E, 0xC05F, AM_READ_WRITE, 0, 0, SwitchDHIRESR, SwitchDHIRESW }, { 0xC061, 0xC061, AM_READ, 0, 0, ReadButton0, 0 }, { 0xC062, 0xC062, AM_READ, 0, 0, ReadButton1, 0 }, { 0xC064, 0xC067, AM_READ, 0, 0, ReadPaddle0, 0 }, { 0xC07E, 0xC07E, AM_READ_WRITE, 0, 0, ReadIOUDIS, SwitchIOUDIS }, { 0xC07F, 0xC07F, AM_READ_WRITE, 0, 0, ReadDHIRES, SwitchIOUDIS }, { 0xC080, 0xC08F, AM_READ_WRITE, 0, 0, SwitchLCR, SwitchLCW }, { 0xC100, 0xC7FF, AM_READ_WRITE, 0, 0, SlotR, SlotW }, { 0xC800, 0xCFFE, AM_READ_WRITE, 0, 0, Slot2KR, Slot2KW }, { 0xCFFF, 0xCFFF, AM_READ_WRITE, 0, 0, SwitchINTC8ROMR, SwitchINTC8ROMW }, { 0xD000, 0xDFFF, AM_BANKED, &lcBankMemoryR, &lcBankMemoryW, 0, 0 }, { 0xE000, 0xFFFF, AM_BANKED, &upperMemoryR, &upperMemoryW, 0, 0 }, ADDRESS_MAP_END }; /* Some stuff that may be useful: N.B.: Page 5-22 of UTA2E has INTC8ROM ON/OFF backwards INTC8ROM is turned OFF by R/W access to $CFFF INTC8ROM is turned ON by $C3xx access and SLOTC3ROM' (off) WRONG: (INTC8ROM on puts card's slot ROM/RAM(?) access in $C800-$CFFF) OK, so it's slightly more complex than that. Basically, when there is an access to $CFFF, all peripheral cards must *stop* responding to I/O STROBE'. Only when a card gets an I/O SELECT' signal, can it respond to I/O STROBE'. INTC8ROM inhibits I/O STROBE' and activates the MB ROM in $C800-$CFFF INTC8ROM is 1 by access to $C3xx when SLOTC3ROM is 0 INTC8ROM is 0 by access to $CFFF ICX = INTCXROM (aka SLOTCXROM), SC3 = SLOTC3ROM ICX=0,SC3=0 ICX=0,SC3=1 ICX=1,SC3=0 ICX=1,SC3=1 $C100-$C2FF slot slot internal internal $C300-$C3FF internal slot internal internal $C400-$CFFF slot slot internal internal Read from $C800-$CFFF causes I/O STROBE to go low (and INTCXROM and INTC8ROM are not set) */ void SetupAddressMap(void) { for(uint32_t i=0; i<0x10000; i++) { funcMapRead[i] = ReadNOP; funcMapWrite[i] = WriteNOP; addrPtrRead[i] = 0; addrPtrWrite[i] = 0; addrOffset[i] = 0; } for(uint32_t i=0; i<8; i++) { slotHandlerR[i] = ReadNOP; slotHandlerW[i] = WriteNOP; slotHandler2KR[i] = ReadNOP; slotHandler2KW[i] = WriteNOP; } uint32_t i=0; while (memoryMap[i].type != AM_END_OF_LIST) { switch (memoryMap[i].type) { case AM_RAM: for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++) { funcMapRead[j] = ReadMemory; funcMapWrite[j] = WriteMemory; addrPtrRead[j] = memoryMap[i].memory; addrPtrWrite[j] = memoryMap[i].memory; addrOffset[j] = j - memoryMap[i].start; //WriteLog("SetupAddressMap: j=$%04X, addrOffset[j]=$%04X\n", j, addrOffset[j]); } break; case AM_ROM: for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++) { funcMapRead[j] = ReadMemory; addrPtrRead[j] = memoryMap[i].memory; addrOffset[j] = j - memoryMap[i].start; } break; case AM_BANKED: for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++) { funcMapRead[j] = ReadMemory; funcMapWrite[j] = WriteMemory; addrPtrRead[j] = memoryMap[i].memory; addrPtrWrite[j] = memoryMap[i].altMemory; addrOffset[j] = j - memoryMap[i].start; } break; case AM_READ: for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++) funcMapRead[j] = memoryMap[i].read; break; case AM_WRITE: for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++) funcMapWrite[j] = memoryMap[i].write; break; case AM_READ_WRITE: for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++) { funcMapRead[j] = memoryMap[i].read; funcMapWrite[j] = memoryMap[i].write; } break; } i++; }; // This should correctly set up the LC pointers, but it doesn't // for some reason... :-/ // It's because we were storing pointers directly, instead of pointers // to the pointer... It's complicated. :-) SwitchLC(); } // // Reset the MMU state after a power down event // void ResetMMUPointers(void) { if (store80Mode) { mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); } else { mainMemoryTextR = (ramwrt ? &ram2[0x0400] : &ram[0x0400]); mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]); } mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]); mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]); mainMemoryW = (ramwrt ? &ram2[0x0200] : &ram[0x0200]); mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]); // slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]); // slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]); pageZeroMemory = (altzp ? &ram2[0x0000] : &ram[0x0000]); SwitchLC(); #if 1 WriteLog("RAMWRT = %s\n", (ramwrt ? "ON" : "off")); WriteLog("RAMRD = %s\n", (ramrd ? "ON" : "off")); WriteLog("SLOTCXROM = %s\n", (intCXROM ? "ON" : "off")); WriteLog("SLOTC3ROM = %s\n", (slotC3ROM ? "ON" : "off")); WriteLog("ALTZP = %s\n", (altzp ? "ON" : "off")); #endif } // // Set up slot access // void InstallSlotHandler(uint8_t slot, SlotData * slotData) { // Sanity check if (slot > 7) { WriteLog("InstallSlotHandler: Caller attempted to put device into slot #%u...\n", slot); return; } // Set up I/O read & write functions for(uint32_t i=0; i<16; i++) { if (slotData->ioR) funcMapRead[0xC080 + (slot * 16) + i] = slotData->ioR; if (slotData->ioW) funcMapWrite[0xC080 + (slot * 16) + i] = slotData->ioW; } // Set up memory access read/write functions if (slotData->pageR) slotHandlerR[slot] = slotData->pageR; if (slotData->pageW) slotHandlerW[slot] = slotData->pageW; if (slotData->extraR) slotHandler2KR[slot] = slotData->extraR; if (slotData->extraW) slotHandler2KW[slot] = slotData->extraW; /* Was thinking about how to make these things more self-contained, so that the management overhead would be less. IOW, you should be able to make an object (struct) that holds everything needed to interface the MMU with itself--InstallSlotHandler *almost* does this, but not quite. A consequence of this approach is that we would have to add generic slot I/O handlers into the mix, but that shouldn't be too horrible. So it could be something like so: struct Card { void * object; uint16_t type; // Probably an enum so we can figure out what 'object' is READFUNC(slotIOR); WRITEFUNC(slotIOW); READFUNC(slotPageR); WRITEFUNC(slotPageW); READFUNC(slot2KR); WRITEFUNC(slot2KW); } So instead of a bunch of crappy shit that sucks in here, we would have a simple thing like: Card * slots[8]; to encapsulate slots. This also makes it easier to move them around and makes things less error prone. So maybe... */ } // // Built-in functions // uint8_t ReadNOP(uint16_t) { // This is for unconnected reads, and some software looks at addresses like // these. In particular, Mr. Robot and His Robot Factory failed in that it // was looking at the first byte of each slots 256 byte driver space and // failing if it saw a zero there. Now I have no idea what happens in the // real hardware, but I suspect it would return something that looks like // ReadFloatingBus(). return 0xFF; } void WriteNOP(uint16_t, uint8_t) { } uint8_t ReadMemory(uint16_t address) { //WriteLog("ReadMemory: addr=$%04X, addrPtrRead[addr]=$%X, addrOffset[addr]=$%X, val=$%02X\n", address, addrPtrRead[address], addrOffset[address], addrPtrRead[address][addrOffset[address]]); // We are guaranteed a valid address here by the setup function, so there's // no need to do any checking here. return (*addrPtrRead[address])[addrOffset[address]]; } void WriteMemory(uint16_t address, uint8_t byte) { // We can write protect memory this way, but it adds a branch to the mix. // :-/ (this can be avoided by setting up another bank of memory which we // ignore... hmm...) if ((*addrPtrWrite[address]) == 0) return; (*addrPtrWrite[address])[addrOffset[address]] = byte; } // // The main memory access functions used by V65C02 // uint8_t AppleReadMem(uint16_t address) { #if 0 if (address == 0xD4 || address == 0xAC20) WriteLog("Reading $%X...\n", address); #endif #if 0 uint8_t memRead = (*(funcMapRead[address]))(address); static uint16_t lastAddr = 0; static uint32_t lastCount = 0; if ((address > 0xC000 && address < 0xC100) || address == 0xC601) { if (lastAddr == address) lastCount++; else { if (lastCount > 1) WriteLog("%d times...\n", lastCount); WriteLog("Reading $%02X from $%X ($%02X, $%02X)\n", memRead, address, diskROM[1], rom[0xC601]); lastCount = 1; lastAddr = address; } } return memRead; #else return (*(funcMapRead[address]))(address); #endif } void AppleWriteMem(uint16_t address, uint8_t byte) { #if 0 static uint16_t lastAddr = 0; static uint32_t lastCount = 0; if ((address > 0xC000 && address < 0xC100) || address == 0xC601) { if (lastAddr == address) lastCount++; else { if (lastCount > 1) WriteLog("%d times...\n", lastCount); WriteLog("Writing to $%X\n", address); lastCount = 1; lastAddr = address; } } #endif #if 0 if (address == 0xD4 || address == 0xAC20) WriteLog("Writing $%02X @ $%X...\n", byte, address); #endif #if 0 //if (address >= 0x0827 && address <= 0x082A) if (address == 0x000D) WriteLog("Writing $%02X @ $%X (PC=$%04X)...\n", byte, address, mainCPU.pc); #endif (*(funcMapWrite[address]))(address, byte); } // // Generic slot handlers. These are set up here so that we can catch INTCXROM, // INTC8ROM & SLOTC3ROM here instead of having to catch them in each slot handler. // uint8_t SlotR(uint16_t address) { //WriteLog("SlotR: address=$%04X, intCXROM=%d, slotC3ROM=%d, intC8ROM=%d\n", address, intCXROM, slotC3ROM, intC8ROM); if (intCXROM) return rom[address]; uint8_t slot = (address & 0xF00) >> 8; enabledSlot = slot; if ((slotC3ROM == 0) && (slot == 3)) { intC8ROM = 1; return rom[address]; } return (*(slotHandlerR[slot]))(address & 0xFF); } void SlotW(uint16_t address, uint8_t byte) { if (intCXROM) return; uint8_t slot = (address & 0xF00) >> 8; enabledSlot = slot; if ((slotC3ROM == 0) && (slot == 3)) { intC8ROM = 1; return; } (*(slotHandlerW[slot]))(address & 0xFF, byte); } // // Slot handling for 2K address space at $C800-$CFFF // uint8_t Slot2KR(uint16_t address) { if (intCXROM || intC8ROM) return rom[address]; return (*(slotHandler2KR[enabledSlot]))(address & 0x7FF); } void Slot2KW(uint16_t address, uint8_t byte) { if (intCXROM || intC8ROM) return; (*(slotHandler2KW[enabledSlot]))(address & 0x7FF, byte); } // // Actual emulated I/O functions follow // uint8_t ReadKeyboard(uint16_t /*addr*/) { return lastKeyPressed | ((uint8_t)keyDown << 7); } void Switch80STORE(uint16_t address, uint8_t) { store80Mode = (bool)(address & 0x01); WriteLog("Setting 80STORE to %s...\n", (store80Mode ? "ON" : "off")); if (store80Mode) { mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); } else { mainMemoryTextR = (ramwrt ? &ram2[0x0400] : &ram[0x0400]); mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]); } } void SwitchRAMRD(uint16_t address, uint8_t) { ramrd = (bool)(address & 0x01); mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]); mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]); if (store80Mode) return; mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]); } void SwitchRAMWRT(uint16_t address, uint8_t) { ramwrt = (bool)(address & 0x01); mainMemoryW = (ramwrt ? &ram2[0x0200] : &ram[0x0200]); mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]); if (store80Mode) return; mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]); } // // Since any slots that aren't populated are set to read from the ROM anyway, // we only concern ourselves with switching populated slots here. (Note that // the MB slot is a split ROM / I/O device, and it's taken care of in the // MB handler.) // // N.B.: SLOTCXROM is also INTCXROM // void SwitchSLOTCXROM(uint16_t address, uint8_t) { WriteLog("Setting SLOTCXROM to %s...\n", (address & 0x01 ? "ON" : "off")); intCXROM = (bool)(address & 0x01); // INTC8ROM trumps all (only in the $C800--$CFFF range... which we don't account for yet... :-/) // if (intC8ROM) // return; #if 0 #if 1 if (intCXROM) { slot3Memory = &rom[0xC300]; slot6Memory = &rom[0xC600]; } else { slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]); slot6Memory = &diskROM[0]; } #else // slot3Memory = (intCXROM ? &rom[0xC300] : &rom[0]); slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]); #endif #endif } void SwitchALTZP(uint16_t address, uint8_t) { altzp = (bool)(address & 0x01); pageZeroMemory = (altzp ? &ram2[0x0000] : &ram[0x0000]); SwitchLC(); } //extern bool dumpDis; // // The interpretation of this name is that if it's set then we access the ROM // for the card actually sitting in SLOT 3 (if any) // void SwitchSLOTC3ROM(uint16_t address, uint8_t) { //dumpDis = true; //WriteLog("Setting SLOTC3ROM to %s...\n", (address & 0x01 ? "ON" : "off")); slotC3ROM = (bool)(address & 0x01); #if 1 if (intCXROM) slot3Memory = &rom[0xC300]; else slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]); #else // slotC3ROM = false; // Seems the h/w forces this with an 80 column card in slot 3... slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]); // slot3Memory = &rom[0xC300]; #endif } /* We need to see where this is being switched from; if we know that, we can switch in the appropriate ROM to $C800-$CFFF. N.B.: Will probably need a custom handler routine, as some cards (like the Apple Hi-Speed SCSI card) split the 2K range into a 1K RAM space and a 1K bank switch ROM space. */ // // This is a problem with split ROM / I/O regions. Because we can't do that // cleanly, we have to have a read handler for this. // // N.B.: We could add AM_IOREAD_WRITE and AM_READ_IOWRITE to the memory handlers // to take care of split ROM / I/O regions... // uint8_t SwitchINTC8ROMR(uint16_t) { WriteLog("Hitting INTC8ROM (read)...\n"); intC8ROM = false; return rom[0xCFFF]; } // // This resets the INTC8ROM switch (RW) // void SwitchINTC8ROMW(uint16_t, uint8_t) { WriteLog("Hitting INTC8ROM (write)...\n"); intC8ROM = false; } void Switch80COL(uint16_t address, uint8_t) { col80Mode = (bool)(address & 0x01); } void SwitchALTCHARSET(uint16_t address, uint8_t) { alternateCharset = (bool)(address & 0x01); WriteLog("Setting ALTCHARSET to %s...\n", (alternateCharset ? "ON" : "off")); } uint8_t ReadKeyStrobe(uint16_t) { // No character data is read from here, just the 'any key was pressed' // signal... uint8_t byte = (uint8_t)keyDown << 7; keyDown = false; return byte; } uint8_t ReadBANK2(uint16_t) { return (lcState < 0x04 ? 0x80 : 0x00); } uint8_t ReadLCRAM(uint16_t) { // If bits 0 & 1 are set, but not at the same time, then it's ROM uint8_t lcROM = (lcState & 0x1) ^ ((lcState & 0x02) >> 1); return (lcROM ? 0x00 : 0x80); } uint8_t ReadRAMRD(uint16_t) { return (uint8_t)ramrd << 7; } uint8_t ReadRAMWRT(uint16_t) { return (uint8_t)ramwrt << 7; } uint8_t ReadSLOTCXROM(uint16_t) { return (uint8_t)intCXROM << 7; } uint8_t ReadALTZP(uint16_t) { return (uint8_t)altzp << 7; } uint8_t ReadSLOTC3ROM(uint16_t) { return (uint8_t)slotC3ROM << 7; } uint8_t Read80STORE(uint16_t) { return (uint8_t)store80Mode << 7; } uint8_t ReadVBL(uint16_t) { return (uint8_t)vbl << 7; } uint8_t ReadTEXT(uint16_t) { return (uint8_t)textMode << 7; } uint8_t ReadMIXED(uint16_t) { return (uint8_t)mixedMode << 7; } uint8_t ReadPAGE2(uint16_t) { return (uint8_t)displayPage2 << 7; } uint8_t ReadHIRES(uint16_t) { return (uint8_t)hiRes << 7; } uint8_t ReadALTCHARSET(uint16_t) { return (uint8_t)alternateCharset << 7; } uint8_t Read80COL(uint16_t) { return (uint8_t)col80Mode << 7; } void WriteKeyStrobe(uint16_t, uint8_t) { keyDown = false; } uint8_t ReadSpeaker(uint16_t) { ToggleSpeaker(); return 0; } void WriteSpeaker(uint16_t, uint8_t) { ToggleSpeaker(); } uint8_t SwitchLCR(uint16_t address) { lcState = address & 0x0B; SwitchLC(); return 0; } void SwitchLCW(uint16_t address, uint8_t) { lcState = address & 0x0B; SwitchLC(); } void SwitchLC(void) { switch (lcState) { case 0x00: #ifdef LC_DEBUG WriteLog("SwitchLC: Read RAM bank 2, no write\n"); #endif // [R ] Read RAM bank 2; no write lcBankMemoryR = (altzp ? &ram2[0xD000] : &ram[0xD000]); lcBankMemoryW = 0; upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]); upperMemoryW = 0; break; case 0x01: #ifdef LC_DEBUG WriteLog("SwitchLC: Read ROM, write bank 2\n"); #endif // [RR] Read ROM; write RAM bank 2 lcBankMemoryR = &rom[0xD000]; lcBankMemoryW = (altzp ? &ram2[0xD000] : &ram[0xD000]); upperMemoryR = &rom[0xE000]; upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]); break; case 0x02: #ifdef LC_DEBUG WriteLog("SwitchLC: Read ROM, no write\n"); #endif // [R ] Read ROM; no write lcBankMemoryR = &rom[0xD000]; lcBankMemoryW = 0; upperMemoryR = &rom[0xE000]; upperMemoryW = 0; break; case 0x03: #ifdef LC_DEBUG WriteLog("SwitchLC: Read/write bank 2\n"); #endif // [RR] Read RAM bank 2; write RAM bank 2 lcBankMemoryR = (altzp ? &ram2[0xD000] : &ram[0xD000]); lcBankMemoryW = (altzp ? &ram2[0xD000] : &ram[0xD000]); upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]); upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]); break; case 0x08: // [R ] Read RAM bank 1; no write lcBankMemoryR = (altzp ? &ram2[0xC000] : &ram[0xC000]); lcBankMemoryW = 0; upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]); upperMemoryW = 0; break; case 0x09: // [RR] Read ROM; write RAM bank 1 lcBankMemoryR = &rom[0xD000]; lcBankMemoryW = (altzp ? &ram2[0xC000] : &ram[0xC000]); upperMemoryR = &rom[0xE000]; upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]); break; case 0x0A: // [R ] Read ROM; no write lcBankMemoryR = &rom[0xD000]; lcBankMemoryW = 0; upperMemoryR = &rom[0xE000]; upperMemoryW = 0; break; case 0x0B: // [RR] Read RAM bank 1; write RAM bank 1 lcBankMemoryR = (altzp ? &ram2[0xC000] : &ram[0xC000]); lcBankMemoryW = (altzp ? &ram2[0xC000] : &ram[0xC000]); upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]); upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]); break; } } uint8_t SwitchTEXTR(uint16_t address) { WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off")); textMode = (bool)(address & 0x01); return 0; } void SwitchTEXTW(uint16_t address, uint8_t) { WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off")); textMode = (bool)(address & 0x01); } uint8_t SwitchMIXEDR(uint16_t address) { WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off")); mixedMode = (bool)(address & 0x01); return 0; } void SwitchMIXEDW(uint16_t address, uint8_t) { WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off")); mixedMode = (bool)(address & 0x01); } uint8_t SwitchPAGE2R(uint16_t address) { WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off")); displayPage2 = (bool)(address & 0x01); if (store80Mode) { mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); } return 0; } void SwitchPAGE2W(uint16_t address, uint8_t) { WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off")); displayPage2 = (bool)(address & 0x01); if (store80Mode) { mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]); } } uint8_t SwitchHIRESR(uint16_t address) { WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off")); hiRes = (bool)(address & 0x01); return 0; } void SwitchHIRESW(uint16_t address, uint8_t) { WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off")); hiRes = (bool)(address & 0x01); } uint8_t SwitchDHIRESR(uint16_t address) { WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off")); // Hmm, this breaks convention too, like SLOTCXROM if (ioudis) dhires = !((bool)(address & 0x01)); return 0; } void SwitchDHIRESW(uint16_t address, uint8_t) { WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off")); if (ioudis) dhires = !((bool)(address & 0x01)); } void SwitchIOUDIS(uint16_t address, uint8_t) { ioudis = !((bool)(address & 0x01)); } uint8_t ReadButton0(uint16_t) { return (uint8_t)openAppleDown << 7; } uint8_t ReadButton1(uint16_t) { return (uint8_t)closedAppleDown << 7; } // The way the paddles work is that a strobe is written (or read) to $C070, // then software counts down the time that it takes for the paddle outputs // to have bit 7 return to 0. If there are no paddles connected, bit 7 // stays at 1. // NB: This is really paddles 0-3, not just 0 :-P uint8_t ReadPaddle0(uint16_t) { return 0xFF; } uint8_t ReadIOUDIS(uint16_t) { return (uint8_t)ioudis << 7; } uint8_t ReadDHIRES(uint16_t) { return (uint8_t)dhires << 7; } // Whenever a read is done to a MMIO location that is unconnected to anything, // it actually sees the RAM access done by the video generation hardware. Some // programs exploit this, so we emulate it here. // N.B.: frameCycles will be off by the true amount because this only // increments by the amount of a speaker cycle, not the cycle count when // the access happens... !!! FIX !!! uint8_t ReadFloatingBus(uint16_t) { // Get the currently elapsed cycle count for this frame uint32_t frameCycles = mainCPU.clock - frameCycleStart; // Make counters out of the cycle count. There are 65 cycles per line. uint32_t numLines = frameCycles / 65; uint32_t numHTicks = frameCycles - (numLines * 65); // Convert these to H/V counters uint32_t hcount = numHTicks - 1; // HC sees zero twice: if (hcount == 0xFFFFFFFF) hcount = 0; uint32_t vcount = numLines + 0xFA; // Now do the address calculations uint32_t sum = 0xD + ((hcount & 0x38) >> 3) + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4)); uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07); // Add in particulars for the gfx mode we're in... if (textMode || (!textMode && !hiRes)) address |= (!(!store80Mode && displayPage2) ? 0x400 : 0) | (!store80Mode && displayPage2 ? 0x800 : 0); else address |= (!(!store80Mode && displayPage2) ? 0x2000: 0) | (!store80Mode && displayPage2 ? 0x4000 : 0) | ((vcount & 0x07) << 10); // The address so read is *always* in main RAM, not alt RAM return ram[address]; }