diff --git a/cpu/cpu.go b/cpu/cpu.go index 8140d6c..4bb76b0 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -898,7 +898,7 @@ func SetColdStartReset() { // Reset sets the CPU and memory states so that a next call to cpu.Run() calls the firmware reset code func Reset() { - mmu.InitROM() + mmu.InitROM() // Set upper memory area for reading from ROM mmu.InitRAM() bootVector := 0xfffc diff --git a/mmu/io.go b/mmu/io.go index 4d59120..57c1aeb 100644 --- a/mmu/io.go +++ b/mmu/io.go @@ -93,6 +93,13 @@ const ( S6Q7H = 0xC0EF // write ) +// VideoState has 3 booleans which determine the video configuration: +// TextMode HiresMode Mixed +// text 1 0 N/A +// lores + text 0 0 1 +// lores 0 0 0 +// hires N/A 1 0 +// hires + text N/A 1 1 var VideoState struct { TextMode bool HiresMode bool @@ -113,6 +120,7 @@ func InitIO() { system.DriveState.Q6 = false system.DriveState.Q7 = false + // Initialize video VideoState.TextMode = true VideoState.HiresMode = false VideoState.Mixed = false @@ -120,12 +128,8 @@ func InitIO() { disk.InitDiskImage() } -func driveIsreadSequencing() bool { - return (!system.DriveState.Q6) && (!system.DriveState.Q7) -} - -// Handle soft switch addresses where both a read and a write has a side -// effect and the return value is meaningless +// Handle soft switch addresses between $c000-$c0ff where both a read and a write has a side +// effect. Returns true if the read/write has been handled. func readWrite(address uint16, isRead bool) bool { lsb := address & 0xff if lsb >= 0x80 && lsb < 0x90 { @@ -140,48 +144,56 @@ func readWrite(address uint16, isRead bool) bool { case SETAUXRD: SetFakeAuxMemoryRead(true) return true + case CLRAUXWR: SetFakeAuxMemoryWrite(false) return true case SETAUXWR: SetFakeAuxMemoryWrite(true) return true + case CLRAUXZP: SetFakeAltZP(false) return true case SETAUXZP: SetFakeAltZP(true) return true + case CLR80VID: SetCol80(false) return true case SET80VID: SetCol80(true) return true + case TXTPAGE1: SetPage2(false) return true case TXTPAGE2: SetPage2(true) return true + case CLRTEXT: VideoState.TextMode = false return true case SETTEXT: VideoState.TextMode = true return true + case CLRMIXED: VideoState.Mixed = false return true case SETMIXED: VideoState.Mixed = true return true + case CLRHIRES: VideoState.HiresMode = false return true case SETHIRES: VideoState.HiresMode = true return true + case CLR80COL: if !isRead { SetStore80(false) @@ -192,6 +204,7 @@ func readWrite(address uint16, isRead bool) bool { case SET80COL: SetStore80(true) return true + case STATEREG: // Ignore not implemented memory management reg return true @@ -200,37 +213,40 @@ func readWrite(address uint16, isRead bool) bool { case S6CLRDRVP0, S6SETDRVP0, S6CLRDRVP1, S6SETDRVP1, S6CLRDRVP2, S6SETDRVP2, S6CLRDRVP3, S6SETDRVP3: magnet := (address - S6CLRDRVP0) / 2 on := ((address - S6CLRDRVP0) % 2) == 1 - - if on { - system.DriveState.Phases |= (1 << magnet) - - // Move head if a neighboring magnet is on and all others are off - direction := int8(0) - if (system.DriveState.Phases & (1 << uint8((system.DriveState.Phase+1)&3))) != 0 { - direction += 1 - } - if (system.DriveState.Phases & (1 << uint8((system.DriveState.Phase+3)&3))) != 0 { - direction -= 1 - } - - if direction != 0 { - system.DriveState.Phase += direction - - if system.DriveState.Phase < 0 { - system.DriveState.Phase = 0 - } - if system.DriveState.Phase == 80 { - system.DriveState.Phase = 79 - } - - disk.MakeTrackData(uint8(system.DriveState.Phase)) - - if audio.ClickWhenDriveHeadMoves { - audio.Click() - } - } - } else { + if !on { + // Turn off the magnet in Phases system.DriveState.Phases &= ^(1 << magnet) + return true + } + + // Implicit else, a magnet has been switched on + system.DriveState.Phases |= (1 << magnet) + + // Move head if a neighboring magnet is on and all others are off + direction := int8(0) + if (system.DriveState.Phases & (1 << uint8((system.DriveState.Phase+1)&3))) != 0 { + direction += 1 + } + if (system.DriveState.Phases & (1 << uint8((system.DriveState.Phase+3)&3))) != 0 { + direction -= 1 + } + + // Move the head + if direction != 0 { + system.DriveState.Phase += direction + + if system.DriveState.Phase < 0 { + system.DriveState.Phase = 0 + } + if system.DriveState.Phase == 80 { + system.DriveState.Phase = 79 + } + + disk.MakeTrackData(uint8(system.DriveState.Phase)) + + if audio.ClickWhenDriveHeadMoves { + audio.Click() + } } return true @@ -241,12 +257,14 @@ func readWrite(address uint16, isRead bool) bool { case S6MOTORON: system.DriveState.Spinning = true return true + case S6SELDRV1: system.DriveState.Drive = 1 return true case S6SELDRV2: system.DriveState.Drive = 2 return true + case S6Q6L: if !isRead { system.DriveState.Q6 = false @@ -259,6 +277,7 @@ func readWrite(address uint16, isRead bool) bool { return true } return false + case S6Q7L: system.DriveState.Q7 = false return true @@ -271,12 +290,15 @@ func readWrite(address uint16, isRead bool) bool { } } +// ReadIO does a read in the $c000-$c0ff area func ReadIO(address uint16) uint8 { + // Try the generic readWrite and return if it has handled the read if readWrite(address, true) { return 0 } switch address { + case KEYBOARD, STROBE: keyBoardData, strobe := keyboard.Read() if address == KEYBOARD { @@ -285,15 +307,18 @@ func ReadIO(address uint16) uint8 { keyboard.ResetStrobe() return strobe } + case RDRAMRD, RDRAMWR, RDAUXZP: panic("Read/write aux memory not implemented") return 0x0d + case RDCXROM: if UsingExternalSlotRom { return 0x8d } else { return 0x0d } + case RD80VID: // using 80-column display mode not implemented return 0x0d @@ -308,24 +333,33 @@ func ReadIO(address uint16) uint8 { // 4-bit annunciator inputs case SETAN0, CLRAN0, SETAN1, CLRAN1, SETAN2, CLRAN2, SETAN3, CLRAN3: // Annunciators not implemented + case OPNAPPLE: // Open apple key not implemented return 0 + case CLSAPPLE: // Closed apple key not implemented + case RD80COL: if Store80 { return 0x8d } else { return 0x0d } + case RDALTCH: - // RDALTCH not implemented + // RDALTCH not implemented, but it's also used, so don't fail on it. return 0x0d + case SPEAKER: audio.Click() + return 0 + case S6Q6L: + // A read from disk return disk.ReadTrackData() + default: panic(fmt.Sprintf("TODO read %04x\n", address)) } @@ -333,31 +367,41 @@ func ReadIO(address uint16) uint8 { return 0 } +// ReadIO does a write in the $c000-$c0ff area func WriteIO(address uint16, value uint8) { + // Try the generic readWrite and return if it has handled the write if readWrite(address, false) { return } switch address { + case STROBE: keyboard.ResetStrobe() + case CLRCXROM: MapFirstHalfOfIO() case SETCXROM: MapSecondHalfOfIO() + case CLRALTCH: return case SETALTCH: panic("SETALTCH not implemented") + case CLR80COL: // CLR80COL not implemented return + case CLRC3ROM: // CLRC3ROM not implemented case SETC3ROM: // SETC3ROM not implemented + case S6Q6H: + // A write to disk disk.WriteTrackData(value) + default: panic(fmt.Sprintf("TODO write %04x\n", address)) } diff --git a/mmu/mmu.go b/mmu/mmu.go index 1aea872..3a95d9c 100644 --- a/mmu/mmu.go +++ b/mmu/mmu.go @@ -7,16 +7,18 @@ import ( "github.com/freewilll/apple2/system" ) -const RomPath = "apple2e.rom" -const StackPage = 1 +const RomPath = "apple2e.rom" // So far only one ROM is supported and it's loaded at startup +const StackPage = 1 // The 6502 stack is at 0x100 +// PhysicalMemory contains all the unmapped memory, ROM and RAM var PhysicalMemory struct { - MainMemory [0x10000]uint8 - UpperROM [0x3000]uint8 - RomC1 [0x1000]uint8 - RomC2 [0x1000]uint8 + 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 } +// Page tables for read & write var ReadPageTable [0x100][]uint8 var WritePageTable [0x100][]uint8 @@ -35,6 +37,7 @@ var ( Page2 bool // Main memory Page2 is selected ) +// Make page tables for current RAM, ROM and IO configuration func ApplyMemoryConfiguration() { // Map main RAM for read/write for i := 0x0; i < 0xc0; i++ { @@ -102,7 +105,7 @@ func MapSecondHalfOfIO() { ApplyMemoryConfiguration() } -// emptySlot zeroes all RAM for a slot +// 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 @@ -134,6 +137,7 @@ func InitApple2eROM() { InitROM() // Map 0xd000-0xffff for reading } +// Set upper memory area for reading from ROM func InitROM() { UpperReadMappedToROM = true ApplyMemoryConfiguration() @@ -149,31 +153,39 @@ func SetUpperRamReadOnly(value bool) { ApplyMemoryConfiguration() } +// Set d000 bank to map to $c000 or $d000 in the physical memory func SetD000Bank(value int) { D000Bank = value ApplyMemoryConfiguration() } +// 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() } +// 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() } +// Alternate zero page isn't implemented func SetFakeAltZP(value bool) { FakeAltZP = value ApplyMemoryConfiguration() } +// 80 column card isn't implemented func SetCol80(value bool) { Col80 = value // No changes are needed when this is toggled } +// Page switching is only implemented for the main memory 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 @@ -185,12 +197,14 @@ func SetPage2(value bool) { } } +// 80 column card isn't implemented 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 @@ -209,6 +223,7 @@ func WipeRAM() { } } +// 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 @@ -236,6 +251,7 @@ func SetMemoryMode(mode uint8) { ApplyMemoryConfiguration() } +// ReadMemory reads the ROM or RAM page table func ReadMemory(address uint16) uint8 { if (address >= 0xc000) && (address < 0xc100) { return ReadIO(address) @@ -257,15 +273,18 @@ func ReadMemory(address uint16) uint8 { } } + // Implicit else, we're reading the main non-IO RAM return ReadPageTable[address>>8][address&0xff] } +// ReadMemory 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) @@ -284,11 +303,14 @@ func WriteMemory(address uint16, value uint8) { } 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 {