diff --git a/apple2/ansiConsoleFrontend.go b/apple2/ansiConsoleFrontend.go index 5373f25..b37817c 100644 --- a/apple2/ansiConsoleFrontend.go +++ b/apple2/ansiConsoleFrontend.go @@ -18,15 +18,15 @@ Those tricks do not work with the Apple2e ROM */ type ansiConsoleFrontend struct { - mmu *memoryManager + apple2 *Apple2 keyChannel chan uint8 extraLineFeeds chan int textUpdated bool } -func newAnsiConsoleFrontend(mmu *memoryManager) *ansiConsoleFrontend { +func newAnsiConsoleFrontend(a *Apple2) *ansiConsoleFrontend { var fe ansiConsoleFrontend - fe.mmu = mmu + fe.apple2 = a fe.subscribeToTextPages() return &fe } @@ -36,7 +36,7 @@ func (fe *ansiConsoleFrontend) subscribeToTextPages() { fe.textUpdated = true } 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 // 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 // Top, middle and botton screen for i = 0; i < 120; i = i + 40 { // Memory pages for j = 0x04; j < 0x08; j++ { - p := fe.mmu.physicalMainRAM[j] + p := fe.apple2.mmu.physicalMainRAM[j] // The two half pages for _, h = range []uint8{0, 128} { line := "" diff --git a/apple2/apple2.go b/apple2/apple2.go index 932e44b..21702ed 100644 --- a/apple2/apple2.go +++ b/apple2/apple2.go @@ -1,19 +1,88 @@ package apple2 -import "go6502/core6502" +import ( + "bufio" + "go6502/core6502" + "os" +) -// Run instantiates an apple2 and start emulation -func Run(romFile string, log bool) { - mmu := newAddressSpace(romFile) - s := core6502.NewNMOS6502(mmu) - fe := newAnsiConsoleFrontend(mmu) - mmu.ioPage.setKeyboardProvider(fe) +type Apple2 struct { + cpu *core6502.State + mmu *memoryManager + isApple2e bool + ioPage *ioC0Page // 0xc000 to 0xc080 + 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() // Start the processor - s.Reset() + a.cpu.Reset() 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]) } } diff --git a/apple2/cardDisk2.go b/apple2/cardDisk2.go new file mode 100644 index 0000000..47efa54 --- /dev/null +++ b/apple2/cardDisk2.go @@ -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 +} diff --git a/apple2/expansionCard.go b/apple2/expansionCard.go new file mode 100644 index 0000000..b070629 --- /dev/null +++ b/apple2/expansionCard.go @@ -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 +} diff --git a/apple2/ioC0Page.go b/apple2/ioC0Page.go index be19f2b..cc25f34 100644 --- a/apple2/ioC0Page.go +++ b/apple2/ioC0Page.go @@ -9,7 +9,7 @@ type ioC0Page struct { softSwitchesW [128]softSwitchW softSwitchesData [128]uint8 keyboard keyboardProvider - mmu *memoryManager + apple2 *Apple2 } type softSwitchR func(io *ioC0Page) uint8 @@ -27,12 +27,12 @@ const ( ssOff uint8 = 0x00 ) -func newIoC0Page(mmu *memoryManager) *ioC0Page { +func newIoC0Page(a *Apple2) *ioC0Page { var io ioC0Page - io.mmu = mmu + io.apple2 = a addApple2SoftSwitches(&io) - if mmu.isApple2e { + if a.isApple2e { addApple2ESoftSwitches(&io) } diff --git a/apple2/memoryManager.go b/apple2/memoryManager.go index a587f39..f0645b6 100644 --- a/apple2/memoryManager.go +++ b/apple2/memoryManager.go @@ -1,30 +1,28 @@ package apple2 -import ( - "bufio" - "os" -) - // See https://fabiensanglard.net/fd_proxy/prince_of_persia/Inside%20the%20Apple%20IIe.pdf // See https://i.stack.imgur.com/yn21s.gif type memoryManager struct { + apple2 *Apple2 // Map of assigned pages - activeMemory *pagedMemory + activeMemory [256]memoryPage + // Pages prepared to be paged in and out physicalMainRAM []ramPage // 0x0000 to 0xbfff, Up to 48 Kb physicalROM []romPage // 0xd000 to 0xffff, 12 Kb physicalROMe []romPage // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e 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 ( - ioAreaMask uint16 = 0xFF80 - ioAreaValue uint16 = 0xC000 - ioC8Off uint16 = 0xCFFF + ioC8Off uint16 = 0xCFFF ) // Peek returns the data on the given address @@ -32,7 +30,10 @@ func (mmu *memoryManager) Peek(address uint16) uint8 { if address == ioC8Off { 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 @@ -40,96 +41,54 @@ func (mmu *memoryManager) Poke(address uint16, value uint8) { if address == ioC8Off { 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 func (mmu *memoryManager) resetSlotExpansionRoms() { - if mmu.ioPage.isSoftSwitchExtActive(ioFlagIntCxRom) { + if mmu.apple2.ioPage.isSoftSwitchExtActive(ioFlagIntCxRom) { // Ignore if the Apple2 shadow ROM is active return } for i := 8; i < 16; 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 m pagedMemory - mmu.activeMemory = &m + mmu.apple2 = a // Assign RAM from 0x0000 to 0xbfff, 48kb mmu.physicalMainRAM = make([]ramPage, 0xc0) 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. mmu.unassignedExpansionROM = make([]unassignedPage, 0x10) for i := 1; i < 0x10; i++ { page := uint8(i + 0xc0) p := &mmu.unassignedExpansionROM[i] p.page = page - m.SetPage(page, p) + mmu.setPage(page, p) } - return &mmu } -// LoadRom loads a binary file to the top of the memory. -const ( - apple2RomSize = 12 * 1024 - apple2eRomSize = 16 * 1024 -) - -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]) +func (mmu *memoryManager) resetPaging() { + // Assign the first 12kb of ROM from 0xd000 to 0xfff + for i := 0xd0; i <= 0xff; i++ { + mmu.setPage(uint8(i), &(mmu.physicalROM[i-0xd0])) } } diff --git a/apple2/pagedMemory.go b/apple2/pagedMemory.go deleted file mode 100644 index 5b87160..0000000 --- a/apple2/pagedMemory.go +++ /dev/null @@ -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 -} diff --git a/apple2/romdumps/DISK2.rom b/apple2/romdumps/DISK2.rom new file mode 100644 index 0000000..56698b0 Binary files /dev/null and b/apple2/romdumps/DISK2.rom differ diff --git a/apple2/softSwitches2e.go b/apple2/softSwitches2e.go index 3a6d25d..1e0c7ce 100644 --- a/apple2/softSwitches2e.go +++ b/apple2/softSwitches2e.go @@ -45,15 +45,17 @@ func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) } func softSwitchIntCxRomOn(io *ioC0Page) { + mmu := io.apple2.mmu 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) { // TODO restore all the ROM from the slot for 0xc1 to 0xc7 + mmu := io.apple2.mmu 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 } // 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) { if io.isSoftSwitchExtActive(ioFlagIntCxRom) { 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]) } diff --git a/core6502/6502functional_test.go b/core6502/6502functional_test.go index 5584cf7..098b1ec 100644 --- a/core6502/6502functional_test.go +++ b/core6502/6502functional_test.go @@ -29,5 +29,4 @@ func TestFunctional(t *testing.T) { } } - t.Errorf("Tests complited in %v megacycles.\n", s.cycles/1000000) } diff --git a/core6502/opcodesNMOS6502.go b/core6502/nmos6502.go similarity index 100% rename from core6502/opcodesNMOS6502.go rename to core6502/nmos6502.go diff --git a/main.go b/main.go index 9bc1d61..8d57540 100644 --- a/main.go +++ b/main.go @@ -8,5 +8,6 @@ func main() { //romFile := "apple2/romdumps/Apple2e.rom" log := false - apple2.Run(romFile, log) + a := apple2.NewApple2(romFile) + a.Run(log) }