Added bank switched upper memory

This commit is contained in:
Will Angenent 2018-05-20 11:02:08 +01:00
parent a43fd7c08c
commit f1dff51564
8 changed files with 302 additions and 45 deletions

109
bank_switch_test.go Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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()

View File

@ -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")
}

30
io_test.go Normal file
View File

@ -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
}

View File

@ -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:

View File

@ -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)

View File

@ -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++ {