2020-10-03 21:38:26 +00:00
|
|
|
package izapple2
|
2019-02-16 19:15:41 +00:00
|
|
|
|
2019-03-02 17:33:50 +00:00
|
|
|
import (
|
2019-05-05 10:38:24 +00:00
|
|
|
"fmt"
|
2022-05-10 11:40:17 +00:00
|
|
|
"sync/atomic"
|
2019-05-04 17:49:11 +00:00
|
|
|
"time"
|
2019-06-01 15:11:25 +00:00
|
|
|
|
2022-01-28 17:09:59 +00:00
|
|
|
"github.com/ivanizag/iz6502"
|
2019-03-02 17:33:50 +00:00
|
|
|
)
|
2019-02-16 19:15:41 +00:00
|
|
|
|
2019-03-02 19:41:25 +00:00
|
|
|
// Apple2 represents all the components and state of the emulated machine
|
2019-03-02 17:33:50 +00:00
|
|
|
type Apple2 struct {
|
2019-06-09 21:54:27 +00:00
|
|
|
Name string
|
2022-01-28 17:09:59 +00:00
|
|
|
cpu *iz6502.State
|
2019-05-10 16:07:36 +00:00
|
|
|
mmu *memoryManager
|
|
|
|
io *ioC0Page
|
|
|
|
cg *CharacterGenerator
|
2020-10-13 22:26:47 +00:00
|
|
|
cards [8]Card
|
2021-03-14 17:46:52 +00:00
|
|
|
softVideoSwitch *SoftVideoSwitch
|
2019-05-10 16:07:36 +00:00
|
|
|
isApple2e bool
|
|
|
|
commandChannel chan int
|
2019-05-17 21:28:20 +00:00
|
|
|
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
|
2019-05-10 16:07:36 +00:00
|
|
|
fastMode bool
|
2022-05-10 11:40:17 +00:00
|
|
|
fastRequestsCounter int32
|
|
|
|
cycleBreakpoint uint64
|
|
|
|
breakPoint bool
|
2020-01-11 16:23:34 +00:00
|
|
|
profile bool
|
2019-11-07 22:20:14 +00:00
|
|
|
showSpeed bool
|
2020-03-14 02:29:12 +00:00
|
|
|
paused bool
|
2021-04-02 18:39:37 +00:00
|
|
|
tracers []executionTracer
|
2020-11-02 19:17:00 +00:00
|
|
|
forceCaps bool
|
2019-03-02 17:33:50 +00:00
|
|
|
}
|
|
|
|
|
2021-04-02 18:39:37 +00:00
|
|
|
type executionTracer interface {
|
|
|
|
inspect()
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
)
|
|
|
|
|
2020-08-13 18:34:37 +00:00
|
|
|
const (
|
|
|
|
maxWaitDuration = 100 * time.Millisecond
|
|
|
|
cpuSpinLoops = 100
|
|
|
|
)
|
2019-05-10 16:07:36 +00:00
|
|
|
|
2019-05-03 20:00:48 +00:00
|
|
|
// Run starts the Apple2 emulation
|
2019-09-24 21:32:03 +00:00
|
|
|
func (a *Apple2) Run() {
|
2022-05-10 11:40:17 +00:00
|
|
|
a.Start(false)
|
|
|
|
}
|
2019-10-19 18:31:18 +00:00
|
|
|
|
2022-05-10 11:40:17 +00:00
|
|
|
// Start the Apple2 emulation, can start paused
|
|
|
|
func (a *Apple2) Start(paused bool) {
|
2019-02-16 19:15:41 +00:00
|
|
|
// Start the processor
|
2019-03-02 17:33:50 +00:00
|
|
|
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
|
|
|
|
2022-05-10 11:40:17 +00:00
|
|
|
a.paused = paused
|
|
|
|
|
2019-02-17 23:00:39 +00:00
|
|
|
for {
|
2022-05-10 11:40:17 +00:00
|
|
|
// Run 6502 steps
|
2020-03-14 02:29:12 +00:00
|
|
|
if !a.paused {
|
2020-08-13 18:34:37 +00:00
|
|
|
for i := 0; i < cpuSpinLoops; i++ {
|
2020-10-24 22:22:52 +00:00
|
|
|
// Conditional tracing
|
|
|
|
//pc, _ := a.cpu.GetPCAndSP()
|
2022-10-24 21:09:06 +00:00
|
|
|
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
|
2020-10-24 22:22:52 +00:00
|
|
|
|
|
|
|
// Execution
|
2020-08-13 18:34:37 +00:00
|
|
|
a.cpu.ExecuteInstruction()
|
2020-10-24 22:22:52 +00:00
|
|
|
|
|
|
|
// Special tracing
|
2020-08-13 18:34:37 +00:00
|
|
|
a.executionTrace()
|
|
|
|
}
|
2022-05-10 11:40:17 +00:00
|
|
|
|
|
|
|
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
|
|
|
|
a.breakPoint = true
|
|
|
|
a.cycleBreakpoint = 0
|
|
|
|
a.paused = true
|
|
|
|
}
|
2020-03-14 02:29:12 +00:00
|
|
|
} else {
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
}
|
2019-03-02 17:33:50 +00:00
|
|
|
|
2019-05-04 17:49:11 +00:00
|
|
|
// Execute meta commands
|
|
|
|
commandsPending := true
|
|
|
|
for commandsPending {
|
|
|
|
select {
|
|
|
|
case command := <-a.commandChannel:
|
2020-06-07 16:23:39 +00:00
|
|
|
switch command {
|
|
|
|
case CommandKill:
|
2019-10-19 18:31:18 +00:00
|
|
|
return
|
2022-05-10 11:40:17 +00:00
|
|
|
case CommandPause:
|
|
|
|
if !a.paused {
|
|
|
|
a.paused = true
|
|
|
|
}
|
|
|
|
case CommandStart:
|
|
|
|
if a.paused {
|
|
|
|
a.paused = false
|
|
|
|
referenceTime = time.Now()
|
|
|
|
speedReferenceTime = referenceTime
|
|
|
|
}
|
|
|
|
case CommandPauseUnpause:
|
2020-06-07 16:23:39 +00:00
|
|
|
a.paused = !a.paused
|
|
|
|
referenceTime = time.Now()
|
|
|
|
speedReferenceTime = referenceTime
|
|
|
|
default:
|
|
|
|
// Execute the other commands
|
|
|
|
a.executeCommand(command)
|
2019-10-19 18:31:18 +00:00
|
|
|
}
|
2019-05-04 17:49:11 +00:00
|
|
|
default:
|
|
|
|
commandsPending = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-10 16:07:36 +00:00
|
|
|
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
|
2019-05-17 21:28:20 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-10-27 23:43:33 +00:00
|
|
|
func (a *Apple2) reset() {
|
|
|
|
a.cpu.Reset()
|
|
|
|
a.mmu.reset()
|
|
|
|
for _, c := range a.cards {
|
|
|
|
if c != nil {
|
|
|
|
c.reset()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-14 02:29:12 +00:00
|
|
|
// IsPaused returns true when emulator is paused
|
|
|
|
func (a *Apple2) IsPaused() bool {
|
|
|
|
return a.paused
|
|
|
|
}
|
|
|
|
|
2022-05-10 11:40:17 +00:00
|
|
|
func (a *Apple2) GetCycles() uint64 {
|
|
|
|
return a.cpu.GetCycles()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetCycleBreakpoint sets a cycle number to pause the emulator. 0 to disable
|
|
|
|
func (a *Apple2) SetCycleBreakpoint(cycle uint64) {
|
|
|
|
a.cycleBreakpoint = cycle
|
|
|
|
a.breakPoint = false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Apple2) BreakPoint() bool {
|
|
|
|
return a.breakPoint
|
|
|
|
}
|
|
|
|
|
2020-01-11 16:23:34 +00:00
|
|
|
func (a *Apple2) setProfiling(value bool) {
|
|
|
|
a.profile = value
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsProfiling returns true when profiling
|
|
|
|
func (a *Apple2) IsProfiling() bool {
|
|
|
|
return a.profile
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:26:56 +00:00
|
|
|
// SetForceCaps allows the caps state to be toggled at runtime
|
|
|
|
func (a *Apple2) SetForceCaps(value bool) {
|
2020-11-02 19:17:00 +00:00
|
|
|
a.forceCaps = value
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsForceCaps returns true when all letters are forced to upper case
|
|
|
|
func (a *Apple2) IsForceCaps() bool {
|
|
|
|
return a.forceCaps
|
|
|
|
}
|
|
|
|
|
2019-03-02 17:33:50 +00:00
|
|
|
const (
|
2019-05-17 21:28:20 +00:00
|
|
|
// 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
|
2020-10-07 07:37:13 +00:00
|
|
|
// CommandDumpDebugInfo dumps useful info
|
2019-05-18 21:40:59 +00:00
|
|
|
CommandDumpDebugInfo
|
2019-06-09 21:54:27 +00:00
|
|
|
// CommandNextCharGenPage cycles the CharGen page if several
|
|
|
|
CommandNextCharGenPage
|
2019-10-02 21:00:02 +00:00
|
|
|
// CommandToggleCPUTrace toggle tracing of CPU execution
|
|
|
|
CommandToggleCPUTrace
|
2019-10-19 18:31:18 +00:00
|
|
|
// CommandKill stops the cpu execution loop
|
|
|
|
CommandKill
|
2019-11-20 23:13:53 +00:00
|
|
|
// CommandReset executes a 6502 reset
|
|
|
|
CommandReset
|
2022-05-10 11:40:17 +00:00
|
|
|
// CommandPauseUnpause allows the Pause button to freeze the emulator for a coffee break
|
|
|
|
CommandPauseUnpause
|
|
|
|
// CommandPause pauses the emulator
|
|
|
|
CommandPause
|
|
|
|
// CommandStart restarts the emulator
|
|
|
|
CommandStart
|
2019-03-02 17:33:50 +00:00
|
|
|
)
|
|
|
|
|
2019-05-17 21:28:20 +00:00
|
|
|
// 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 {
|
2020-10-17 18:10:48 +00:00
|
|
|
//fmt.Println("Slow")
|
2019-11-07 22:20:14 +00:00
|
|
|
a.cycleDurationNs = 1000.0 / CPUClockMhz
|
2019-05-17 21:28:20 +00:00
|
|
|
} else {
|
2020-10-17 18:10:48 +00:00
|
|
|
//fmt.Println("Fast")
|
2019-05-17 21:28:20 +00:00
|
|
|
a.cycleDurationNs = 0
|
|
|
|
}
|
2019-11-07 22:20:14 +00:00
|
|
|
case CommandShowSpeed:
|
|
|
|
a.showSpeed = !a.showSpeed
|
2019-05-18 21:40:59 +00:00
|
|
|
case CommandDumpDebugInfo:
|
|
|
|
a.dumpDebugInfo()
|
2019-06-09 21:54:27 +00:00
|
|
|
case CommandNextCharGenPage:
|
|
|
|
a.cg.nextPage()
|
|
|
|
fmt.Printf("Chargen page %v\n", a.cg.page)
|
2019-10-02 21:00:02 +00:00
|
|
|
case CommandToggleCPUTrace:
|
|
|
|
a.cpu.SetTrace(!a.cpu.GetTrace())
|
2019-11-20 23:13:53 +00:00
|
|
|
case CommandReset:
|
2020-10-27 23:43:33 +00:00
|
|
|
a.reset()
|
2019-05-17 21:28:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 11:40:17 +00:00
|
|
|
func (a *Apple2) RequestFastMode() {
|
2019-05-10 16:07:36 +00:00
|
|
|
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
|
|
|
if a.fastMode {
|
2022-05-10 11:40:17 +00:00
|
|
|
atomic.AddInt32(&a.fastRequestsCounter, 1)
|
2019-05-10 16:07:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 11:40:17 +00:00
|
|
|
func (a *Apple2) ReleaseFastMode() {
|
2019-05-10 16:07:36 +00:00
|
|
|
if a.fastMode {
|
2022-05-10 11:40:17 +00:00
|
|
|
atomic.AddInt32(&a.fastRequestsCounter, -1)
|
2019-05-10 16:07:36 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-18 14:43:51 +00:00
|
|
|
|
2020-06-07 16:23:39 +00:00
|
|
|
func (a *Apple2) executionTrace() {
|
2021-04-02 18:39:37 +00:00
|
|
|
if a.tracers != nil {
|
|
|
|
for _, v := range a.tracers {
|
|
|
|
v.inspect()
|
|
|
|
}
|
2020-06-07 16:23:39 +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",
|
2021-04-02 18:39:37 +00:00
|
|
|
0xe2: "ACJVAFLDL", // Apple Pascal
|
|
|
|
0xe3: "ACJVAFLDH", // Apple Pascal
|
|
|
|
0xec: "JVBFOLDL", // Apple Pascal
|
|
|
|
0xed: "JVBFOLDH", // Apple Pascal
|
|
|
|
0xee: "JVAFOLDL", // Apple Pascal
|
|
|
|
0xef: "JVAFOLDH", // Apple Pascal
|
2019-05-18 21:40:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Page zero values:\n")
|
2021-04-02 18:39:37 +00:00
|
|
|
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
|
2019-05-18 21:40:59 +00:00
|
|
|
d := a.mmu.physicalMainRAM.data[k]
|
|
|
|
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
|
|
|
|
}
|
|
|
|
}
|