1
0
mirror of https://github.com/pevans/erc-c.git synced 2024-08-20 09:29:01 +00:00

Add zero page memory mapper functions

This also adds tests for those functions. We have, furthermore, removed
some redundant (and non-externed!) functions from apple2.mem.c which was
the old bank switching code that got moved to apple2.bank.c.
This commit is contained in:
Peter Evans 2018-01-13 00:38:29 -06:00
parent 3738d70a43
commit 45eb7b5e58
5 changed files with 121 additions and 182 deletions

View File

@ -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 *);

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}