Extract components from the memory manager

This commit is contained in:
Ivan Izaguirre 2019-03-02 18:33:50 +01:00
parent 55ad73d01d
commit 5b186f9b15
12 changed files with 223 additions and 132 deletions

View File

@ -18,15 +18,15 @@ Those tricks do not work with the Apple2e ROM
*/ */
type ansiConsoleFrontend struct { type ansiConsoleFrontend struct {
mmu *memoryManager apple2 *Apple2
keyChannel chan uint8 keyChannel chan uint8
extraLineFeeds chan int extraLineFeeds chan int
textUpdated bool textUpdated bool
} }
func newAnsiConsoleFrontend(mmu *memoryManager) *ansiConsoleFrontend { func newAnsiConsoleFrontend(a *Apple2) *ansiConsoleFrontend {
var fe ansiConsoleFrontend var fe ansiConsoleFrontend
fe.mmu = mmu fe.apple2 = a
fe.subscribeToTextPages() fe.subscribeToTextPages()
return &fe return &fe
} }
@ -36,7 +36,7 @@ func (fe *ansiConsoleFrontend) subscribeToTextPages() {
fe.textUpdated = true fe.textUpdated = true
} }
for i := 0x04; i < 0x08; i++ { for i := 0x04; i < 0x08; i++ {
fe.mmu.physicalMainRAM[i].observer = observer fe.apple2.mmu.physicalMainRAM[i].observer = observer
} }
} }
@ -102,13 +102,13 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine() {
// See "Understand the Apple II", page 5-10 // See "Understand the Apple II", page 5-10
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf // http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
isAltText := fe.mmu.isApple2e && fe.mmu.ioPage.isSoftSwitchExtActive(ioFlagAltChar) isAltText := fe.apple2.isApple2e && fe.apple2.ioPage.isSoftSwitchExtActive(ioFlagAltChar)
var i, j, h, c uint8 var i, j, h, c uint8
// Top, middle and botton screen // Top, middle and botton screen
for i = 0; i < 120; i = i + 40 { for i = 0; i < 120; i = i + 40 {
// Memory pages // Memory pages
for j = 0x04; j < 0x08; j++ { for j = 0x04; j < 0x08; j++ {
p := fe.mmu.physicalMainRAM[j] p := fe.apple2.mmu.physicalMainRAM[j]
// The two half pages // The two half pages
for _, h = range []uint8{0, 128} { for _, h = range []uint8{0, 128} {
line := "" line := ""

View File

@ -1,19 +1,88 @@
package apple2 package apple2
import "go6502/core6502" import (
"bufio"
"go6502/core6502"
"os"
)
// Run instantiates an apple2 and start emulation type Apple2 struct {
func Run(romFile string, log bool) { cpu *core6502.State
mmu := newAddressSpace(romFile) mmu *memoryManager
s := core6502.NewNMOS6502(mmu) isApple2e bool
fe := newAnsiConsoleFrontend(mmu) ioPage *ioC0Page // 0xc000 to 0xc080
mmu.ioPage.setKeyboardProvider(fe) activeSlot int // Slot that has the addressing 0xc800 to 0ccfff
}
// NewApple2 instantiates an apple2
func NewApple2(romFile string) *Apple2 {
var a Apple2
a.mmu = newMemoryManager(&a)
a.cpu = core6502.NewNMOS6502(a.mmu)
a.loadRom(romFile)
a.mmu.resetPaging()
// Set the io in 0xc000
a.ioPage = newIoC0Page(&a)
a.mmu.setPage(0xc0, a.ioPage)
return &a
}
func (a *Apple2) Run(log bool) {
// Init frontend
fe := newAnsiConsoleFrontend(a)
a.ioPage.setKeyboardProvider(fe)
go fe.textModeGoRoutine() go fe.textModeGoRoutine()
// Start the processor // Start the processor
s.Reset() a.cpu.Reset()
for { for {
s.ExecuteInstruction(log) a.cpu.ExecuteInstruction(log)
}
}
// LoadRom loads a binary file to the top of the memory.
const (
apple2RomSize = 12 * 1024
apple2eRomSize = 16 * 1024
)
func (a *Apple2) loadRom(filename string) {
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
stats, statsErr := f.Stat()
if statsErr != nil {
panic(err)
}
size := stats.Size()
if size != apple2RomSize && size != apple2eRomSize {
panic("Rom size not supported")
}
bytes := make([]byte, size)
buf := bufio.NewReader(f)
buf.Read(bytes)
romStart := 0
if size == apple2eRomSize {
// The extra 4kb ROM is first in the rom file.
// It starts with 256 unused bytes not mapped to 0xc000.
a.isApple2e = true
extraRomSize := apple2eRomSize - apple2RomSize
a.mmu.physicalROMe = make([]romPage, extraRomSize>>8)
for i := 0; i < extraRomSize; i++ {
a.mmu.physicalROMe[i>>8].burn(uint8(i), bytes[i])
}
romStart = extraRomSize
}
a.mmu.physicalROM = make([]romPage, apple2RomSize>>8)
for i := 0; i < apple2RomSize; i++ {
a.mmu.physicalROM[i>>8].burn(uint8(i), bytes[i+romStart])
} }
} }

13
apple2/cardDisk2.go Normal file
View File

@ -0,0 +1,13 @@
package apple2
type disk2 struct {
mmu *memoryManager
slot int
}
func insertCardDisk2(mmu *memoryManager, slot int) disk2 {
var c disk2
c.mmu = mmu
c.slot = slot
return c
}

78
apple2/expansionCard.go Normal file
View File

@ -0,0 +1,78 @@
package apple2
import (
"bufio"
"os"
)
type expansionSlots struct {
mmu *memoryManager
cards [7]expansionCard
}
type expansionCard interface {
insert(es *expansionSlots, slot int) cardBase
activateRom()
}
type cardBase struct {
es *expansionSlots
rom []romPage
slot int
softSwitchesR [16]softSwitchR
softSwitchesW [16]softSwitchW
}
/*
https://applesaucefdc.com/woz/reference2/
http://yesterbits.com/media/pubs/AppleOrchard/articles/disk-ii-part-1-1983-apr.pdf
*/
type cardDisk2 struct {
cardBase
}
func newCardDisk2(filename string) *cardDisk2 {
var c cardDisk2
c.rom = loadCardRom(filename)
return &c
}
func (c *cardBase) insert(es *expansionSlots, slot int) {
c.es = es
c.slot = slot
if c.rom != nil {
//rom = c.rom[0]
}
}
func loadCardRom(filename string) []romPage {
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
stats, statsErr := f.Stat()
if statsErr != nil {
panic(err)
}
size := stats.Size()
bytes := make([]byte, size)
buf := bufio.NewReader(f)
buf.Read(bytes)
pages := size / 256
if (size % 256) > 0 {
pages++
}
rom := make([]romPage, pages)
for i := int64(0); i < size; i++ {
rom[i>>8].burn(uint8(i), bytes[i])
}
return rom
}

View File

@ -9,7 +9,7 @@ type ioC0Page struct {
softSwitchesW [128]softSwitchW softSwitchesW [128]softSwitchW
softSwitchesData [128]uint8 softSwitchesData [128]uint8
keyboard keyboardProvider keyboard keyboardProvider
mmu *memoryManager apple2 *Apple2
} }
type softSwitchR func(io *ioC0Page) uint8 type softSwitchR func(io *ioC0Page) uint8
@ -27,12 +27,12 @@ const (
ssOff uint8 = 0x00 ssOff uint8 = 0x00
) )
func newIoC0Page(mmu *memoryManager) *ioC0Page { func newIoC0Page(a *Apple2) *ioC0Page {
var io ioC0Page var io ioC0Page
io.mmu = mmu io.apple2 = a
addApple2SoftSwitches(&io) addApple2SoftSwitches(&io)
if mmu.isApple2e { if a.isApple2e {
addApple2ESoftSwitches(&io) addApple2ESoftSwitches(&io)
} }

View File

@ -1,30 +1,28 @@
package apple2 package apple2
import (
"bufio"
"os"
)
// See https://fabiensanglard.net/fd_proxy/prince_of_persia/Inside%20the%20Apple%20IIe.pdf // See https://fabiensanglard.net/fd_proxy/prince_of_persia/Inside%20the%20Apple%20IIe.pdf
// See https://i.stack.imgur.com/yn21s.gif // See https://i.stack.imgur.com/yn21s.gif
type memoryManager struct { type memoryManager struct {
apple2 *Apple2
// Map of assigned pages // Map of assigned pages
activeMemory *pagedMemory activeMemory [256]memoryPage
// Pages prepared to be paged in and out // Pages prepared to be paged in and out
physicalMainRAM []ramPage // 0x0000 to 0xbfff, Up to 48 Kb physicalMainRAM []ramPage // 0x0000 to 0xbfff, Up to 48 Kb
physicalROM []romPage // 0xd000 to 0xffff, 12 Kb physicalROM []romPage // 0xd000 to 0xffff, 12 Kb
physicalROMe []romPage // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e physicalROMe []romPage // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e
unassignedExpansionROM []unassignedPage // 0xc000 to 0xcfff unassignedExpansionROM []unassignedPage // 0xc000 to 0xcfff
ioPage *ioC0Page // 0xc000 to 0xc080 }
isApple2e bool
activeSlot int // Slot that has the addressing 0xc800 to 0ccfff // memoryPage is a data page of 256 bytes
type memoryPage interface {
Peek(uint8) uint8
Poke(uint8, uint8)
} }
const ( const (
ioAreaMask uint16 = 0xFF80 ioC8Off uint16 = 0xCFFF
ioAreaValue uint16 = 0xC000
ioC8Off uint16 = 0xCFFF
) )
// Peek returns the data on the given address // Peek returns the data on the given address
@ -32,7 +30,10 @@ func (mmu *memoryManager) Peek(address uint16) uint8 {
if address == ioC8Off { if address == ioC8Off {
mmu.resetSlotExpansionRoms() mmu.resetSlotExpansionRoms()
} }
return mmu.activeMemory.Peek(address)
hi := uint8(address >> 8)
lo := uint8(address)
return mmu.activeMemory[hi].Peek(lo)
} }
// Poke sets the data at the given address // Poke sets the data at the given address
@ -40,96 +41,54 @@ func (mmu *memoryManager) Poke(address uint16, value uint8) {
if address == ioC8Off { if address == ioC8Off {
mmu.resetSlotExpansionRoms() mmu.resetSlotExpansionRoms()
} }
mmu.activeMemory.Poke(address, value) hi := uint8(address >> 8)
lo := uint8(address)
mmu.activeMemory[hi].Poke(lo, value)
}
// SetPage assigns a MemoryPage implementation on the page given
func (mmu *memoryManager) setPage(index uint8, page memoryPage) {
//fmt.Printf("Assigning page 0x%02x type %s\n", index, reflect.TypeOf(page))
mmu.activeMemory[index] = page
} }
// When 0xcfff is accessed the card expansion rom is unassigned // When 0xcfff is accessed the card expansion rom is unassigned
func (mmu *memoryManager) resetSlotExpansionRoms() { func (mmu *memoryManager) resetSlotExpansionRoms() {
if mmu.ioPage.isSoftSwitchExtActive(ioFlagIntCxRom) { if mmu.apple2.ioPage.isSoftSwitchExtActive(ioFlagIntCxRom) {
// Ignore if the Apple2 shadow ROM is active // Ignore if the Apple2 shadow ROM is active
return return
} }
for i := 8; i < 16; i++ { for i := 8; i < 16; i++ {
p := mmu.unassignedExpansionROM[i] p := mmu.unassignedExpansionROM[i]
mmu.activeMemory.SetPage(uint8(i+0xc0), &p) mmu.setPage(uint8(i+0xc0), &p)
} }
} }
func newAddressSpace(romImage string) *memoryManager { func newMemoryManager(a *Apple2) *memoryManager {
var mmu memoryManager var mmu memoryManager
mmu.apple2 = a
var m pagedMemory
mmu.activeMemory = &m
// Assign RAM from 0x0000 to 0xbfff, 48kb // Assign RAM from 0x0000 to 0xbfff, 48kb
mmu.physicalMainRAM = make([]ramPage, 0xc0) mmu.physicalMainRAM = make([]ramPage, 0xc0)
for i := 0; i <= 0xbf; i++ { for i := 0; i <= 0xbf; i++ {
m.SetPage(uint8(i), &(mmu.physicalMainRAM[i])) mmu.setPage(uint8(i), &(mmu.physicalMainRAM[i]))
} }
mmu.loadRom(romImage)
// Assign the first 12kb of ROM from 0xd000 to 0xfff
for i := 0xd0; i <= 0xff; i++ {
m.SetPage(uint8(i), &(mmu.physicalROM[i-0xd0]))
}
// Set the io in 0xc000
mmu.ioPage = newIoC0Page(&mmu)
m.SetPage(0xc0, mmu.ioPage)
// Set the 0xc100 to 0xcfff as unasigned, 4kb. It wil be taken by slot cards. // Set the 0xc100 to 0xcfff as unasigned, 4kb. It wil be taken by slot cards.
mmu.unassignedExpansionROM = make([]unassignedPage, 0x10) mmu.unassignedExpansionROM = make([]unassignedPage, 0x10)
for i := 1; i < 0x10; i++ { for i := 1; i < 0x10; i++ {
page := uint8(i + 0xc0) page := uint8(i + 0xc0)
p := &mmu.unassignedExpansionROM[i] p := &mmu.unassignedExpansionROM[i]
p.page = page p.page = page
m.SetPage(page, p) mmu.setPage(page, p)
} }
return &mmu return &mmu
} }
// LoadRom loads a binary file to the top of the memory. func (mmu *memoryManager) resetPaging() {
const ( // Assign the first 12kb of ROM from 0xd000 to 0xfff
apple2RomSize = 12 * 1024 for i := 0xd0; i <= 0xff; i++ {
apple2eRomSize = 16 * 1024 mmu.setPage(uint8(i), &(mmu.physicalROM[i-0xd0]))
)
func (mmu *memoryManager) loadRom(filename string) {
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
stats, statsErr := f.Stat()
if statsErr != nil {
panic(err)
}
size := stats.Size()
if size != apple2RomSize && size != apple2eRomSize {
panic("Rom size not supported")
}
bytes := make([]byte, size)
buf := bufio.NewReader(f)
buf.Read(bytes)
romStart := 0
if size == apple2eRomSize {
// The extra 4kb ROM is first in the rom file.
// It starts with 256 unused bytes not mapped to 0xc000.
mmu.isApple2e = true
extraRomSize := apple2eRomSize - apple2RomSize
mmu.physicalROMe = make([]romPage, extraRomSize>>8)
for i := 0; i < extraRomSize; i++ {
mmu.physicalROMe[i>>8].burn(uint8(i), bytes[i])
}
romStart = extraRomSize
}
mmu.physicalROM = make([]romPage, apple2RomSize>>8)
for i := 0; i < apple2RomSize; i++ {
mmu.physicalROM[i>>8].burn(uint8(i), bytes[i+romStart])
} }
} }

View File

@ -1,32 +0,0 @@
package apple2
// memoryPage is a data page of 256 bytes
type memoryPage interface {
Peek(uint8) uint8
Poke(uint8, uint8)
}
// pagedMemory represents the addressable space of the processor
type pagedMemory struct {
data [256]memoryPage
}
// Peek returns the data on the given address
func (m *pagedMemory) Peek(address uint16) uint8 {
hi := uint8(address >> 8)
lo := uint8(address)
return m.data[hi].Peek(lo)
}
// Poke sets the data at the given address
func (m *pagedMemory) Poke(address uint16, value uint8) {
hi := uint8(address >> 8)
lo := uint8(address)
m.data[hi].Poke(lo, value)
}
// SetPage assigns a MemoryPage implementation on the page given
func (m *pagedMemory) SetPage(index uint8, page memoryPage) {
//fmt.Printf("Assigning page 0x%02x type %s\n", index, reflect.TypeOf(page))
m.data[index] = page
}

BIN
apple2/romdumps/DISK2.rom Normal file

Binary file not shown.

View File

@ -45,15 +45,17 @@ func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction)
} }
func softSwitchIntCxRomOn(io *ioC0Page) { func softSwitchIntCxRomOn(io *ioC0Page) {
mmu := io.apple2.mmu
for i := uint8(1); i < 16; i++ { for i := uint8(1); i < 16; i++ {
io.mmu.activeMemory.SetPage(uint8(0xc0+i), &io.mmu.physicalROMe[i]) mmu.setPage(uint8(0xc0+i), &mmu.physicalROMe[i])
} }
} }
func softSwitchIntCxRomOff(io *ioC0Page) { func softSwitchIntCxRomOff(io *ioC0Page) {
// TODO restore all the ROM from the slot for 0xc1 to 0xc7 // TODO restore all the ROM from the slot for 0xc1 to 0xc7
mmu := io.apple2.mmu
for i := 1; i < 16; i++ { for i := 1; i < 16; i++ {
io.mmu.activeMemory.SetPage(uint8(0xc0+i), &io.mmu.unassignedExpansionROM[i]) mmu.setPage(uint8(0xc0+i), &mmu.unassignedExpansionROM[i])
} }
} }
@ -62,12 +64,14 @@ func softSwitchSlotC3RomOn(io *ioC0Page) {
return // Ignore if allt the Apple2 shadow ROM is active return // Ignore if allt the Apple2 shadow ROM is active
} }
// TODO restore the slot 3 ROM // TODO restore the slot 3 ROM
io.mmu.activeMemory.SetPage(0xC3, &io.mmu.unassignedExpansionROM[3]) mmu := io.apple2.mmu
mmu.setPage(0xC3, &mmu.unassignedExpansionROM[3])
} }
func softSwitchSlotC3RomOff(io *ioC0Page) { func softSwitchSlotC3RomOff(io *ioC0Page) {
if io.isSoftSwitchExtActive(ioFlagIntCxRom) { if io.isSoftSwitchExtActive(ioFlagIntCxRom) {
return // Ignore if allt the Apple2 shadow ROM is active return // Ignore if allt the Apple2 shadow ROM is active
} }
io.mmu.activeMemory.SetPage(0xC3, &io.mmu.physicalROMe[3]) mmu := io.apple2.mmu
mmu.setPage(0xC3, &mmu.physicalROMe[3])
} }

View File

@ -29,5 +29,4 @@ func TestFunctional(t *testing.T) {
} }
} }
t.Errorf("Tests complited in %v megacycles.\n", s.cycles/1000000)
} }

View File

@ -8,5 +8,6 @@ func main() {
//romFile := "apple2/romdumps/Apple2e.rom" //romFile := "apple2/romdumps/Apple2e.rom"
log := false log := false
apple2.Run(romFile, log) a := apple2.NewApple2(romFile)
a.Run(log)
} }