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:
parent
3738d70a43
commit
45eb7b5e58
@ -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 *);
|
||||
|
224
src/apple2.mem.c
224
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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user