diff --git a/README.md b/README.md index 5fa213c..bb3b266 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ Only valid on SDL mode emulate a green phosphor monitor instead of a NTSC color TV. Use F6 to toggle. -panicss panic if a not implemented softswitch is used + -profile + generate profile trace to analyse with pprof -rom string main rom file (default "/Apple2_Plus.rom") -saturnCardSlot int diff --git a/apple2.go b/apple2.go index 7ebab0b..f4c6e40 100644 --- a/apple2.go +++ b/apple2.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ivanizag/apple2/core6502" + "github.com/pkg/profile" ) // Apple2 represents all the components and state of the emulated machine @@ -25,6 +26,7 @@ type Apple2 struct { isColor bool fastMode bool fastRequestsCounter int + profile bool } const ( @@ -37,6 +39,12 @@ const maxWaitDuration = 100 * time.Millisecond // Run starts the Apple2 emulation func (a *Apple2) Run() { + 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() referenceTime := time.Now() @@ -50,6 +58,9 @@ func (a *Apple2) Run() { for commandsPending { select { case command := <-a.commandChannel: + if command == CommandKill { + return + } a.executeCommand(command) default: commandsPending = false @@ -73,6 +84,10 @@ func (a *Apple2) Run() { } } +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 @@ -88,6 +103,8 @@ const ( CommandNextCharGenPage // CommandToggleCPUTrace toggle tracing of CPU execution CommandToggleCPUTrace + // CommandKill stops the cpu execution loop + CommandKill ) // SendCommand enqueues a command to the emulator thread diff --git a/apple2main.go b/apple2main.go index 32a6a6c..70e0c3a 100644 --- a/apple2main.go +++ b/apple2main.go @@ -88,6 +88,11 @@ func MainApple() *Apple2 { "2plus", "set base model. Models available 2plus, 2e, base64a", ) + profile := flag.Bool( + "profile", + false, + "generate profile trace to analyse with pprof", + ) flag.Parse() var a *Apple2 @@ -138,6 +143,7 @@ func MainApple() *Apple2 { a.cpu.SetTrace(*traceCPU) a.io.setTrace(*traceSS) a.io.setPanicNotImplemented(*panicSS) + a.setProfile(*profile) // Load ROM if not loaded already if *romFile != "" { diff --git a/apple2sdl/main.go b/apple2sdl/main.go index 4166e64..5c81b4b 100644 --- a/apple2sdl/main.go +++ b/apple2sdl/main.go @@ -43,6 +43,7 @@ func SDLRun(a *apple2.Apple2) { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch t := event.(type) { case *sdl.QuitEvent: + a.SendCommand(apple2.CommandKill) running = false case *sdl.KeyboardEvent: //fmt.Printf("[%d ms] Keyboard\ttype:%d\tsym:%c\tmodifiers:%d\tstate:%d\trepeat:%d\n", diff --git a/apple2sdl/sdlSpeaker.go b/apple2sdl/sdlSpeaker.go index df131cc..d26adcf 100644 --- a/apple2sdl/sdlSpeaker.go +++ b/apple2sdl/sdlSpeaker.go @@ -21,6 +21,7 @@ const ( sampleDurationCycles = 1000000 * apple2.CpuClockMhz / samplingHz // each sample on the sound stream is 21.31 cpu cycles approx maxOutOfSyncMs = 2000 + decayLevel = 128 ) type sdlSpeaker struct { @@ -28,6 +29,7 @@ type sdlSpeaker struct { pendingClicks []uint64 lastCycle uint64 lastState bool + lastLevel C.Uint8 } /* @@ -40,6 +42,7 @@ func newSDLSpeaker() *sdlSpeaker { var s sdlSpeaker s.clickChannel = make(chan uint64, bufferSize) s.pendingClicks = make([]uint64, 0, bufferSize) + s.lastLevel = decayLevel // Mid position to avoid starting clicks. return &s } @@ -55,6 +58,7 @@ func stateToLevel(state bool) C.Uint8 { return 0 } +// SpeakerCallback is called to get more sound buffer data //export SpeakerCallback func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) { s := theSDLSpeaker @@ -90,7 +94,7 @@ func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) { // Build wave var i, p int - level := stateToLevel(s.lastState) + level := s.lastLevel for p = 0; p < len(s.pendingClicks); p++ { cycle := s.pendingClicks[p] if cycle < s.lastCycle { @@ -119,10 +123,26 @@ func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) { } } + // If the buffer is empty lets decay the signal + if i == 0 { + for level != decayLevel && i < bufferSize { + if i%100 == 0 { + if level > decayLevel { + level-- + } else { + level++ + } + } + buf[i] = level + i++ + } + } + // Complete the buffer if needed for b := i; b < bufferSize; b++ { buf[b] = level } + s.lastLevel = level // Remove processed clicks, store the rest for later remainingClicks := len(s.pendingClicks) - p diff --git a/memoryManager.go b/memoryManager.go index 56360f3..32f02bd 100644 --- a/memoryManager.go +++ b/memoryManager.go @@ -33,7 +33,7 @@ const ( ioC8Off uint16 = 0xCFFF ) -func (mmu *memoryManager) access(address uint16, activeMemory [256]memoryHandler) memoryHandler { +func (mmu *memoryManager) access(address uint16, activeMemory *[256]memoryHandler) memoryHandler { if address == ioC8Off { mmu.resetSlotExpansionRoms() } @@ -54,7 +54,7 @@ func (mmu *memoryManager) access(address uint16, activeMemory [256]memoryHandler // Peek returns the data on the given address func (mmu *memoryManager) Peek(address uint16) uint8 { - mh := mmu.access(address, mmu.activeMemoryRead) + mh := mmu.access(address, &mmu.activeMemoryRead) if mh == nil { return 0xf4 // Or some random number } @@ -63,7 +63,7 @@ func (mmu *memoryManager) Peek(address uint16) uint8 { // Poke sets the data at the given address func (mmu *memoryManager) Poke(address uint16, value uint8) { - mh := mmu.access(address, mmu.activeMemoryWrite) + mh := mmu.access(address, &mmu.activeMemoryWrite) if mh == nil { return }