diff --git a/include/apple2.mem.h b/include/apple2.mem.h index b36f902..fcec7be 100644 --- a/include/apple2.mem.h +++ b/include/apple2.mem.h @@ -56,6 +56,8 @@ */ #define APPLE2_BANK_OFFSET 0xD000 +extern SEGMENT_READER(apple2_mem_zp_read); +extern SEGMENT_WRITER(apple2_mem_zp_write); extern int apple2_mem_init_peripheral_rom(apple2 *); extern int apple2_mem_init_sys_rom(apple2 *); extern void apple2_mem_map(apple2 *, vm_segment *); diff --git a/src/apple2.mem.c b/src/apple2.mem.c index a4421de..e75d6ef 100644 --- a/src/apple2.mem.c +++ b/src/apple2.mem.c @@ -7,82 +7,6 @@ #include "apple2.bank.h" #include "objstore.h" -/* - * 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_mem_read_bank) -{ - 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_BANK_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_mem_write_bank) -{ - 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; -} - /* * Set the memory map functions for main memory in an apple2 machine */ @@ -93,7 +17,18 @@ apple2_mem_map(apple2 *mach, vm_segment *segment) vm_segment_set_map_machine(mach); + // Set up all of the bank-switch-related mapping. Well--almost all + // of it. apple2_bank_map(segment); + + // We will do the mapping for the zero page and stack addresses. + // Accessing those addresses can be affected by bank-switching, but + // those addresses do not actually exist in the capital + // Bank-Switching address space. + for (addr = 0x0; addr < 0x200; addr++) { + vm_segment_read_map(segment, addr, apple2_mem_zp_read); + vm_segment_write_map(segment, addr, apple2_mem_zp_write); + } } /* @@ -154,115 +89,40 @@ apple2_mem_init_sys_rom(apple2 *mach) } /* - * 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. + * This is a wrapper for reads in the zero and stack pages; if + * BANK_ALTZP is high, then we need to use aux memory regardless of the + * segment passed into the function. Otherwise, we need to use main + * memory--again, regardless of the passed-in segment. */ -SEGMENT_READER(apple2_mem_read_bank_switch) -{ - 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); - return 0; - - case 0xC081: - if (last_addr == addr) { - apple2_set_bank_switch(mach, BANK_WRITE | BANK_RAM2); - } - return 0; - case 0xC082: - apple2_set_bank_switch(mach, BANK_RAM2); - return 0; - - case 0xC083: - if (last_addr == addr) { - apple2_set_bank_switch(mach, BANK_RAM | BANK_WRITE | BANK_RAM2); - } - return 0; - - // Conversely, the $C088 - $C08B range control memory access - // while using bank 1 RAM. - case 0xC088: - apple2_set_bank_switch(mach, BANK_RAM); - return 0; - - case 0xC089: - if (last_addr == addr) { - apple2_set_bank_switch(mach, BANK_WRITE); - } - return 0; - - case 0xC08A: - apple2_set_bank_switch(mach, BANK_DEFAULT); - return 0; - - case 0xC08B: - if (last_addr == addr) { - apple2_set_bank_switch(mach, BANK_RAM | BANK_WRITE); - } - return 0; - - // 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; - } - - log_critical("Bank switch read mapper called with an unexpected address: %x", addr); - return 0; -} - -/* - * Handle writes to the soft switches that modify bank-switching - * behavior. - */ -SEGMENT_WRITER(apple2_mem_write_bank_switch) +SEGMENT_READER(apple2_mem_zp_read) { 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); - return; + // This is another case (see apple2.bank.c) where we don't care the + // originating segment was; we only care whether BANK_ALTZP is on or + // not. Like bank-switchable addresses, if it is on, then we should + // be referencing aux memory; if not, main memory. + segment = (mach->bank_switch & BANK_ALTZP) + ? mach->aux + : mach->main; - // Disable auxiliary memory for zero page + stack - case 0xC009: - apple2_set_bank_switch(mach, - mach->bank_switch & ~BANK_ALTZP); - return; - } - - log_critical("Bank switch write mapper called with an unexpected address: %x", addr); + return segment->memory[addr]; +} + +/* + * This write function will intercept writes to the zero page and the + * stack page; primarily as a wrapper to handle the BANK_ALTZP bit in + * the bank_switch field of the apple2 struct. + */ +SEGMENT_WRITER(apple2_mem_zp_write) +{ + apple2 *mach = (apple2 *)_mach; + + // See the zp_read function for further details; the same logic + // applies here. + segment = (mach->bank_switch & BANK_ALTZP) + ? mach->aux + : mach->main; + + segment->memory[addr] = value; } diff --git a/tests/apple2.c b/tests/apple2.c index da79bd5..a7dcd0c 100644 --- a/tests/apple2.c +++ b/tests/apple2.c @@ -149,3 +149,16 @@ Test(apple2, reset) cr_assert_eq(mach->cpu->P, MOS_INTERRUPT); cr_assert_eq(mach->cpu->S, 0); } + +Test(apple2, set_memory_mode) +{ + apple2_set_memory_mode(mach, MEMORY_READ_AUX); + cr_assert_eq(mach->memory_mode, MEMORY_READ_AUX); + cr_assert_eq(mach->cpu->rmem, mach->aux); + cr_assert_eq(mach->cpu->wmem, mach->main); + + apple2_set_memory_mode(mach, MEMORY_WRITE_AUX); + cr_assert_eq(mach->memory_mode, MEMORY_WRITE_AUX); + cr_assert_eq(mach->cpu->rmem, mach->main); + cr_assert_eq(mach->cpu->wmem, mach->aux); +} diff --git a/tests/apple2.mem.c b/tests/apple2.mem.c index 4a4113b..256f2d7 100644 --- a/tests/apple2.mem.c +++ b/tests/apple2.mem.c @@ -22,6 +22,23 @@ teardown() TestSuite(apple2_mem, .init = setup, .fini = teardown); +Test(apple2_mem, map) +{ + size_t addr; + int i; + vm_segment *segments[2]; + + segments[0] = mach->main; + segments[1] = mach->aux; + + for (i = 0; i < 2; i++) { + for (addr = 0x0; addr < 0x200; addr++) { + cr_assert_eq(segments[i]->read_table[addr], apple2_mem_zp_read); + cr_assert_eq(segments[i]->write_table[addr], apple2_mem_zp_write); + } + } +} + Test(apple2_mem, init_peripheral_rom) { // FIXME: this isn't working, _and_ it's pretty tightly coupled into @@ -34,3 +51,29 @@ Test(apple2_mem, init_sys_rom) // FIXME: same //cr_assert_eq(apple2_mem_init_sys_rom(mach), OK); } + +/* + * You may notice some direct accesses to the memory field; it's needed + * to get around the mapper functionality we're trying to test! This + * test also works on both the read/write mapper functionality. + * + * Test(apple2_mem, zp_write) + */ +Test(apple2_mem, zp_read) +{ + apple2_set_bank_switch(mach, BANK_DEFAULT); + mos6502_set(mach->cpu, 0, 123); + cr_assert_eq(mach->main->memory[0], 123); + cr_assert_neq(mach->aux->memory[0], 123); + + // Once we switch to BANK_ALTZP, we should see that the data in main + // got copied over. That's tested elsewhere, but I put it here just + // to make sure you have the right mental model when looking at this + // test code. + apple2_set_bank_switch(mach, BANK_ALTZP); + cr_assert_eq(mach->aux->memory[0], 123); + + mos6502_set(mach->cpu, 0, 234); + cr_assert_neq(mach->main->memory[0], 234); + cr_assert_eq(mach->aux->memory[0], 234); +} diff --git a/tests/mos6502.c b/tests/mos6502.c index 3c7e19c..7db1d01 100644 --- a/tests/mos6502.c +++ b/tests/mos6502.c @@ -188,3 +188,24 @@ Test(mos6502, set_memory) cr_assert_eq(cpu->rmem, rmem); cr_assert_eq(cpu->wmem, wmem); } + +Test(mos6502, last_executed) +{ + vm_8bit opcode, operand; + vm_16bit addr; + + mos6502_set(cpu, 0, 0xA9); // LDA #$EE + mos6502_set(cpu, 1, 0xEE); + mos6502_set(cpu, 2, 0x8D); // STA $1234 + mos6502_set16(cpu, 3, 0x1234); + + mos6502_execute(cpu); + mos6502_last_executed(cpu, &opcode, &operand, NULL); + cr_assert_eq(opcode, 0xA9); + cr_assert_eq(operand, 0xEE); + + mos6502_execute(cpu); + mos6502_last_executed(cpu, &opcode, NULL, &addr); + cr_assert_eq(opcode, 0x8D); + cr_assert_eq(addr, 0x1234); +}