/* * apple2.mem.c */ #include "apple2.h" #include "apple2.bank.h" #include "apple2.mem.h" #include "objstore.h" /* * These are the addresses that need to be mapped to the * bank_switch_read function. */ static size_t switch_reads[] = { 0xC080, 0xC081, 0xC082, 0xC083, 0xC088, 0xC089, 0xC08A, 0xC08B, 0xC011, 0xC012, 0xC016, }; /* * These will be mapped to the bank_switch_write function. */ static size_t switch_writes[] = { 0xC008, 0xC009, }; /* * Return a byte of memory from a bank-switchable address. This may be * from ROM, from main memory, or from the "extra" 4k bank of RAM. */ SEGMENT_READER(apple2_bank_read) { apple2 *mach; mach = (apple2 *)_mach; // In the case of bank-switchable memory, BANK_ALTZP is the ultimate // arbitrator; if it's on, we have to use aux, and if not, we have // to use main. Whatever the segment was that was passed in will // turn out to be immaterial. segment = (mach->bank_switch & BANK_ALTZP) ? mach->aux : mach->main; if (~mach->bank_switch & BANK_RAM) { // We need to account for the difference in address location // before we can successfully get any data from ROM. return vm_segment_get(mach->rom, addr - APPLE2_SYSROM_OFFSET); } // Each memory bank (main or auxiliary) have an additional 4k of RAM // that you can access through bank-switching in the $D000 - $DFFF // range, which is actually held at the _end_ of memory beyond the // 64k mark. if (addr < 0xE000 && mach->bank_switch & BANK_RAM2) { // The same caution holds for getting data from the // second RAM bank. return segment->memory[addr + 0x3000]; } // Otherwise, the byte is returned from bank 1 RAM, which is the // literal memory available in the segment. return segment->memory[addr]; } /* * Write a byte into bank-switchable memory. Many of the same cautions, * notes, etc. written for the read function apply here as well. */ SEGMENT_WRITER(apple2_bank_write) { apple2 *mach; mach = (apple2 *)_mach; // No writes are allowed... sorry! if (~mach->bank_switch & BANK_WRITE) { return; } // See my spiel in the read bank mapper; the same applies here. segment = (mach->bank_switch & BANK_ALTZP) ? mach->aux : mach->main; // You will note, if we've gotten here, that it's possible to write // to the bank-switch addresses even if the ROM flag is 1. It's // true! Except that writes never go to ROM. That is to say, it's // possible to read from ROM and write to RAM at the same // time--well, nearly the same time, considering the 6502 does not // allow parallel actions! // In this case, we need to assign the value at the 64-68k range at // the end of memory; this is just a simple offset from the given // address. if (addr < 0xE000 && mach->bank_switch & BANK_RAM2) { segment->memory[addr + 0x3000] = value; return; } // But if bank 2 RAM is not turned on, or the address is between // $E000 - $FFFF, then writes go to bank 1 RAM, which is our main // memory. segment->memory[addr] = value; } /* * This function will establish all of the mapper functions to handle * the soft switches for memory bank-switching. */ void apple2_bank_map(vm_segment *segment) { size_t addr; int i, rlen, wlen; for (addr = APPLE2_BANK_OFFSET; addr < MOS6502_MEMSIZE; addr++) { vm_segment_read_map(segment, addr, apple2_bank_read); vm_segment_write_map(segment, addr, apple2_bank_write); } rlen = sizeof(switch_reads) / sizeof(size_t); wlen = sizeof(switch_writes) / sizeof(size_t); for (i = 0; i < rlen; i++) { vm_segment_read_map(segment, switch_reads[i], apple2_bank_switch_read); } for (i = 0; i < wlen; i++) { vm_segment_write_map(segment, switch_writes[i], apple2_bank_switch_write); } } /* * Handle reads to the soft switches that handle bank-switching. Note * that some of these "reads" actually modify how banks are switched * between ROM, RAM, or bank 2 RAM. Sorry about that -- it's just the * way it worked on the Apple II. */ SEGMENT_READER(apple2_bank_switch_read) { apple2 *mach; vm_16bit last_addr; mach = (apple2 *)_mach; // We need to know the last opcode and address, because some of our // soft switches require two consecutive reads mos6502_last_executed(mach->cpu, NULL, NULL, &last_addr); switch (addr) { // The $C080 - $C083 range all control memory access while using // bank 2 RAM for the $Dnnn range. Note that here and in the // $C088 range, the returns are zero; I'm not exactly sure // that's what they should be, but the purpose of reading from // these soft switches is not actually to read anything useful, // but simply to change the bank switch mode. case 0xC080: apple2_set_bank_switch(mach, BANK_RAM | BANK_RAM2); break; case 0xC081: if (last_addr == addr) { apple2_set_bank_switch(mach, BANK_WRITE | BANK_RAM2); } break; case 0xC082: apple2_set_bank_switch(mach, BANK_RAM2); break; case 0xC083: if (last_addr == addr) { apple2_set_bank_switch(mach, BANK_RAM | BANK_WRITE | BANK_RAM2); } break; // Conversely, the $C088 - $C08B range control memory access // while using bank 1 RAM. case 0xC088: apple2_set_bank_switch(mach, BANK_RAM); break; case 0xC089: if (last_addr == addr) { apple2_set_bank_switch(mach, BANK_WRITE); } break; case 0xC08A: apple2_set_bank_switch(mach, BANK_DEFAULT); break; case 0xC08B: if (last_addr == addr) { apple2_set_bank_switch(mach, BANK_RAM | BANK_WRITE); } break; // Return high on the 7th bit if we're using bank 2 memory case 0xC011: return mach->bank_switch & BANK_RAM2 ? 0x80 : 0x00; // Return high on 7th bit if we're reading RAM case 0xC012: return mach->bank_switch & BANK_RAM ? 0x80 : 0x00; // Return high on the 7th bit if we are using the zero page and // stack from aux memory. case 0xC016: return mach->bank_switch & BANK_ALTZP ? 0x80 : 0x00; } return 0; } /* * Handle writes to the soft switches that modify bank-switching * behavior. */ SEGMENT_WRITER(apple2_bank_switch_write) { apple2 *mach = (apple2 *)_mach; switch (addr) { // Turn on auxiliary memory for zero page + stack case 0xC008: apple2_set_bank_switch(mach, mach->bank_switch | BANK_ALTZP); break; // Disable auxiliary memory for zero page + stack case 0xC009: apple2_set_bank_switch(mach, mach->bank_switch & ~BANK_ALTZP); break; } }