mirror of
https://github.com/freewilll/apple2-go.git
synced 2024-11-15 05:06:43 +00:00
334 lines
8.8 KiB
Go
334 lines
8.8 KiB
Go
package mmu
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
|
|
"github.com/freewilll/apple2-go/system"
|
|
)
|
|
|
|
// RomPath is a hardcoded path to an Apple //e ROM file that's loaded at startup
|
|
const RomPath = "apple2e.rom"
|
|
|
|
// StackPage is the location of the 6504 stack
|
|
const StackPage = 1
|
|
|
|
// PhysicalMemory contains all the unmapped memory, ROM and RAM
|
|
var PhysicalMemory struct {
|
|
MainMemory [0x10000]uint8 // Main RAM
|
|
UpperROM [0x3000]uint8 // $c000-$ffff ROM area
|
|
RomC1 [0x1000]uint8 // First half of IO ROM
|
|
RomC2 [0x1000]uint8 // Second half of IO ROM
|
|
}
|
|
|
|
// ReadPageTable is the page table for reads
|
|
var ReadPageTable [0x100][]uint8
|
|
|
|
// WritePageTable is the page table for writes
|
|
var WritePageTable [0x100][]uint8
|
|
|
|
// Memory mapping states
|
|
var (
|
|
D000Bank int // one maps to $c000, two maps to $d000
|
|
UsingExternalSlotRom bool // Which IO ROM is being used
|
|
UpperReadMappedToROM bool // Do reads go to the RAM or ROM
|
|
UpperRAMReadOnly bool // Is the upper RAM read only
|
|
FakeAuxMemoryRead bool // Aux memory isn't implemented
|
|
FakeAuxMemoryWrite bool // Aux memory isn't implemented
|
|
FakeAltZP bool // Aux memory isn't implemented
|
|
FakePage2 bool // Aux memory isn't implemented
|
|
Col80 bool // 80 Column card is on (not implemented)
|
|
Store80 bool // 80 Column card is on (not implemented)
|
|
Page2 bool // Main memory Page2 is selected
|
|
)
|
|
|
|
// ApplyMemoryConfiguration creates the page tables for current RAM, ROM and IO configuration
|
|
func ApplyMemoryConfiguration() {
|
|
// Map main RAM for read/write
|
|
for i := 0x0; i < 0xc0; i++ {
|
|
ReadPageTable[i] = PhysicalMemory.MainMemory[i*0x100 : i*0x100+0x100]
|
|
WritePageTable[i] = PhysicalMemory.MainMemory[i*0x100 : i*0x100+0x100]
|
|
}
|
|
|
|
// Map $c000
|
|
var ioRom *[0x1000]uint8
|
|
if UsingExternalSlotRom {
|
|
ioRom = &PhysicalMemory.RomC2
|
|
} else {
|
|
ioRom = &PhysicalMemory.RomC1
|
|
}
|
|
|
|
for i := 0x1; i < 0x10; i++ {
|
|
ReadPageTable[0xc0+i] = (*ioRom)[i*0x100 : i*0x100+0x100]
|
|
WritePageTable[0xc0+i] = nil
|
|
}
|
|
|
|
// Map $d000
|
|
for i := 0xd0; i < 0xe0; i++ {
|
|
base := i*0x100 + D000Bank*0x1000 - 0x2000
|
|
if !UpperReadMappedToROM {
|
|
ReadPageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
|
}
|
|
|
|
if UpperRAMReadOnly {
|
|
WritePageTable[i] = nil
|
|
} else {
|
|
WritePageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
|
}
|
|
}
|
|
|
|
// Map 0xe00 to 0xffff
|
|
for i := 0xe0; i < 0x100; i++ {
|
|
base := i * 0x100
|
|
if !UpperReadMappedToROM {
|
|
ReadPageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
|
}
|
|
if UpperRAMReadOnly {
|
|
WritePageTable[i] = nil
|
|
} else {
|
|
WritePageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
|
}
|
|
}
|
|
|
|
if UpperReadMappedToROM {
|
|
for i := 0x00; i < 0x30; i++ {
|
|
ReadPageTable[i+0xd0] = PhysicalMemory.UpperROM[i*0x100 : i*0x100+0x100]
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MapFirstHalfOfIO maps 0xc100-0xcfff for reading from RomC1
|
|
func MapFirstHalfOfIO() {
|
|
UsingExternalSlotRom = false
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// MapSecondHalfOfIO map 0xc100-0xcfff for reading from RomC2
|
|
func MapSecondHalfOfIO() {
|
|
UsingExternalSlotRom = true
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// emptySlot zeroes all RAM for a slot, effectively disabling the slot
|
|
func emptySlot(slot int) {
|
|
for i := slot * 0x100; i < (slot+1)*0x100; i++ {
|
|
PhysicalMemory.RomC1[i] = 0
|
|
PhysicalMemory.RomC2[i] = 0
|
|
}
|
|
}
|
|
|
|
func loadApple2eROM() {
|
|
bytes, err := ioutil.ReadFile(RomPath)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Unable to read ROM: %s", err))
|
|
}
|
|
|
|
// Copy both I/O areas over c000-cfff, including unused c000-c0ff
|
|
for i := 0x0000; i < 0x1000; i++ {
|
|
PhysicalMemory.RomC1[i] = bytes[i]
|
|
PhysicalMemory.RomC2[i] = bytes[i+0x4000]
|
|
}
|
|
|
|
// Copy ROM over for 0xd000-0xffff area
|
|
for i := 0x0; i < 0x3000; i++ {
|
|
PhysicalMemory.UpperROM[i] = bytes[i+0x1000]
|
|
}
|
|
}
|
|
|
|
// InitApple2eROM loads the ROM and inits the ROM page tables
|
|
func InitApple2eROM() {
|
|
loadApple2eROM()
|
|
MapFirstHalfOfIO() // Map 0xc100-0xcfff for reading
|
|
InitROM() // Map 0xd000-0xffff for reading
|
|
}
|
|
|
|
// InitROM sets the upper memory area for reading from ROM
|
|
func InitROM() {
|
|
UpperReadMappedToROM = true
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// SetUpperReadMappedToROM sets the upper area so that reads are done from the ROM if true or RAM if false
|
|
func SetUpperReadMappedToROM(value bool) {
|
|
UpperReadMappedToROM = value
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// SetUpperRAMReadOnly sets the upper RAM area to read only
|
|
func SetUpperRAMReadOnly(value bool) {
|
|
UpperRAMReadOnly = value
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// SetD000Bank sets the $d000 bank to map to $c000 or $d000 in the physical memory
|
|
func SetD000Bank(value int) {
|
|
D000Bank = value
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// SetFakeAuxMemoryRead sets an internal state to fake aux memory reads. Aux memory hasn't been implemented. If aux memory is selected, and a read
|
|
// is attempted, then nonsense must be returned.
|
|
func SetFakeAuxMemoryRead(value bool) {
|
|
FakeAuxMemoryRead = value
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// SetFakeAuxMemoryWrite sets an internal state to fake aux memory writes. Aux memory hasn't been implemented. If aux memory is selected, and a write
|
|
// is attempted, then it must be ignored.
|
|
func SetFakeAuxMemoryWrite(value bool) {
|
|
FakeAuxMemoryWrite = value
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// SetFakeAltZP sets an internal state to fake a missing alternate zero page. Alternate zero page isn't implemented
|
|
func SetFakeAltZP(value bool) {
|
|
FakeAltZP = value
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// SetCol80 sets an internal state to fake a missing 80 column card
|
|
func SetCol80(value bool) {
|
|
Col80 = value
|
|
// No changes are needed when this is toggled
|
|
}
|
|
|
|
// SetPage2 sets page1/page2 in text, lores or hires. This only works if the
|
|
// 80 column card is disabled since the 80 columns card has not yet nbeen
|
|
// implemented.
|
|
func SetPage2(value bool) {
|
|
// If the 80 column card is enabled, then this toggles aux memory
|
|
// Otherwise, page1/page2 is toggled in the main memory
|
|
if Col80 {
|
|
FakePage2 = value
|
|
ApplyMemoryConfiguration()
|
|
} else {
|
|
Page2 = value
|
|
}
|
|
}
|
|
|
|
// SetStore80 sets an internal state to fake a missing 80 column card
|
|
func SetStore80(value bool) {
|
|
Store80 = value
|
|
FakePage2 = value
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// InitRAM sets all default RAM memory settings and resets the page tables
|
|
func InitRAM() {
|
|
UpperRAMReadOnly = false
|
|
D000Bank = 2
|
|
FakeAuxMemoryRead = false // Aux memory isn't implemented
|
|
FakeAuxMemoryWrite = false // Aux memory isn't implemented
|
|
FakeAltZP = false // Aux memory isn't implemented
|
|
FakePage2 = false // Aux memory isn't implemented
|
|
Col80 = false // Aux memory isn't implemented
|
|
Page2 = false
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// WipeRAM wipes all the physical RAM
|
|
func WipeRAM() {
|
|
for i := 0; i < 0x10000; i++ {
|
|
PhysicalMemory.MainMemory[i] = 0
|
|
}
|
|
}
|
|
|
|
// SetMemoryMode is used to set UpperRAMReadOnly, UpperReadMappedToROM and D000Bank number
|
|
func SetMemoryMode(mode uint8) {
|
|
// mode corresponds to a read/write to $c080 with
|
|
// $c080 mode=$00
|
|
// $c08f mode=$0f
|
|
|
|
if (mode & 1) == 0 {
|
|
UpperRAMReadOnly = true
|
|
} else {
|
|
UpperRAMReadOnly = false
|
|
}
|
|
|
|
if (((mode & 2) >> 1) ^ (mode & 1)) == 0 {
|
|
UpperReadMappedToROM = false
|
|
|
|
} else {
|
|
UpperReadMappedToROM = true
|
|
}
|
|
|
|
if (mode & 8) == 0 {
|
|
D000Bank = 2
|
|
} else {
|
|
D000Bank = 1
|
|
}
|
|
|
|
ApplyMemoryConfiguration()
|
|
}
|
|
|
|
// ReadMemory reads the ROM or RAM page table
|
|
func ReadMemory(address uint16) uint8 {
|
|
if (address >= 0xc000) && (address < 0xc100) {
|
|
return ReadIO(address)
|
|
}
|
|
|
|
if FakePage2 && (address >= 0x400 && address < 0x800) {
|
|
// Return nothingness
|
|
return uint8(0x00)
|
|
}
|
|
|
|
if FakeAuxMemoryRead {
|
|
if address >= 0x200 {
|
|
// Return nothingness
|
|
return uint8(0x00)
|
|
}
|
|
if FakeAltZP {
|
|
return uint8(0x00)
|
|
}
|
|
|
|
}
|
|
|
|
// Implicit else, we're reading the main non-IO RAM
|
|
return ReadPageTable[address>>8][address&0xff]
|
|
}
|
|
|
|
// WriteMemory writes to the ROM or RAM page table
|
|
func WriteMemory(address uint16, value uint8) {
|
|
if (address >= 0xc000) && (address < 0xc100) {
|
|
WriteIO(address, value)
|
|
return
|
|
}
|
|
|
|
// Magic routine to trigger an interrupt, used in the CPU interrupt tests
|
|
if system.RunningInterruptTests && address == 0xbffc {
|
|
oldValue := ReadMemory(address)
|
|
system.WriteInterruptTestOpenCollector(address, oldValue, value)
|
|
WritePageTable[uint8(address>>8)][uint8(address&0xff)] = value
|
|
return
|
|
}
|
|
|
|
if FakePage2 && (address >= 0x400 && address < 0x800) {
|
|
// Do nothing
|
|
return
|
|
}
|
|
|
|
if FakeAuxMemoryWrite {
|
|
// If there is no aux memory, then the write is ignored.
|
|
return
|
|
}
|
|
|
|
memory := WritePageTable[address>>8]
|
|
|
|
// If memory is nil, then it's read only. The write is ignored.
|
|
if memory != nil {
|
|
memory[uint8(address&0xff)] = value
|
|
}
|
|
|
|
// If doing CPU functional tests, 0x200 has the test number in it. A write to
|
|
// it means a test passed or the tests are complete.
|
|
if system.RunningFunctionalTests && address == 0x200 {
|
|
testNumber := ReadMemory(0x200)
|
|
if testNumber == 0xf0 {
|
|
fmt.Println("Opcode testing completed")
|
|
} else {
|
|
fmt.Printf("Test %d OK\n", ReadMemory(0x200))
|
|
}
|
|
}
|
|
}
|