izapple2/apple2.go

291 lines
6.4 KiB
Go
Raw Normal View History

package apple2
import (
2019-05-18 14:43:51 +00:00
"bufio"
"encoding/binary"
2019-05-05 10:38:24 +00:00
"fmt"
2019-05-18 14:43:51 +00:00
"io"
"os"
2019-05-04 17:49:11 +00:00
"time"
"github.com/ivanizag/apple2/core6502"
2019-10-19 18:31:18 +00:00
"github.com/pkg/profile"
)
2019-03-02 19:41:25 +00:00
// Apple2 represents all the components and state of the emulated machine
type Apple2 struct {
Name string
cpu *core6502.State
mmu *memoryManager
io *ioC0Page
cg *CharacterGenerator
2019-05-18 14:43:51 +00:00
cards [8]card
isApple2e bool
commandChannel chan int
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
isColor bool
fastMode bool
fastRequestsCounter int
2019-10-19 18:31:18 +00:00
profile bool
2019-11-07 22:20:14 +00:00
showSpeed bool
}
2019-05-04 17:49:11 +00:00
const (
2019-11-07 22:20:14 +00:00
// CPUClockMhz is the actual Apple II clock speed
CPUClockMhz = 14.318 / 14
2019-05-04 17:49:11 +00:00
cpuClockEuroMhz = 14.238 / 14
)
const maxWaitDuration = 100 * time.Millisecond
// Run starts the Apple2 emulation
func (a *Apple2) Run() {
2019-10-19 18:31:18 +00:00
if a.profile {
// See the log with:
// go tool pprof --pdf ~/go/bin/apple2sdl /tmp/profile329536248/cpu.pprof > profile.pdf
defer profile.Start().Stop()
}
// Start the processor
a.cpu.Reset()
2019-05-05 10:38:24 +00:00
referenceTime := time.Now()
2019-11-07 22:20:14 +00:00
speedReferenceTime := referenceTime
speedReferenceCycles := uint64(0)
2019-05-18 21:40:59 +00:00
for {
2019-05-04 17:49:11 +00:00
// Run a 6502 step
a.cpu.ExecuteInstruction()
2019-05-04 17:49:11 +00:00
// Execute meta commands
commandsPending := true
for commandsPending {
select {
case command := <-a.commandChannel:
2019-10-19 18:31:18 +00:00
if command == CommandKill {
return
}
2019-05-04 17:49:11 +00:00
a.executeCommand(command)
default:
commandsPending = false
}
}
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
2019-05-04 17:49:11 +00:00
// Wait until next 6502 step has to run
2019-05-05 10:38:24 +00:00
clockDuration := time.Since(referenceTime)
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
waitDuration := simulatedDuration - clockDuration
if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
// We have to wait too long or are too much behind. Let's fast forward
2019-05-05 10:38:24 +00:00
referenceTime = referenceTime.Add(-waitDuration)
waitDuration = 0
}
2019-05-04 17:49:11 +00:00
if waitDuration > 0 {
time.Sleep(waitDuration)
}
}
2019-11-07 22:20:14 +00:00
if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 {
// Calculate speed in MHz every million cycles
newTime := time.Now()
newCycles := a.cpu.GetCycles()
elapsedCycles := float64(newCycles - speedReferenceCycles)
freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
fmt.Printf("Freq: %f Mhz\n", freq)
speedReferenceTime = newTime
speedReferenceCycles = newCycles
}
2019-05-04 17:49:11 +00:00
}
2019-04-13 18:29:31 +00:00
}
2019-10-19 18:31:18 +00:00
func (a *Apple2) setProfile(value bool) {
a.profile = value
}
const (
// CommandToggleSpeed toggles cpu speed between full speed and actual Apple II speed
CommandToggleSpeed = iota + 1
2019-11-07 22:20:14 +00:00
// CommandShowSpeed toggles printinf the current freq in Mhz
CommandShowSpeed
// CommandToggleColor toggles between NTSC color TV and Green phospor monitor
CommandToggleColor
// CommandSaveState stores the state to file
CommandSaveState
// CommandLoadState reload the last state
CommandLoadState
2019-05-18 21:40:59 +00:00
// CommandDumpDebugInfo dumps usefull info
CommandDumpDebugInfo
// CommandNextCharGenPage cycles the CharGen page if several
CommandNextCharGenPage
// CommandToggleCPUTrace toggle tracing of CPU execution
CommandToggleCPUTrace
2019-10-19 18:31:18 +00:00
// CommandKill stops the cpu execution loop
CommandKill
)
// 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")
2019-11-07 22:20:14 +00:00
a.cycleDurationNs = 1000.0 / CPUClockMhz
} else {
fmt.Println("Fast")
a.cycleDurationNs = 0
}
2019-11-07 22:20:14 +00:00
case CommandShowSpeed:
a.showSpeed = !a.showSpeed
case CommandToggleColor:
a.isColor = !a.isColor
case CommandSaveState:
fmt.Println("Saving state")
2019-10-05 23:26:00 +00:00
err := a.save("apple2.state")
if err != nil {
fmt.Printf("Error loadind state: %v.", err)
}
case CommandLoadState:
fmt.Println("Loading state")
2019-10-05 23:26:00 +00:00
err := a.load("apple2.state")
if err != nil {
fmt.Printf("Error loadind state: %v.", err)
}
2019-05-18 21:40:59 +00:00
case CommandDumpDebugInfo:
a.dumpDebugInfo()
case CommandNextCharGenPage:
a.cg.nextPage()
fmt.Printf("Chargen page %v\n", a.cg.page)
case CommandToggleCPUTrace:
a.cpu.SetTrace(!a.cpu.GetTrace())
}
}
func (a *Apple2) requestFastMode() {
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
if a.fastMode {
a.fastRequestsCounter++
}
}
func (a *Apple2) releaseFastMode() {
if a.fastMode {
a.fastRequestsCounter--
}
}
2019-05-18 14:43:51 +00:00
type persistent interface {
2019-10-05 23:26:00 +00:00
save(io.Writer) error
load(io.Reader) error
2019-05-18 14:43:51 +00:00
}
2019-10-05 23:26:00 +00:00
func (a *Apple2) save(filename string) error {
2019-05-18 14:43:51 +00:00
f, err := os.Create(filename)
if err != nil {
2019-10-05 23:26:00 +00:00
return err
2019-05-18 14:43:51 +00:00
}
defer f.Close()
w := bufio.NewWriter(f)
defer w.Flush()
2019-10-05 23:26:00 +00:00
err = a.cpu.Save(w)
if err != nil {
return err
}
err = a.mmu.save(w)
if err != nil {
return err
}
err = a.io.save(w)
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, a.isColor)
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, a.fastMode)
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, a.fastRequestsCounter)
if err != nil {
return err
}
2019-05-18 14:43:51 +00:00
for _, c := range a.cards {
if c != nil {
2019-10-05 23:26:00 +00:00
err = c.save(w)
if err != nil {
return err
}
2019-05-18 14:43:51 +00:00
}
}
2019-10-05 23:26:00 +00:00
return nil
2019-05-18 14:43:51 +00:00
}
2019-10-05 23:26:00 +00:00
func (a *Apple2) load(filename string) error {
2019-05-18 14:43:51 +00:00
f, err := os.Open(filename)
if err != nil {
2019-10-05 23:26:00 +00:00
return err
2019-05-18 14:43:51 +00:00
}
defer f.Close()
r := bufio.NewReader(f)
2019-10-05 23:26:00 +00:00
err = a.cpu.Load(r)
if err != nil {
return err
}
err = a.mmu.load(r)
if err != nil {
return err
}
err = a.io.load(r)
if err != nil {
return err
}
err = binary.Read(r, binary.BigEndian, &a.isColor)
if err != nil {
return err
}
err = binary.Read(r, binary.BigEndian, &a.fastMode)
if err != nil {
return err
}
err = binary.Read(r, binary.BigEndian, &a.fastRequestsCounter)
if err != nil {
return err
}
2019-05-18 14:43:51 +00:00
for _, c := range a.cards {
if c != nil {
2019-10-05 23:26:00 +00:00
err = c.load(r)
if err != nil {
return err
}
2019-05-18 14:43:51 +00:00
}
}
2019-10-05 23:26:00 +00:00
return nil
2019-05-18 14:43:51 +00:00
}
2019-05-18 21:40:59 +00:00
func (a *Apple2) dumpDebugInfo() {
// See "Apple II Monitors Peeled"
pageZeroSymbols := map[int]string{
0x36: "CSWL",
0x37: "CSWH",
0x38: "KSWL",
0x39: "KSWH",
}
fmt.Printf("Page zero values:\n")
for _, k := range []int{0x36, 0x37, 0x38, 0x39} {
d := a.mmu.physicalMainRAM.data[k]
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
}
}