Rework of the memory manager. No longer based on pages, but on bigger go byte ranges

This commit is contained in:
Ivan Izaguirre 2019-05-16 22:51:04 +02:00
parent 51c7a8cb57
commit c4f7a7e709
13 changed files with 155 additions and 312 deletions

View File

@ -21,27 +21,17 @@ type ansiConsoleFrontend struct {
apple2 *Apple2
keyChannel chan uint8
extraLineFeeds chan int
textUpdated bool
stdinKeyboard bool
lastContent string
}
func newAnsiConsoleFrontend(a *Apple2, stdinKeyboard bool) *ansiConsoleFrontend {
var fe ansiConsoleFrontend
fe.apple2 = a
fe.stdinKeyboard = stdinKeyboard
fe.subscribeToTextPages()
return &fe
}
func (fe *ansiConsoleFrontend) subscribeToTextPages() {
observer := func(_ uint8, _ bool) {
fe.textUpdated = true
}
for i := 0x04; i < 0x08; i++ {
fe.apple2.mmu.physicalMainRAM[i].observer = observer
}
}
const refreshDelayMs = 100
func (fe *ansiConsoleFrontend) GetKey(strobed bool) (key uint8, ok bool) {
@ -85,101 +75,53 @@ func (fe *ansiConsoleFrontend) GetKey(strobed bool) (key uint8, ok bool) {
return
}
func ansiCursorUp(steps int) {
fmt.Printf("\033[%vA", steps)
}
func (fe *ansiConsoleFrontend) textModeGoRoutineFast() {
fe.extraLineFeeds = make(chan int, 100)
fmt.Printf(strings.Repeat("\n", 26))
for {
if fe.textUpdated {
fe.textUpdated = false
// Go up
ansiCursorUp(26)
done := false
for !done {
select {
case lineFeeds := <-fe.extraLineFeeds:
ansiCursorUp(lineFeeds)
default:
done = true
}
}
fmt.Println(strings.Repeat("#", 44))
// See "Understand the Apple II", page 5-10
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchActive(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.apple2.mmu.physicalMainRAM[j]
// The two half pages
for _, h = range []uint8{0, 128} {
line := ""
for c = i + h; c < i+h+40; c++ {
line += textMemoryByteToString(p.internalPeek(c), isAltText)
}
fmt.Printf("# %v #\n", line)
}
}
}
fmt.Println(strings.Repeat("#", 44))
if fe.stdinKeyboard {
fmt.Print("\033[KLine: ")
}
}
time.Sleep(refreshDelayMs * time.Millisecond)
}
func ansiCursorUp(steps int) string {
return fmt.Sprintf("\033[%vA", steps)
}
func (fe *ansiConsoleFrontend) textModeGoRoutine() {
fe.extraLineFeeds = make(chan int, 100)
fmt.Printf(strings.Repeat("\n", 26))
fmt.Printf(strings.Repeat("\n", textLines+3))
for {
if fe.textUpdated {
fe.textUpdated = false
// Go up
ansiCursorUp(26)
done := false
for !done {
select {
case lineFeeds := <-fe.extraLineFeeds:
ansiCursorUp(lineFeeds)
default:
done = true
}
// Go up
content := ansiCursorUp(textLines + 3)
done := false
for !done {
select {
case lineFeeds := <-fe.extraLineFeeds:
content += ansiCursorUp(lineFeeds)
default:
done = true
}
}
pageIndex := 0
if fe.apple2.io.isSoftSwitchActive(ioFlagSecondPage) {
pageIndex = 1
content += "\n"
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
pageIndex := 0
if fe.apple2.io.isSoftSwitchActive(ioFlagSecondPage) {
pageIndex = 1
}
isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchActive(ioFlagAltChar)
for l := 0; l < textLines; l++ {
line := ""
for c := 0; c < textColumns; c++ {
char := getTextChar(fe.apple2, c, l, pageIndex)
line += textMemoryByteToString(char, isAltText)
}
isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchActive(ioFlagAltChar)
content += fmt.Sprintf("# %v #\n", line)
}
fmt.Println(strings.Repeat("#", 44))
for line := 0; line < 24; line++ {
text := ""
for col := 0; col < 40; col++ {
value := getTextChar(fe.apple2, col, line, pageIndex)
text += textMemoryByteToString(value, isAltText)
}
fmt.Printf("# %v #\n", text)
}
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
if fe.stdinKeyboard {
content += "\033[KLine: "
}
fmt.Println(strings.Repeat("#", 44))
if fe.stdinKeyboard {
fmt.Print("\033[KLine: ")
}
//saveSnapshot(fe.apple2)
if content != fe.lastContent {
fmt.Print(content)
fe.lastContent = content
}
time.Sleep(refreshDelayMs * time.Millisecond)
}

View File

@ -3,7 +3,6 @@ package apple2
import (
"fmt"
"go6502/core6502"
"io/ioutil"
"time"
)
@ -36,11 +35,10 @@ func NewApple2(romFile string, charRomFile string, clockMhz float64,
var a Apple2
a.mmu = newMemoryManager(&a)
a.cpu = core6502.NewNMOS6502(a.mmu)
a.loadRom(romFile)
a.mmu.loadRom(romFile)
if charRomFile != "" {
a.cg = NewCharacterGenerator(charRomFile)
}
a.mmu.resetRomPaging()
a.commandChannel = make(chan int, 100)
a.isColor = isColor
a.fastMode = fastMode
@ -182,32 +180,3 @@ func (a *Apple2) releaseFastMode() {
a.fastRequestsCounter--
}
}
func (a *Apple2) loadRom(filename string) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
size := len(bytes)
if size != apple2RomSize && size != apple2eRomSize {
panic("Rom size not supported")
}
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])
}
}

View File

@ -2,7 +2,7 @@ package apple2
type cardBase struct {
a *Apple2
rom []memoryPage
rom *memoryRange
slot int
ssr [16]softSwitchR
ssw [16]softSwitchW
@ -11,8 +11,9 @@ type cardBase struct {
func (c *cardBase) insert(a *Apple2, slot int) {
c.a = a
c.slot = slot
if slot != 0 && c.rom[0] != nil {
a.mmu.setPage(uint8(0xC0+slot), c.rom[0])
if slot != 0 && c.rom != nil {
c.rom.base = uint16(0xC000 + slot*0x100)
a.mmu.setPage(uint8(0xC0+slot), c.rom)
}
for i := 0; i < 0x10; i++ {

View File

@ -30,7 +30,7 @@ type cardDisk2Drive struct {
func newCardDisk2(filename string) *cardDisk2 {
var c cardDisk2
c.rom = loadCardRom(filename)
c.rom = newMemoryRange(0, loadCardRom(filename))
// Phase control soft switches
// Lazy emulation. It only checks for phases on and move the head
@ -137,29 +137,12 @@ func newCardDisk2(filename string) *cardDisk2 {
return &c
}
func loadCardRom(filename string) []memoryPage {
bytes, err := ioutil.ReadFile(filename)
func loadCardRom(filename string) []uint8 {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
size := len(bytes)
pages := size / 256
if (size % 256) > 0 {
pages++
}
rom := make([]romPage, pages)
for i := 0; i < size; i++ {
rom[i>>8].burn(uint8(i), bytes[i])
}
memPages := make([]memoryPage, pages)
for i := range rom {
memPages[i] = &rom[i]
}
return memPages
return data
}
func (d *cardDisk2Drive) insertDiskette(dt *diskette16sector) {

View File

@ -1,6 +1,7 @@
package apple2
import (
"io/ioutil"
"os"
)
@ -33,41 +34,25 @@ func (d *diskette16sector) read(track int, position int) (value uint8, newPositi
func loadDisquette(filename string) *diskette16sector {
var d diskette16sector
f, err := os.Open(filename)
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
defer f.Close()
size := len(data)
stats, statsErr := f.Stat()
if statsErr != nil {
panic(err)
}
size := stats.Size()
if size == nibImageSize {
// Load file already in nib format
for i := 0; i < numberOfTracks; i++ {
d.track[i] = make([]byte, nibBytesPerTrack)
n, err := f.Read(d.track[i])
if err != nil || n != nibBytesPerTrack {
panic("Error loading file")
}
d.track[i] = data[nibBytesPerTrack*i : nibBytesPerTrack*(i+1)]
}
} else if size == dskImageSize {
// Load file in dsk format
data := make([]byte, dskImageSize)
n, err := f.Read(data)
if err != nil || n != dskImageSize {
panic("Error loading file")
}
// Convert to nib
for i := 0; i < numberOfTracks; i++ {
trackData := data[i*bytesPerTrack : (i+1)*bytesPerTrack]
d.track[i] = nibEncodeTrack(trackData, defaultVolumeTag, byte(i))
}
} else {
panic("Disk size with nib format has to be 232960 bytes")
panic("Invalid disk size")
}
return &d

View File

@ -80,32 +80,26 @@ func (p *ioC0Page) setSpeakerProvider(s SpeakerProvider) {
p.speaker = s
}
func (p *ioC0Page) Peek(address uint8) uint8 {
func (p *ioC0Page) peek(address uint16) uint8 {
//fmt.Printf("Peek on $C0%02x ", address)
ss := p.softSwitchesR[address]
pageAddress := uint8(address)
ss := p.softSwitchesR[pageAddress]
if ss == nil {
if p.apple2.panicSS {
panic(fmt.Sprintf("Unknown softswitch on read to 0xC0%02x", address))
panic(fmt.Sprintf("Unknown softswitch on read to 0xC0%02x", pageAddress))
}
return 0
}
return ss(p)
}
func (p *ioC0Page) internalPeek(address uint8) uint8 {
return 0
}
func (p *ioC0Page) all() []uint8 {
return nil
}
func (p *ioC0Page) Poke(address uint8, value uint8) {
func (p *ioC0Page) poke(address uint16, value uint8) {
//fmt.Printf("Poke on $C0%02x with %02x ", address, value)
ss := p.softSwitchesW[address]
pageAddress := uint8(address)
ss := p.softSwitchesW[pageAddress]
if ss == nil {
if p.apple2.panicSS {
panic(fmt.Sprintf("Unknown softswitch on write to 0xC0%02x", address))
panic(fmt.Sprintf("Unknown softswitch on write to 0xC0%02x", pageAddress))
}
return
}

View File

@ -1,26 +1,25 @@
package apple2
import "io/ioutil"
// 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 [256]memoryPage
activeMemoryRead [256]memoryHandler
activeMemoryWrite [256]memoryHandler
// 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
physicalMainRAM *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb
physicalROM *memoryRange // 0xd000 to 0xffff, 12 Kb
physicalROMe *memoryRange // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e
}
// memoryPage is a data page of 256 bytes
type memoryPage interface {
Peek(uint8) uint8
Poke(uint8, uint8)
internalPeek(uint8) uint8
all() []uint8
type memoryHandler interface {
peek(uint16) uint8
poke(uint16, uint8)
}
const (
@ -34,18 +33,11 @@ func (mmu *memoryManager) Peek(address uint16) uint8 {
}
hi := uint8(address >> 8)
lo := uint8(address)
return mmu.activeMemory[hi].Peek(lo)
}
func (mmu *memoryManager) internalPeek(address uint16) uint8 {
hi := uint8(address >> 8)
lo := uint8(address)
return mmu.activeMemory[hi].internalPeek(lo)
}
func (mmu *memoryManager) internalPage(hi uint8) []uint8 {
return mmu.activeMemory[hi].all()
mh := mmu.activeMemoryRead[hi]
if mh == nil {
return 0xf4 // Or some random number
}
return mh.peek(address)
}
// Poke sets the data at the given address
@ -54,15 +46,24 @@ func (mmu *memoryManager) Poke(address uint16, value uint8) {
mmu.resetSlotExpansionRoms()
}
hi := uint8(address >> 8)
lo := uint8(address)
mmu.activeMemory[hi].Poke(lo, value)
mh := mmu.activeMemoryWrite[hi]
if mh == nil {
return
}
mh.poke(address, 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
func (mmu *memoryManager) setPage(index uint8, mh memoryHandler) {
mmu.setPageRead(index, mh)
mmu.setPageWrite(index, mh)
}
func (mmu *memoryManager) setPageRead(index uint8, mh memoryHandler) {
mmu.activeMemoryRead[index] = mh
}
func (mmu *memoryManager) setPageWrite(index uint8, mh memoryHandler) {
mmu.activeMemoryWrite[index] = mh
}
// When 0xcfff is accessed the card expansion rom is unassigned
@ -71,9 +72,8 @@ func (mmu *memoryManager) resetSlotExpansionRoms() {
// Ignore if the Apple2 shadow ROM is active
return
}
for i := 8; i < 16; i++ {
p := mmu.unassignedExpansionROM[i]
mmu.setPage(uint8(i+0xc0), &p)
for i := uint8(0xc8); i < 0xd0; i++ {
mmu.setPage(i, nil)
}
}
@ -82,25 +82,43 @@ func newMemoryManager(a *Apple2) *memoryManager {
mmu.apple2 = a
// Assign RAM from 0x0000 to 0xbfff, 48kb
mmu.physicalMainRAM = make([]ramPage, 0xc0)
for i := 0; i <= 0xbf; i++ {
mmu.setPage(uint8(i), &(mmu.physicalMainRAM[i]))
ram := make([]uint8, 0xc000)
mmu.physicalMainRAM = newMemoryRange(0, ram)
for i := 0; i < 0xc000; i = i + 0x100 {
mmu.setPage(uint8(i>>8), mmu.physicalMainRAM)
}
// 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
mmu.setPage(page, p)
}
return &mmu
}
func (mmu *memoryManager) loadRom(filename string) {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
size := len(data)
if size != apple2RomSize && size != apple2eRomSize {
panic("Rom size not supported")
}
a := mmu.apple2
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 = newMemoryRange(0xc000, data[0:extraRomSize])
romStart = extraRomSize
}
a.mmu.physicalROM = newMemoryRange(0xd000, data[romStart:])
mmu.resetRomPaging()
}
func (mmu *memoryManager) resetRomPaging() {
// Assign the first 12kb of ROM from 0xd000 to 0xfff
for i := 0xd0; i <= 0xff; i++ {
mmu.setPage(uint8(i), &(mmu.physicalROM[i-0xd0]))
for i := 0x0000; i < 0x3000; i = i + 0x100 {
mmu.setPageRead(uint8(0xd0+(i>>8)), mmu.physicalROM)
}
}

25
apple2/memoryRange.go Normal file
View File

@ -0,0 +1,25 @@
package apple2
type memoryRange struct {
base uint16
data []uint8
}
func newMemoryRange(base uint16, data []uint8) *memoryRange {
var m memoryRange
m.base = base
m.data = data
return &m
}
func (m *memoryRange) peek(address uint16) uint8 {
return m.data[address-m.base]
}
func (m *memoryRange) poke(address uint16, value uint8) {
m.data[address-m.base] = value
}
func (m *memoryRange) subRange(a, b uint16) []uint8 {
return m.data[a-m.base : b-m.base]
}

View File

@ -1,46 +0,0 @@
package apple2
type rxmPage struct {
data [256]uint8
observer func(address uint8, isWrite bool)
}
type ramPage struct {
rxmPage
}
type romPage struct {
rxmPage
}
func (p *rxmPage) Peek(address uint8) uint8 {
p.touch(address, false)
return p.data[address]
}
func (p *rxmPage) internalPeek(address uint8) uint8 {
return p.data[address]
}
func (p *rxmPage) all() []uint8 {
return p.data[:]
}
func (p *rxmPage) Poke(address uint8, value uint8) {
p.touch(address, true)
p.data[address] = value
}
func (p *rxmPage) touch(address uint8, isWrite bool) {
if p.observer != nil {
p.observer(address, isWrite)
}
}
func (p *romPage) Poke(address uint8, value uint8) {
// Do nothing
}
func (p *romPage) burn(address uint8, value uint8) {
p.data[address] = value
}

View File

@ -30,11 +30,7 @@ func getHiResLine(a *Apple2, line int, page int) []uint8 {
}
address += getHiResLineOffset(line)
hi := uint8(address >> 8)
lo := uint8(address)
memPage := a.mmu.internalPage(hi)
return memPage[lo : lo+40]
return a.mmu.physicalMainRAM.subRange(address, address+40)
}
func snapshotHiResModeMonoShift(a *Apple2, page int, mixedMode bool, light color.Color) *image.RGBA {

View File

@ -31,7 +31,7 @@ func getTextChar(a *Apple2, col int, line int, page int) uint8 {
address = textPage2Address
}
address += getTextCharOffset(col, line)
return a.mmu.internalPeek(address)
return a.mmu.physicalMainRAM.subRange(address, address+1)[0]
}
func snapshotTextMode(a *Apple2, page int, mixMode bool, light color.Color) *image.RGBA {

View File

@ -46,8 +46,8 @@ func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction)
func softSwitchIntCxRomOn(io *ioC0Page) {
mmu := io.apple2.mmu
for i := uint8(1); i < 16; i++ {
mmu.setPage(uint8(0xc0+i), &mmu.physicalROMe[i])
for i := 0x100; i < 0x1000; i = i + 0x100 {
mmu.setPage(uint8(i>>8), mmu.physicalROMe)
}
}
@ -55,7 +55,7 @@ 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++ {
mmu.setPage(uint8(0xc0+i), &mmu.unassignedExpansionROM[i])
mmu.setPage(uint8(0xc0+i), nil)
}
}
@ -65,13 +65,13 @@ func softSwitchSlotC3RomOn(io *ioC0Page) {
}
// TODO restore the slot 3 ROM
mmu := io.apple2.mmu
mmu.setPage(0xC3, &mmu.unassignedExpansionROM[3])
mmu.setPage(0xC3, nil)
}
func softSwitchSlotC3RomOff(io *ioC0Page) {
if io.isSoftSwitchActive(ioFlagIntCxRom) {
return // Ignore if allt the Apple2 shadow ROM is active
return // Ignore if alt the Apple2 shadow ROM is active
}
mmu := io.apple2.mmu
mmu.setPage(0xC3, &mmu.physicalROMe[3])
mmu.setPageRead(0xC3, mmu.physicalROMe)
}

View File

@ -1,24 +0,0 @@
package apple2
type unassignedPage struct {
page uint8
}
func (p *unassignedPage) Peek(address uint8) uint8 {
//fmt.Printf("Read on address 0x%02x%02x\n", p.page, address)
//panic(address)
return 0xdd
}
func (p *unassignedPage) internalPeek(address uint8) uint8 {
return 0xdd
}
func (p *unassignedPage) all() []uint8 {
return nil
}
func (p *unassignedPage) Poke(address uint8, value uint8) {
//fmt.Printf("Write on address 0x%02x%02x\n", p.page, address)
//panic(address)
}