mirror of
https://github.com/pevans/erc-c.git
synced 2024-11-27 20:51:17 +00:00
251 lines
7.2 KiB
C
251 lines
7.2 KiB
C
/*
|
|
* 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;
|
|
}
|
|
}
|