apple2-go/mmu/mmu.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))
}
}
}