mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-01-02 04:31:49 +00:00
Support for state snapshots. F7 to save, F8 to load
This commit is contained in:
parent
cccb0583db
commit
cb21a1fefc
154
apple2/apple2.go
154
apple2/apple2.go
@ -12,15 +12,14 @@ type Apple2 struct {
|
|||||||
mmu *memoryManager
|
mmu *memoryManager
|
||||||
io *ioC0Page
|
io *ioC0Page
|
||||||
cg *CharacterGenerator
|
cg *CharacterGenerator
|
||||||
cards []cardBase
|
|
||||||
isApple2e bool
|
isApple2e bool
|
||||||
panicSS bool
|
panicSS bool
|
||||||
activeSlot int // Slot that has the addressing 0xc800 to 0ccfff
|
|
||||||
commandChannel chan int
|
commandChannel chan int
|
||||||
cycleDurationNs float64 // Inverse of the cpu clock in Ghz
|
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
|
||||||
isColor bool
|
isColor bool
|
||||||
fastMode bool
|
fastMode bool
|
||||||
fastRequestsCounter int
|
fastRequestsCounter int
|
||||||
|
persistance *persistance
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,114 +28,6 @@ const (
|
|||||||
cpuClockEuroMhz = 14.238 / 14
|
cpuClockEuroMhz = 14.238 / 14
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewApple2 instantiates an apple2
|
|
||||||
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
|
||||||
isColor bool, fastMode bool, panicSS bool) *Apple2 {
|
|
||||||
var a Apple2
|
|
||||||
a.mmu = newMemoryManager(&a)
|
|
||||||
a.cpu = core6502.NewNMOS6502(a.mmu)
|
|
||||||
a.mmu.loadRom(romFile)
|
|
||||||
if charRomFile != "" {
|
|
||||||
a.cg = NewCharacterGenerator(charRomFile)
|
|
||||||
}
|
|
||||||
a.commandChannel = make(chan int, 100)
|
|
||||||
a.isColor = isColor
|
|
||||||
a.fastMode = fastMode
|
|
||||||
a.panicSS = panicSS
|
|
||||||
|
|
||||||
if clockMhz <= 0 {
|
|
||||||
// Full speed
|
|
||||||
a.cycleDurationNs = 0
|
|
||||||
} else {
|
|
||||||
a.cycleDurationNs = 1000.0 / clockMhz
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the io in 0xc000
|
|
||||||
a.io = newIoC0Page(&a)
|
|
||||||
a.mmu.setPages(0xc0, 0xc0, a.io)
|
|
||||||
|
|
||||||
return &a
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDisk2 insterts a DiskII controller
|
|
||||||
func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage string) {
|
|
||||||
d := newCardDisk2(diskRomFile)
|
|
||||||
d.cardBase.insert(a, slot)
|
|
||||||
|
|
||||||
if diskImage != "" {
|
|
||||||
diskette := loadDisquette(diskImage)
|
|
||||||
//diskette.saveNib(diskImage + "bak")
|
|
||||||
d.drive[0].insertDiskette(diskette)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLanguageCard inserts a 16Kb card
|
|
||||||
func (a *Apple2) AddLanguageCard(slot int) {
|
|
||||||
d := newCardLanguage()
|
|
||||||
d.cardBase.insert(a, slot)
|
|
||||||
d.applyState()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSaturnCard inserts a 128Kb card
|
|
||||||
func (a *Apple2) AddSaturnCard(slot int) {
|
|
||||||
d := newCardSaturn()
|
|
||||||
d.cardBase.insert(a, slot)
|
|
||||||
d.applyState()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigureStdConsole uses stdin and stdout to interface with the Apple2
|
|
||||||
func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) {
|
|
||||||
if !stdinKeyboard && !stdoutScreen {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init frontend
|
|
||||||
fe := newAnsiConsoleFrontend(a, stdinKeyboard)
|
|
||||||
if stdinKeyboard {
|
|
||||||
a.io.setKeyboardProvider(fe)
|
|
||||||
}
|
|
||||||
if stdoutScreen {
|
|
||||||
go fe.textModeGoRoutine()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKeyboardProvider attaches an external keyboard provider
|
|
||||||
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
|
||||||
a.io.setKeyboardProvider(kb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSpeakerProvider attaches an external keyboard provider
|
|
||||||
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
|
||||||
a.io.setSpeakerProvider(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// CommandToggleSpeed toggles cpu speed between full speed and actual Apple II speed
|
|
||||||
CommandToggleSpeed = iota + 1
|
|
||||||
// CommandToggleColor toggles between NTSC color TV and Green phospor monitor
|
|
||||||
CommandToggleColor
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendCommand enqueues a command to the emulator thread
|
|
||||||
func (a *Apple2) SendCommand(command int) {
|
|
||||||
a.commandChannel <- command
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Apple2) executeCommand(command int) {
|
|
||||||
switch command {
|
|
||||||
case CommandToggleSpeed:
|
|
||||||
if a.cycleDurationNs == 0 {
|
|
||||||
fmt.Println("Slow")
|
|
||||||
a.cycleDurationNs = 1000.0 / CpuClockMhz
|
|
||||||
} else {
|
|
||||||
fmt.Println("Fast")
|
|
||||||
a.cycleDurationNs = 0
|
|
||||||
}
|
|
||||||
case CommandToggleColor:
|
|
||||||
a.isColor = !a.isColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxWaitDuration = 100 * time.Millisecond
|
const maxWaitDuration = 100 * time.Millisecond
|
||||||
|
|
||||||
// Run starts the Apple2 emulation
|
// Run starts the Apple2 emulation
|
||||||
@ -164,8 +55,8 @@ func (a *Apple2) Run(log bool) {
|
|||||||
clockDuration := time.Since(referenceTime)
|
clockDuration := time.Since(referenceTime)
|
||||||
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
|
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
|
||||||
waitDuration := simulatedDuration - clockDuration
|
waitDuration := simulatedDuration - clockDuration
|
||||||
if waitDuration > maxWaitDuration {
|
if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
|
||||||
// We have to wait too long. Let's fast forward
|
// We have to wait too long or are too much behind. Let's fast forward
|
||||||
referenceTime = referenceTime.Add(-waitDuration)
|
referenceTime = referenceTime.Add(-waitDuration)
|
||||||
waitDuration = 0
|
waitDuration = 0
|
||||||
}
|
}
|
||||||
@ -176,12 +67,43 @@ func (a *Apple2) Run(log bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRom loads a binary file to the top of the memory.
|
|
||||||
const (
|
const (
|
||||||
apple2RomSize = 12 * 1024
|
// CommandToggleSpeed toggles cpu speed between full speed and actual Apple II speed
|
||||||
apple2eRomSize = 16 * 1024
|
CommandToggleSpeed = iota + 1
|
||||||
|
// CommandToggleColor toggles between NTSC color TV and Green phospor monitor
|
||||||
|
CommandToggleColor
|
||||||
|
// CommandSaveState stores the state to file
|
||||||
|
CommandSaveState
|
||||||
|
// CommandLoadState reload the last state
|
||||||
|
CommandLoadState
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SendCommand enqueues a command to the emulator thread
|
||||||
|
func (a *Apple2) SendCommand(command int) {
|
||||||
|
a.commandChannel <- command
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Apple2) executeCommand(command int) {
|
||||||
|
switch command {
|
||||||
|
case CommandToggleSpeed:
|
||||||
|
if a.cycleDurationNs == 0 {
|
||||||
|
fmt.Println("Slow")
|
||||||
|
a.cycleDurationNs = 1000.0 / CpuClockMhz
|
||||||
|
} else {
|
||||||
|
fmt.Println("Fast")
|
||||||
|
a.cycleDurationNs = 0
|
||||||
|
}
|
||||||
|
case CommandToggleColor:
|
||||||
|
a.isColor = !a.isColor
|
||||||
|
case CommandSaveState:
|
||||||
|
fmt.Println("Saving state")
|
||||||
|
a.persistance.save("apple2.state")
|
||||||
|
case CommandLoadState:
|
||||||
|
fmt.Println("Loading state")
|
||||||
|
a.persistance.load("apple2.state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Apple2) requestFastMode() {
|
func (a *Apple2) requestFastMode() {
|
||||||
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
||||||
if a.fastMode {
|
if a.fastMode {
|
||||||
|
91
apple2/apple2Setup.go
Normal file
91
apple2/apple2Setup.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go6502/core6502"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewApple2 instantiates an apple2
|
||||||
|
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
||||||
|
isColor bool, fastMode bool, panicSS bool) *Apple2 {
|
||||||
|
var a Apple2
|
||||||
|
a.persistance = newPersistance(&a)
|
||||||
|
a.mmu = newMemoryManager(&a, romFile)
|
||||||
|
a.persistance.register(a.mmu)
|
||||||
|
a.cpu = core6502.NewNMOS6502(a.mmu)
|
||||||
|
if charRomFile != "" {
|
||||||
|
a.cg = NewCharacterGenerator(charRomFile)
|
||||||
|
}
|
||||||
|
a.commandChannel = make(chan int, 100)
|
||||||
|
a.isColor = isColor
|
||||||
|
a.fastMode = fastMode
|
||||||
|
a.panicSS = panicSS
|
||||||
|
|
||||||
|
if clockMhz <= 0 {
|
||||||
|
// Full speed
|
||||||
|
a.cycleDurationNs = 0
|
||||||
|
} else {
|
||||||
|
a.cycleDurationNs = 1000.0 / clockMhz
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the io in 0xc000
|
||||||
|
a.io = newIoC0Page(&a)
|
||||||
|
a.persistance.register(a.io)
|
||||||
|
a.mmu.setPages(0xc0, 0xc0, a.io)
|
||||||
|
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDisk2 insterts a DiskII controller
|
||||||
|
func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage string) {
|
||||||
|
d := newCardDisk2(diskRomFile)
|
||||||
|
d.cardBase.insert(a, slot)
|
||||||
|
a.persistance.register(d)
|
||||||
|
|
||||||
|
if diskImage != "" {
|
||||||
|
diskette := loadDisquette(diskImage)
|
||||||
|
//diskette.saveNib(diskImage + "bak")
|
||||||
|
d.drive[0].insertDiskette(diskette)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLanguageCard inserts a 16Kb card
|
||||||
|
func (a *Apple2) AddLanguageCard(slot int) {
|
||||||
|
d := newCardLanguage()
|
||||||
|
d.cardBase.insert(a, slot)
|
||||||
|
d.applyState()
|
||||||
|
a.persistance.register(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSaturnCard inserts a 128Kb card
|
||||||
|
func (a *Apple2) AddSaturnCard(slot int) {
|
||||||
|
d := newCardSaturn()
|
||||||
|
d.cardBase.insert(a, slot)
|
||||||
|
d.applyState()
|
||||||
|
a.persistance.register(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureStdConsole uses stdin and stdout to interface with the Apple2
|
||||||
|
func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) {
|
||||||
|
if !stdinKeyboard && !stdoutScreen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init frontend
|
||||||
|
fe := newAnsiConsoleFrontend(a, stdinKeyboard)
|
||||||
|
if stdinKeyboard {
|
||||||
|
a.io.setKeyboardProvider(fe)
|
||||||
|
}
|
||||||
|
if stdoutScreen {
|
||||||
|
go fe.textModeGoRoutine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKeyboardProvider attaches an external keyboard provider
|
||||||
|
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
||||||
|
a.io.setKeyboardProvider(kb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSpeakerProvider attaches an external keyboard provider
|
||||||
|
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
||||||
|
a.io.setSpeakerProvider(s)
|
||||||
|
}
|
@ -1,6 +1,10 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
import "io/ioutil"
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://applesaucefdc.com/woz/reference2/
|
https://applesaucefdc.com/woz/reference2/
|
||||||
@ -148,3 +152,31 @@ func loadCardRom(filename string) []uint8 {
|
|||||||
func (d *cardDisk2Drive) insertDiskette(dt *diskette16sector) {
|
func (d *cardDisk2Drive) insertDiskette(dt *diskette16sector) {
|
||||||
d.diskette = dt
|
d.diskette = dt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cardDisk2) save(w io.Writer) {
|
||||||
|
binary.Write(w, binary.BigEndian, c.selected)
|
||||||
|
c.drive[0].save(w)
|
||||||
|
c.drive[1].save(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cardDisk2) load(r io.Reader) {
|
||||||
|
binary.Read(r, binary.BigEndian, &c.selected)
|
||||||
|
c.drive[0].load(r)
|
||||||
|
c.drive[1].load(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *cardDisk2Drive) save(w io.Writer) {
|
||||||
|
binary.Write(w, binary.BigEndian, d.currentPhase)
|
||||||
|
binary.Write(w, binary.BigEndian, d.power)
|
||||||
|
binary.Write(w, binary.BigEndian, d.writeMode)
|
||||||
|
binary.Write(w, binary.BigEndian, d.halfTrack)
|
||||||
|
binary.Write(w, binary.BigEndian, d.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *cardDisk2Drive) load(r io.Reader) {
|
||||||
|
binary.Read(r, binary.BigEndian, &d.currentPhase)
|
||||||
|
binary.Read(r, binary.BigEndian, &d.power)
|
||||||
|
binary.Read(r, binary.BigEndian, &d.writeMode)
|
||||||
|
binary.Read(r, binary.BigEndian, &d.halfTrack)
|
||||||
|
binary.Read(r, binary.BigEndian, &d.position)
|
||||||
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Language card with 16 extra kb for the Apple ][ and ][+
|
Language card with 16 extra kb for the Apple ][ and ][+
|
||||||
Manual: http://www.applelogic.org/files/LANGCARDMAN.pdf
|
Manual: http://www.applelogic.org/files/LANGCARDMAN.pdf
|
||||||
@ -118,3 +123,23 @@ func (c *cardLanguage) applyState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cardLanguage) save(w io.Writer) {
|
||||||
|
binary.Write(w, binary.BigEndian, c.readState)
|
||||||
|
binary.Write(w, binary.BigEndian, c.writeState)
|
||||||
|
binary.Write(w, binary.BigEndian, c.activeBank)
|
||||||
|
c.ramBankA.save(w)
|
||||||
|
c.ramBankB.save(w)
|
||||||
|
c.ramUpper.save(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cardLanguage) load(r io.Reader) {
|
||||||
|
binary.Read(r, binary.BigEndian, &c.readState)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.writeState)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.activeBank)
|
||||||
|
c.ramBankA.load(r)
|
||||||
|
c.ramBankB.load(r)
|
||||||
|
c.ramUpper.load(r)
|
||||||
|
|
||||||
|
c.applyState()
|
||||||
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
RAM card with 128Kb. It's like 8 language cards.
|
RAM card with 128Kb. It's like 8 language cards.
|
||||||
@ -14,9 +19,9 @@ type cardSaturn struct {
|
|||||||
writeState int
|
writeState int
|
||||||
activeBank int
|
activeBank int
|
||||||
activeBlock int
|
activeBlock int
|
||||||
ramBankA [8]*memoryRange // First 4kb to map in 0xD000-0xDFFF
|
ramBankA [saturnBlocks]*memoryRange // First 4kb to map in 0xD000-0xDFFF
|
||||||
ramBankB [8]*memoryRange // Second 4kb to map in 0xD000-0xDFFF
|
ramBankB [saturnBlocks]*memoryRange // Second 4kb to map in 0xD000-0xDFFF
|
||||||
ramUpper [8]*memoryRange // Upper 8kb to map in 0xE000-0xFFFF
|
ramUpper [saturnBlocks]*memoryRange // Upper 8kb to map in 0xE000-0xFFFF
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -26,13 +31,17 @@ const (
|
|||||||
saturnWriteEnabled = 2
|
saturnWriteEnabled = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
saturnBlocks = 8
|
||||||
|
)
|
||||||
|
|
||||||
func newCardSaturn() *cardSaturn {
|
func newCardSaturn() *cardSaturn {
|
||||||
var c cardSaturn
|
var c cardSaturn
|
||||||
c.readState = false
|
c.readState = false
|
||||||
c.writeState = lcWriteEnabled
|
c.writeState = lcWriteEnabled
|
||||||
c.activeBank = 1
|
c.activeBank = 1
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < saturnBlocks; i++ {
|
||||||
c.ramBankA[i] = newMemoryRange(0xd000, make([]uint8, 0x1000))
|
c.ramBankA[i] = newMemoryRange(0xd000, make([]uint8, 0x1000))
|
||||||
c.ramBankB[i] = newMemoryRange(0xd000, make([]uint8, 0x1000))
|
c.ramBankB[i] = newMemoryRange(0xd000, make([]uint8, 0x1000))
|
||||||
c.ramUpper[i] = newMemoryRange(0xe000, make([]uint8, 0x2000))
|
c.ramUpper[i] = newMemoryRange(0xe000, make([]uint8, 0x2000))
|
||||||
@ -102,13 +111,13 @@ func (c *cardSaturn) ssAction(ss int) {
|
|||||||
c.readState = true
|
c.readState = true
|
||||||
c.writeState++
|
c.writeState++
|
||||||
case 12:
|
case 12:
|
||||||
c.activeBlock = 0
|
c.activeBlock = 4
|
||||||
case 13:
|
case 13:
|
||||||
c.activeBlock = 1
|
c.activeBlock = 5
|
||||||
case 14:
|
case 14:
|
||||||
c.activeBlock = 2
|
c.activeBlock = 6
|
||||||
case 15:
|
case 15:
|
||||||
c.activeBlock = 3
|
c.activeBlock = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.writeState > lcWriteEnabled {
|
if c.writeState > lcWriteEnabled {
|
||||||
@ -142,5 +151,30 @@ func (c *cardSaturn) applyState() {
|
|||||||
} else {
|
} else {
|
||||||
mmu.setPagesWrite(0xd0, 0xff, nil)
|
mmu.setPagesWrite(0xd0, 0xff, nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cardSaturn) save(w io.Writer) {
|
||||||
|
for i := 0; i < saturnBlocks; i++ {
|
||||||
|
binary.Write(w, binary.BigEndian, c.readState)
|
||||||
|
binary.Write(w, binary.BigEndian, c.writeState)
|
||||||
|
binary.Write(w, binary.BigEndian, c.activeBank)
|
||||||
|
binary.Write(w, binary.BigEndian, c.activeBlock)
|
||||||
|
c.ramBankA[i].save(w)
|
||||||
|
c.ramBankB[i].save(w)
|
||||||
|
c.ramUpper[i].save(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cardSaturn) load(r io.Reader) {
|
||||||
|
for i := 0; i < saturnBlocks; i++ {
|
||||||
|
binary.Read(r, binary.BigEndian, &c.readState)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.writeState)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.activeBank)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.activeBlock)
|
||||||
|
c.ramBankA[i].load(r)
|
||||||
|
c.ramBankB[i].load(r)
|
||||||
|
c.ramUpper[i].load(r)
|
||||||
|
|
||||||
|
c.applyState()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ioC0Page struct {
|
type ioC0Page struct {
|
||||||
@ -47,6 +49,14 @@ func newIoC0Page(a *Apple2) *ioC0Page {
|
|||||||
return &io
|
return &io
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ioC0Page) save(w io.Writer) {
|
||||||
|
binary.Write(w, binary.BigEndian, p.softSwitchesData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ioC0Page) load(r io.Reader) {
|
||||||
|
binary.Read(r, binary.BigEndian, &p.softSwitchesData)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ioC0Page) addSoftSwitchRW(address uint8, ss softSwitchR) {
|
func (p *ioC0Page) addSoftSwitchRW(address uint8, ss softSwitchR) {
|
||||||
p.addSoftSwitchR(address, ss)
|
p.addSoftSwitchR(address, ss)
|
||||||
p.addSoftSwitchW(address, func(p *ioC0Page, _ uint8) {
|
p.addSoftSwitchW(address, func(p *ioC0Page, _ uint8) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -91,18 +92,35 @@ func (mmu *memoryManager) resetSlotExpansionRoms() {
|
|||||||
mmu.setPagesRead(0xc8, 0xcf, nil)
|
mmu.setPagesRead(0xc8, 0xcf, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMemoryManager(a *Apple2) *memoryManager {
|
func (mmu *memoryManager) resetRomPaging() {
|
||||||
|
// Assign the first 12kb of ROM from 0xd000 to 0xffff
|
||||||
|
mmu.setPagesRead(0xd0, 0xff, mmu.physicalROM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mmu *memoryManager) resetBaseRamPaging() {
|
||||||
|
// Assign the base RAM from 0x0000 to 0xbfff
|
||||||
|
mmu.setPages(0x00, 0xbf, mmu.physicalMainRAM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMemoryManager(a *Apple2, romFile string) *memoryManager {
|
||||||
var mmu memoryManager
|
var mmu memoryManager
|
||||||
mmu.apple2 = a
|
mmu.apple2 = a
|
||||||
|
|
||||||
// Assign RAM from 0x0000 to 0xbfff, 48kb
|
ram := make([]uint8, 0xc000) // Reserve 48kb
|
||||||
ram := make([]uint8, 0xc000)
|
|
||||||
mmu.physicalMainRAM = newMemoryRange(0, ram)
|
mmu.physicalMainRAM = newMemoryRange(0, ram)
|
||||||
mmu.setPages(0x00, 0xc0, mmu.physicalMainRAM)
|
|
||||||
|
mmu.loadRom(romFile)
|
||||||
|
mmu.resetBaseRamPaging()
|
||||||
|
mmu.resetRomPaging()
|
||||||
|
|
||||||
return &mmu
|
return &mmu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
apple2RomSize = 12 * 1024
|
||||||
|
apple2eRomSize = 16 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
func (mmu *memoryManager) loadRom(filename string) {
|
func (mmu *memoryManager) loadRom(filename string) {
|
||||||
data, err := ioutil.ReadFile(filename)
|
data, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -120,17 +138,18 @@ func (mmu *memoryManager) loadRom(filename string) {
|
|||||||
// It starts with 256 unused bytes not mapped to 0xc000.
|
// It starts with 256 unused bytes not mapped to 0xc000.
|
||||||
a.isApple2e = true
|
a.isApple2e = true
|
||||||
extraRomSize := apple2eRomSize - apple2RomSize
|
extraRomSize := apple2eRomSize - apple2RomSize
|
||||||
a.mmu.physicalROMe = newMemoryRange(0xc000, data[0:extraRomSize])
|
mmu.physicalROMe = newMemoryRange(0xc000, data[0:extraRomSize])
|
||||||
romStart = extraRomSize
|
romStart = extraRomSize
|
||||||
}
|
}
|
||||||
|
|
||||||
a.mmu.physicalROM = newMemoryRange(0xd000, data[romStart:])
|
mmu.physicalROM = newMemoryRange(0xd000, data[romStart:])
|
||||||
mmu.resetRomPaging()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mmu *memoryManager) resetRomPaging() {
|
func (mmu *memoryManager) save(w io.Writer) {
|
||||||
// Assign the first 12kb of ROM from 0xd000 to 0xfff
|
mmu.physicalMainRAM.save(w)
|
||||||
for i := 0x0000; i < 0x3000; i = i + 0x100 {
|
}
|
||||||
mmu.setPagesRead(0xd0, 0xff, mmu.physicalROM)
|
|
||||||
}
|
func (mmu *memoryManager) load(r io.Reader) {
|
||||||
|
mmu.physicalMainRAM.load(r)
|
||||||
|
mmu.resetBaseRamPaging()
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
type memoryRange struct {
|
type memoryRange struct {
|
||||||
base uint16
|
base uint16
|
||||||
data []uint8
|
data []uint8
|
||||||
@ -23,3 +28,13 @@ func (m *memoryRange) poke(address uint16, value uint8) {
|
|||||||
func (m *memoryRange) subRange(a, b uint16) []uint8 {
|
func (m *memoryRange) subRange(a, b uint16) []uint8 {
|
||||||
return m.data[a-m.base : b-m.base]
|
return m.data[a-m.base : b-m.base]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *memoryRange) save(w io.Writer) {
|
||||||
|
binary.Write(w, binary.BigEndian, m.base)
|
||||||
|
binary.Write(w, binary.BigEndian, m.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryRange) load(r io.Reader) {
|
||||||
|
binary.Read(r, binary.BigEndian, &m.base)
|
||||||
|
binary.Read(r, binary.BigEndian, &m.data)
|
||||||
|
}
|
||||||
|
66
apple2/persistance.go
Normal file
66
apple2/persistance.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type persistent interface {
|
||||||
|
save(w io.Writer)
|
||||||
|
load(r io.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
type persistance struct {
|
||||||
|
a *Apple2
|
||||||
|
items []persistent
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPersistance(a *Apple2) *persistance {
|
||||||
|
var p persistance
|
||||||
|
p.a = a
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *persistance) register(i persistent) {
|
||||||
|
p.items = append(p.items, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *persistance) save(filename string) {
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
w := bufio.NewWriter(f)
|
||||||
|
defer w.Flush()
|
||||||
|
|
||||||
|
binary.Write(w, binary.BigEndian, p.a.isColor)
|
||||||
|
binary.Write(w, binary.BigEndian, p.a.fastMode)
|
||||||
|
binary.Write(w, binary.BigEndian, p.a.fastRequestsCounter)
|
||||||
|
p.a.cpu.Save(w)
|
||||||
|
|
||||||
|
for _, v := range p.items {
|
||||||
|
v.save(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *persistance) load(filename string) {
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore error if can't load the file
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := bufio.NewReader(f)
|
||||||
|
|
||||||
|
binary.Read(r, binary.BigEndian, &p.a.isColor)
|
||||||
|
binary.Read(r, binary.BigEndian, &p.a.fastMode)
|
||||||
|
binary.Read(r, binary.BigEndian, &p.a.fastRequestsCounter)
|
||||||
|
p.a.cpu.Load(r)
|
||||||
|
|
||||||
|
for _, v := range p.items {
|
||||||
|
v.load(r)
|
||||||
|
}
|
||||||
|
}
|
@ -61,3 +61,5 @@ func softSwitchSlotC3RomOn(io *ioC0Page) {
|
|||||||
func softSwitchSlotC3RomOff(io *ioC0Page) {
|
func softSwitchSlotC3RomOff(io *ioC0Page) {
|
||||||
io.apple2.mmu.setPagesRead(0xc3, 0xc3, io.apple2.mmu.physicalROMe)
|
io.apple2.mmu.setPagesRead(0xc3, 0xc3, io.apple2.mmu.physicalROMe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: apply state after persistance load
|
||||||
|
@ -93,6 +93,10 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
|||||||
k.a.SendCommand(apple2.CommandToggleSpeed)
|
k.a.SendCommand(apple2.CommandToggleSpeed)
|
||||||
case sdl.K_F6:
|
case sdl.K_F6:
|
||||||
k.a.SendCommand(apple2.CommandToggleColor)
|
k.a.SendCommand(apple2.CommandToggleColor)
|
||||||
|
case sdl.K_F7:
|
||||||
|
k.a.SendCommand(apple2.CommandSaveState)
|
||||||
|
case sdl.K_F8:
|
||||||
|
k.a.SendCommand(apple2.CommandLoadState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Missing values 91 to 95. Usually control for [\]^_
|
// Missing values 91 to 95. Usually control for [\]^_
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package core6502
|
package core6502
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
// https://www.masswerk.at/6502/6502_instruction_set.html
|
// https://www.masswerk.at/6502/6502_instruction_set.html
|
||||||
// http://www.emulator101.com/reference/6502-reference.html
|
// http://www.emulator101.com/reference/6502-reference.html
|
||||||
@ -78,6 +82,18 @@ func (s *State) GetCycles() uint64 {
|
|||||||
return s.cycles
|
return s.cycles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save saves the CPU state (registers and cycle counter)
|
||||||
|
func (s *State) Save(w io.Writer) {
|
||||||
|
binary.Write(w, binary.BigEndian, s.cycles)
|
||||||
|
binary.Write(w, binary.BigEndian, s.reg.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the CPU state (registers and cycle counter)
|
||||||
|
func (s *State) Load(r io.Reader) {
|
||||||
|
binary.Read(r, binary.BigEndian, &s.cycles)
|
||||||
|
binary.Read(r, binary.BigEndian, &s.reg.data)
|
||||||
|
}
|
||||||
|
|
||||||
func lineString(line []uint8, opcode opcode) string {
|
func lineString(line []uint8, opcode opcode) string {
|
||||||
t := opcode.name
|
t := opcode.name
|
||||||
switch opcode.addressMode {
|
switch opcode.addressMode {
|
||||||
|
@ -6,9 +6,10 @@ const (
|
|||||||
regA = 0
|
regA = 0
|
||||||
regX = 1
|
regX = 1
|
||||||
regY = 2
|
regY = 2
|
||||||
regP = 4
|
regP = 3
|
||||||
regSP = 5
|
regSP = 4
|
||||||
regPC = 6 // 2 bytes
|
regPC = 5 // 2 bytes
|
||||||
|
regPC2 = 6
|
||||||
regNone = -1
|
regNone = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type registers struct {
|
type registers struct {
|
||||||
data [8]uint8
|
data [7]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *registers) getRegister(i int) uint8 { return r.data[i] }
|
func (r *registers) getRegister(i int) uint8 { return r.data[i] }
|
||||||
@ -45,12 +46,12 @@ func (r *registers) setP(v uint8) { r.setRegister(regP, v) }
|
|||||||
func (r *registers) setSP(v uint8) { r.setRegister(regSP, v) }
|
func (r *registers) setSP(v uint8) { r.setRegister(regSP, v) }
|
||||||
|
|
||||||
func (r *registers) getPC() uint16 {
|
func (r *registers) getPC() uint16 {
|
||||||
return uint16(r.data[regPC])*256 + uint16(r.data[regPC+1])
|
return uint16(r.data[regPC])*256 + uint16(r.data[regPC2])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *registers) setPC(v uint16) {
|
func (r *registers) setPC(v uint16) {
|
||||||
r.data[regPC] = uint8(v >> 8)
|
r.data[regPC] = uint8(v >> 8)
|
||||||
r.data[regPC+1] = uint8(v)
|
r.data[regPC2] = uint8(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *registers) getFlagBit(i uint8) uint8 {
|
func (r *registers) getFlagBit(i uint8) uint8 {
|
||||||
|
21
main.go
21
main.go
@ -18,7 +18,7 @@ func main() {
|
|||||||
disk2Slot := flag.Int(
|
disk2Slot := flag.Int(
|
||||||
"disk2Slot",
|
"disk2Slot",
|
||||||
6,
|
6,
|
||||||
"slot for the disk driver. 0 for none.")
|
"slot for the disk driver. -1 for none.")
|
||||||
diskImage := flag.String(
|
diskImage := flag.String(
|
||||||
"disk",
|
"disk",
|
||||||
"../dos33.dsk",
|
"../dos33.dsk",
|
||||||
@ -31,6 +31,15 @@ func main() {
|
|||||||
"charRom",
|
"charRom",
|
||||||
"apple2/romdumps/Apple2rev7CharGen.rom",
|
"apple2/romdumps/Apple2rev7CharGen.rom",
|
||||||
"rom file for the disk drive controller")
|
"rom file for the disk drive controller")
|
||||||
|
languageCardSlot := flag.Int(
|
||||||
|
"languageCardSlot",
|
||||||
|
0,
|
||||||
|
"slot for the 16kb language card. -1 for none")
|
||||||
|
saturnCardSlot := flag.Int(
|
||||||
|
"saturnCardSlot",
|
||||||
|
-1,
|
||||||
|
"slot for the 256kb Saturn card. -1 for none")
|
||||||
|
|
||||||
useSdl := flag.Bool(
|
useSdl := flag.Bool(
|
||||||
"sdl",
|
"sdl",
|
||||||
true,
|
true,
|
||||||
@ -69,9 +78,13 @@ func main() {
|
|||||||
|
|
||||||
log := false
|
log := false
|
||||||
a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
|
a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
|
||||||
//a.AddLanguageCard(0)
|
if *languageCardSlot >= 0 {
|
||||||
a.AddSaturnCard(0)
|
a.AddLanguageCard(*languageCardSlot)
|
||||||
if *disk2Slot > 0 {
|
}
|
||||||
|
if *saturnCardSlot >= 0 {
|
||||||
|
a.AddSaturnCard(*saturnCardSlot)
|
||||||
|
}
|
||||||
|
if *disk2Slot >= 0 {
|
||||||
a.AddDisk2(*disk2Slot, *disk2RomFile, *diskImage)
|
a.AddDisk2(*disk2Slot, *disk2RomFile, *diskImage)
|
||||||
}
|
}
|
||||||
if *useSdl {
|
if *useSdl {
|
||||||
|
Loading…
Reference in New Issue
Block a user