From f1dff515643a8ea4345095e8b7621b65ab94ba5b Mon Sep 17 00:00:00 2001 From: Will Angenent Date: Sun, 20 May 2018 11:02:08 +0100 Subject: [PATCH] Added bank switched upper memory --- bank_switch_test.go | 109 +++++++++++++++++++++++++++++++ cpu/cpu.go | 19 +++--- cpu/cpu_test.go | 3 +- cpu/debug.go | 14 ++-- io_test.go | 30 +++++++++ mmu/io.go | 10 +++ mmu/mmu.go | 156 ++++++++++++++++++++++++++++++++++++-------- video/video.go | 6 +- 8 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 bank_switch_test.go create mode 100644 io_test.go diff --git a/bank_switch_test.go b/bank_switch_test.go new file mode 100644 index 0000000..4080a03 --- /dev/null +++ b/bank_switch_test.go @@ -0,0 +1,109 @@ +package main + +import ( + "mos6502go/cpu" + "mos6502go/keyboard" + "mos6502go/mmu" + "mos6502go/system" + "mos6502go/video" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBankSwitching(t *testing.T) { + cpu.InitInstructionDecoder() + mmu.InitRAM() + mmu.InitApple2eROM() + mmu.InitIO() + cpu.Init() + keyboard.Init() + video.Init() + system.Init() + cpu.SetColdStartReset() + cpu.Reset() + + // Sanity test that what we expect from the apple //e ROM is correct + assert.Equal(t, uint8(0x6f), mmu.ReadMemory(0xd000)) // read from ROM + assert.Equal(t, uint8(0xc3), mmu.ReadMemory(0xffff)) // read from ROM + + // Verify ROM & RAM settings at startup + mmu.WipeRAM() + assert.Equal(t, uint8(0xc3), mmu.ReadMemory(0xffff)) // read from ROM + mmu.WriteMemory(0xffff, 0xff) // write to $ffff + assert.Equal(t, uint8(0xc3), mmu.ReadMemory(0xffff)) // ROM value is the same + assert.Equal(t, uint8(0xff), mmu.PhysicalMemory.MainMemory[0xffff]) // RAM has been updated + mmu.WriteMemory(0xd000, 0xfe) // write to $d000 + assert.Equal(t, uint8(0x00), mmu.PhysicalMemory.MainMemory[0xc000]) // bank #1 RAM + assert.Equal(t, uint8(0xfe), mmu.PhysicalMemory.MainMemory[0xd000]) // bank #2 RAM + + // Switch bank to 1, write and check physical memory + mmu.SetD000Bank(1) + mmu.SetUpperReadMappedToROM(false) + mmu.WriteMemory(0xd000, 0xfd) // write to $d000 + assert.Equal(t, uint8(0xfd), mmu.PhysicalMemory.MainMemory[0xc000]) // bank #1 RAM + assert.Equal(t, uint8(0xfe), mmu.PhysicalMemory.MainMemory[0xd000]) // bank #2 RAM + + // Enable RAM area for reading and check values + mmu.SetUpperReadMappedToROM(false) + assert.Equal(t, uint8(0xfd), mmu.ReadMemory(0xd000)) // read from bank #1 RAM + mmu.SetD000Bank(2) + assert.Equal(t, uint8(0xfe), mmu.ReadMemory(0xd000)) // read from bank #1 RAM + + // Enable ROM area for reading and check values + mmu.SetUpperReadMappedToROM(true) + assert.Equal(t, uint8(0x6f), mmu.ReadMemory(0xd000)) // read from ROM + assert.Equal(t, uint8(0xc3), mmu.ReadMemory(0xffff)) // read from ROM + + // Set d000 RAM to bank 1, RAM to read only and attempt writes + mmu.SetD000Bank(1) + mmu.SetUpperRamReadOnly(true) + assert.Equal(t, uint8(0xfd), mmu.PhysicalMemory.MainMemory[0xc000]) // bank #1 RAM + assert.Equal(t, uint8(0xfe), mmu.PhysicalMemory.MainMemory[0xd000]) // bank #2 RAM + mmu.WriteMemory(0xd000, 0x01) // attempt to write to read only RAM + mmu.WriteMemory(0xffff, 0x02) // attempt to write to read only RAM + assert.Equal(t, uint8(0xfd), mmu.PhysicalMemory.MainMemory[0xc000]) // bank #1 RAM is unchanged + assert.Equal(t, uint8(0xfe), mmu.PhysicalMemory.MainMemory[0xd000]) // bank #2 RAM is unchanged + assert.Equal(t, uint8(0xff), mmu.PhysicalMemory.MainMemory[0xffff]) // top of RAM is unchanged + + // Set RAM to write and write to it + mmu.SetUpperRamReadOnly(false) + mmu.WriteMemory(0xd000, 0xfc) // write to RAM + mmu.WriteMemory(0xffff, 0xfb) // write to RAM + assert.Equal(t, uint8(0xfc), mmu.PhysicalMemory.MainMemory[0xc000]) // bank #1 RAM has been updated + assert.Equal(t, uint8(0xfe), mmu.PhysicalMemory.MainMemory[0xd000]) // bank #2 RAM is untouched + assert.Equal(t, uint8(0xfb), mmu.PhysicalMemory.MainMemory[0xffff]) // top of RAM has been updated + + // Enable ROM area for reading and check values + mmu.SetUpperReadMappedToROM(true) + assert.Equal(t, uint8(0x6f), mmu.ReadMemory(0xd000)) // read from ROM + assert.Equal(t, uint8(0xc3), mmu.ReadMemory(0xffff)) // read from ROM + + testSwitches(t) +} + +func assertMemoryConfiguration(t *testing.T, address uint16, upperRamReadOnly bool, upperReadMappedToROM bool, d000Bank int) { + mmu.WriteMemory(address, 0x00) + // assert.Equal(t, upperRamReadOnly, mmu.UpperRamReadOnly) + assert.Equal(t, upperReadMappedToROM, mmu.UpperReadMappedToROM) + assert.Equal(t, d000Bank, mmu.D000Bank) +} + +func testSwitches(t *testing.T) { + assertMemoryConfiguration(t, 0xc080, true, false, 2) + assertMemoryConfiguration(t, 0xc081, false, true, 2) + assertMemoryConfiguration(t, 0xc082, true, true, 2) + assertMemoryConfiguration(t, 0xc083, false, false, 2) + assertMemoryConfiguration(t, 0xc084, true, false, 2) + assertMemoryConfiguration(t, 0xc085, false, true, 2) + assertMemoryConfiguration(t, 0xc086, true, true, 2) + assertMemoryConfiguration(t, 0xc087, false, false, 2) + assertMemoryConfiguration(t, 0xc088, true, false, 1) + assertMemoryConfiguration(t, 0xc089, false, true, 1) + assertMemoryConfiguration(t, 0xc08a, true, true, 1) + assertMemoryConfiguration(t, 0xc08b, false, false, 1) + assertMemoryConfiguration(t, 0xc08c, true, false, 1) + assertMemoryConfiguration(t, 0xc08d, false, true, 1) + assertMemoryConfiguration(t, 0xc08e, true, true, 1) + assertMemoryConfiguration(t, 0xc08f, false, false, 1) +} diff --git a/cpu/cpu.go b/cpu/cpu.go index e4d51cf..de4602d 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -94,14 +94,14 @@ func isN() bool { } func push8(value uint8) { - mmu.PageTable[mmu.StackPage][State.SP] = value + mmu.WritePageTable[mmu.StackPage][State.SP] = value State.SP -= 1 State.SP &= 0xff } func push16(value uint16) { - mmu.PageTable[mmu.StackPage][State.SP] = uint8(value >> 8) - mmu.PageTable[mmu.StackPage][State.SP-1] = uint8(value & 0xff) + mmu.WritePageTable[mmu.StackPage][State.SP] = uint8(value >> 8) + mmu.WritePageTable[mmu.StackPage][State.SP-1] = uint8(value & 0xff) State.SP -= 2 State.SP &= 0xff } @@ -109,14 +109,14 @@ func push16(value uint16) { func pop8() uint8 { State.SP += 1 State.SP &= 0xff - return mmu.PageTable[mmu.StackPage][State.SP] + return mmu.ReadPageTable[mmu.StackPage][State.SP] } func pop16() uint16 { State.SP += 2 State.SP &= 0xff - msb := uint16(mmu.PageTable[mmu.StackPage][State.SP]) - lsb := uint16(mmu.PageTable[mmu.StackPage][State.SP-1]) + msb := uint16(mmu.ReadPageTable[mmu.StackPage][State.SP]) + lsb := uint16(mmu.ReadPageTable[mmu.StackPage][State.SP-1]) return lsb + msb<<8 } @@ -864,8 +864,11 @@ func SetColdStartReset() { } func Reset() { + mmu.InitROM() + mmu.InitRAM() + bootVector := 0xfffc - lsb := mmu.PageTable[bootVector>>8][bootVector&0xff] // TODO move readMemory to mmu - msb := mmu.PageTable[(bootVector+1)>>8][(bootVector+1)&0xff] + lsb := mmu.ReadPageTable[bootVector>>8][bootVector&0xff] + msb := mmu.ReadPageTable[(bootVector+1)>>8][(bootVector+1)&0xff] State.PC = uint16(lsb) + uint16(msb)<<8 } diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go index f0f67cc..07271e7 100644 --- a/cpu/cpu_test.go +++ b/cpu/cpu_test.go @@ -68,7 +68,8 @@ func TestCPU(t *testing.T) { RomPretendingToBeRAM[i] = bytes[0xc000+i] } for i := 0x0; i < 0x40; i++ { - mmu.PageTable[0xc0+i] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100] + mmu.ReadPageTable[0xc0+i] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100] + mmu.WritePageTable[0xc0+i] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100] } keyboard.Init() diff --git a/cpu/debug.go b/cpu/debug.go index 219b96d..ba35413 100644 --- a/cpu/debug.go +++ b/cpu/debug.go @@ -40,7 +40,7 @@ func printInstruction(instruction string, showRegisters bool) { } func PrintInstruction(showRegisters bool) { - opcodeValue := mmu.PageTable[(State.PC)>>8][(State.PC)&0xff] + opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff] opcode := OpCodes[opcodeValue] mnemonic := opcode.Mnemonic size := opcode.AddressingMode.OperandSize @@ -56,7 +56,7 @@ func PrintInstruction(showRegisters bool) { var suffix string if opcode.AddressingMode.Mode == AmRelative { - value = uint16(mmu.PageTable[(State.PC+1)>>8][(State.PC+1)&0xff]) + value = uint16(mmu.ReadPageTable[(State.PC+1)>>8][(State.PC+1)&0xff]) var relativeAddress uint16 if (value & 0x80) == 0 { relativeAddress = State.PC + 2 + uint16(value) @@ -67,12 +67,12 @@ func PrintInstruction(showRegisters bool) { suffix = fmt.Sprintf(stringFormat, relativeAddress) opcodes = fmt.Sprintf("%02x %02x ", opcodeValue, value) } else if size == 1 { - value = uint16(mmu.PageTable[(State.PC+1)>>8][(State.PC+1)&0xff]) + value = uint16(mmu.ReadPageTable[(State.PC+1)>>8][(State.PC+1)&0xff]) suffix = fmt.Sprintf(stringFormat, value) opcodes = fmt.Sprintf("%02x %02x ", opcodeValue, value) } else if size == 2 { - lsb := mmu.PageTable[(State.PC+1)>>8][(State.PC+1)&0xff] - msb := mmu.PageTable[(State.PC+2)>>8][(State.PC+2)&0xff] + lsb := mmu.ReadPageTable[(State.PC+1)>>8][(State.PC+1)&0xff] + msb := mmu.ReadPageTable[(State.PC+2)>>8][(State.PC+2)&0xff] value = uint16(lsb) + uint16(msb)*0x100 suffix = fmt.Sprintf(stringFormat, value) opcodes = fmt.Sprintf("%02x %02x %02x ", opcodeValue, lsb, msb) @@ -82,7 +82,7 @@ func PrintInstruction(showRegisters bool) { } func AdvanceInstruction() { - opcodeValue := mmu.PageTable[(State.PC)>>8][(State.PC)&0xff] + opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff] opcode := OpCodes[opcodeValue] size := opcode.AddressingMode.OperandSize + 1 State.PC += uint16(size) @@ -100,7 +100,7 @@ func DumpMemory(offset uint16) { } fmt.Printf("%04x ", offset+i) } - fmt.Printf(" %02x", mmu.PageTable[(offset+i)>>8][(offset+i)&0xff]) + fmt.Printf(" %02x", mmu.ReadPageTable[(offset+i)>>8][(offset+i)&0xff]) } fmt.Print("\n") } diff --git a/io_test.go b/io_test.go new file mode 100644 index 0000000..12ef408 --- /dev/null +++ b/io_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "mos6502go/cpu" + "mos6502go/keyboard" + "mos6502go/mmu" + "mos6502go/system" + "mos6502go/video" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIoBankSwitching(t *testing.T) { + cpu.InitInstructionDecoder() + mmu.InitRAM() + mmu.InitApple2eROM() + mmu.InitIO() + cpu.Init() + keyboard.Init() + video.Init() + system.Init() + cpu.SetColdStartReset() + cpu.Reset() + + mmu.MapFirstHalfOfIO() + assert.Equal(t, uint8(0xa2), mmu.ReadMemory(0xc600)) // read from Primary Slot 6 ROM + mmu.MapSecondHalfOfIO() + assert.Equal(t, uint8(0x8d), mmu.ReadMemory(0xc600)) // read from Primary Slot 6 ROM +} diff --git a/mmu/io.go b/mmu/io.go index 1f3530c..c64985b 100644 --- a/mmu/io.go +++ b/mmu/io.go @@ -67,6 +67,7 @@ const ( OPNAPPLE = 0xC061 // open apple (command) key data CLSAPPLE = 0xC062 // closed apple (option) key data + STATEREG = 0xC068 // Has no effect on //e PDLTRIG = 0xC070 // trigger paddles @@ -134,6 +135,12 @@ func driveIsreadSequencing() bool { // Handle soft switch addresses where both a read and a write has a side // effect and the return value is meaningless func readWrite(address uint16, isRead bool) bool { + lsb := address & 0xff + if lsb >= 0x80 && lsb < 0x90 { + SetMemoryMode(uint8(lsb - 0x80)) + return true + } + switch address { case CLR80VID: // 80 column card hasn't been implemented yet @@ -162,6 +169,9 @@ func readWrite(address uint16, isRead bool) bool { case SETHIRES: VideoState.HiresMode = true return true + case STATEREG: + // Ignore not implemented memory management reg + return true // Drive stepper motor phase change case S6CLRDRVP0, S6SETDRVP0, S6CLRDRVP1, S6SETDRVP1, S6CLRDRVP2, S6SETDRVP2, S6CLRDRVP3, S6SETDRVP3: diff --git a/mmu/mmu.go b/mmu/mmu.go index 97c1208..605c1f3 100644 --- a/mmu/mmu.go +++ b/mmu/mmu.go @@ -10,30 +10,88 @@ const RomPath = "apple2e.rom" const StackPage = 1 var PhysicalMemory struct { - MainMemory [0xc000]uint8 + MainMemory [0x10000]uint8 UpperROM [0x3000]uint8 RomC1 [0x1000]uint8 RomC2 [0x1000]uint8 } -var PageTable [0x100][]uint8 +var ReadPageTable [0x100][]uint8 +var WritePageTable [0x100][]uint8 -var UsingExternalSlotRom bool +// 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 +) -func MapFirstHalfOfIO() { - UsingExternalSlotRom = false +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++ { - PageTable[i+0xc0] = PhysicalMemory.RomC1[i*0x100 : i*0x100+0x100] + 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] + } + } + } +// Map 0xc100-0xcfff for reading from RomC1 +func MapFirstHalfOfIO() { + UsingExternalSlotRom = false + ApplyMemoryConfiguration() +} + +// Map 0xc100-0xcfff for reading from RomC2 func MapSecondHalfOfIO() { UsingExternalSlotRom = true - - for i := 0x1; i < 0x10; i++ { - PageTable[i+0xc0] = PhysicalMemory.RomC2[i*0x100 : i*0x100+0x100] - } + ApplyMemoryConfiguration() } // emptySlot zeroes all RAM for a slot @@ -44,7 +102,7 @@ func emptySlot(slot int) { } } -func readApple2eROM() { +func loadApple2eROM() { bytes, err := ioutil.ReadFile(RomPath) if err != nil { panic(fmt.Sprintf("Unable to read ROM: %s", err)) @@ -63,33 +121,75 @@ func readApple2eROM() { } func InitApple2eROM() { - readApple2eROM() + loadApple2eROM() + MapFirstHalfOfIO() // Map 0xc100-0xcfff for reading + InitROM() // Map 0xd000-0xffff for reading +} - // Map 0xc100-0xcfff - MapFirstHalfOfIO() +func InitROM() { + UpperReadMappedToROM = true + ApplyMemoryConfiguration() +} - // Map 0xd000-0xffff - for i := 0x0; i < 0x30; i++ { - PageTable[i+0xd0] = PhysicalMemory.UpperROM[i*0x100 : i*0x100+0x100] - } +func SetUpperReadMappedToROM(value bool) { + UpperReadMappedToROM = value + ApplyMemoryConfiguration() +} + +func SetUpperRamReadOnly(value bool) { + UpperRamReadOnly = value + ApplyMemoryConfiguration() +} + +func SetD000Bank(value int) { + D000Bank = value + ApplyMemoryConfiguration() } func InitRAM() { - // Map main RAM - for i := 0x0; i < 0xc0; i++ { - PageTable[i] = PhysicalMemory.MainMemory[i*0x100 : i*0x100+0x100] + UpperRamReadOnly = false + D000Bank = 2 + ApplyMemoryConfiguration() +} + +func WipeRAM() { + for i := 0; i < 0x10000; i++ { + PhysicalMemory.MainMemory[i] = 0 + } +} + +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 } - UsingExternalSlotRom = true + if (((mode & 2) >> 1) ^ (mode & 1)) == 0 { + UpperReadMappedToROM = false - return + } else { + UpperReadMappedToROM = true + } + + if (mode & 8) == 0 { + D000Bank = 2 + } else { + D000Bank = 1 + } + + ApplyMemoryConfiguration() } func ReadMemory(address uint16) uint8 { if (address >= 0xc000) && (address < 0xc100) { return ReadIO(address) } else { - return PageTable[address>>8][address&0xff] + return ReadPageTable[address>>8][address&0xff] } } @@ -102,11 +202,15 @@ func WriteMemory(address uint16, value uint8) { if system.RunningInterruptTests && address == 0xbffc { oldValue := ReadMemory(address) system.WriteInterruptTestOpenCollector(address, oldValue, value) - PageTable[uint8(address>>8)][uint8(address&0xff)] = value + WritePageTable[uint8(address>>8)][uint8(address&0xff)] = value return } - PageTable[uint8(address>>8)][uint8(address&0xff)] = value + 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 system.RunningFunctionalTests && address == 0x200 { testNumber := ReadMemory(0x200) diff --git a/video/video.go b/video/video.go index 3eeafe4..ac24c79 100644 --- a/video/video.go +++ b/video/video.go @@ -122,7 +122,7 @@ func drawTextBlock(screen *ebiten.Image, start int, end int) error { base := 128*(y%8) + 40*(y/8) for x := 0; x < 40; x++ { offset := textVideoMemory + base + x - value := mmu.PageTable[offset>>8][offset&0xff] + value := mmu.ReadPageTable[offset>>8][offset&0xff] if err := drawText(screen, x, y, value); err != nil { return err @@ -138,7 +138,7 @@ func drawLoresBlock(screen *ebiten.Image, start int, end int) error { base := 128*(y%8) + 40*(y/8) for x := 0; x < 40; x++ { offset := textVideoMemory + base + x - value := mmu.PageTable[offset>>8][offset&0xff] + value := mmu.ReadPageTable[offset>>8][offset&0xff] if err := drawLores(screen, x, y, value); err != nil { return err } @@ -186,7 +186,7 @@ func drawHiresScreen(screen *ebiten.Image) error { for x := 0; x < 40; x++ { offset := yOffset + x - value := mmu.PageTable[offset>>8][offset&0xff] + value := mmu.ReadPageTable[offset>>8][offset&0xff] value &= 0x7f for bit := 0; bit < 7; bit++ {