From 78ff401ff0d606f77b29322fe580c542301a8ecc Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sat, 4 May 2019 19:49:11 +0200 Subject: [PATCH] CPU clock speed emulation --- apple2/ansiConsoleFrontend.go | 4 +- apple2/apple2.go | 79 ++++++++++++++++++++++++++++------- apple2/memoryManager.go | 2 +- apple2sdl/run.go | 2 +- core6502/execute.go | 6 +++ main.go | 14 +++---- 6 files changed, 79 insertions(+), 28 deletions(-) diff --git a/apple2/ansiConsoleFrontend.go b/apple2/ansiConsoleFrontend.go index 8ac5587..9817914 100644 --- a/apple2/ansiConsoleFrontend.go +++ b/apple2/ansiConsoleFrontend.go @@ -51,12 +51,12 @@ func (fe *ansiConsoleFrontend) GetKey(strobed bool) (key uint8, ok bool) { stdinReader := func(c chan uint8) { reader := bufio.NewReader(os.Stdin) for { - byte, err := reader.ReadByte() + key, err := reader.ReadByte() if err != nil { fmt.Println(err) return } - c <- byte + c <- key } } diff --git a/apple2/apple2.go b/apple2/apple2.go index 5e13093..b74ff59 100644 --- a/apple2/apple2.go +++ b/apple2/apple2.go @@ -4,22 +4,31 @@ import ( "bufio" "go6502/core6502" "os" + "time" ) // Apple2 represents all the components and state of the emulated machine type Apple2 struct { - cpu *core6502.State - mmu *memoryManager - io *ioC0Page - cg *CharacterGenerator - cards []cardBase - isApple2e bool - panicSS bool - activeSlot int // Slot that has the addressing 0xc800 to 0ccfff + cpu *core6502.State + mmu *memoryManager + io *ioC0Page + cg *CharacterGenerator + cards []cardBase + isApple2e bool + panicSS bool + activeSlot int // Slot that has the addressing 0xc800 to 0ccfff + commandChannel chan int + cycleDurationNs float64 // Inverse of the cpu clock in Ghz } +const ( + // CpuClockMhz is the actual Apple II clock speed + CpuClockMhz = 14.318 / 14 + cpuClockEuroMhz = 14.238 / 14 +) + // NewApple2 instantiates an apple2 -func NewApple2(romFile string, charRomFile string, panicSS bool) *Apple2 { +func NewApple2(romFile string, charRomFile string, clockMhz float64, panicSS bool) *Apple2 { var a Apple2 a.mmu = newMemoryManager(&a) a.cpu = core6502.NewNMOS6502(a.mmu) @@ -27,9 +36,17 @@ func NewApple2(romFile string, charRomFile string, panicSS bool) *Apple2 { if charRomFile != "" { a.cg = NewCharacterGenerator(charRomFile) } - a.mmu.resetPaging() + a.mmu.resetRomPaging() + a.commandChannel = make(chan int, 100) 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.setPage(0xc0, a.io) @@ -65,18 +82,50 @@ func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) { } } +// SetKeyboardProvider attaches an external keyboard provider +func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) { + a.io.setKeyboardProvider(kb) +} + +// SendCommand enqueues a command to the emulator thread +func (a *Apple2) SendCommand(command int) { + a.commandChannel <- command +} + +func (a *Apple2) executeCommand(command int) { + //TODO +} + // Run starts the Apple2 emulation func (a *Apple2) Run(log bool) { // Start the processor a.cpu.Reset() + startTime := time.Now() for { + // Run a 6502 step a.cpu.ExecuteInstruction(log) - } -} -// SetKeyboardProvider attaches an external keyboard provider -func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) { - a.io.setKeyboardProvider(kb) + // Execute meta commands + commandsPending := true + for commandsPending { + select { + case command := <-a.commandChannel: + a.executeCommand(command) + default: + commandsPending = false + } + } + + if a.cycleDurationNs != 0 { + // Wait until next 6502 step has to run + clockDuration := time.Since(startTime) + simulatedDurationNs := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs) + waitDuration := simulatedDurationNs - clockDuration + if waitDuration > 0 { + time.Sleep(waitDuration) + } + } + } } // LoadRom loads a binary file to the top of the memory. diff --git a/apple2/memoryManager.go b/apple2/memoryManager.go index d8c0a65..e453792 100644 --- a/apple2/memoryManager.go +++ b/apple2/memoryManager.go @@ -98,7 +98,7 @@ func newMemoryManager(a *Apple2) *memoryManager { return &mmu } -func (mmu *memoryManager) resetPaging() { +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])) diff --git a/apple2sdl/run.go b/apple2sdl/run.go index d79ac1e..3135675 100644 --- a/apple2sdl/run.go +++ b/apple2sdl/run.go @@ -68,7 +68,7 @@ func SDLRun(a *apple2.Apple2) { surface.Free() texture.Destroy() } - sdl.Delay(1000 / 60) + sdl.Delay(1000 / 30) } } diff --git a/core6502/execute.go b/core6502/execute.go index 21e127c..e53515b 100644 --- a/core6502/execute.go +++ b/core6502/execute.go @@ -69,9 +69,15 @@ func (s *State) ExecuteInstruction(log bool) { // Reset resets the processor state. Moves the program counter to the vector in 0cfffc. func (s *State) Reset() { startAddress := getWord(s.mem, vectorReset) + s.cycles = 0 s.reg.setPC(startAddress) } +// GetCycles returns the count of CPU cycles since last reset. +func (s *State) GetCycles() int64 { + return s.cycles +} + func lineString(line []uint8, opcode opcode) string { t := opcode.name switch opcode.addressMode { diff --git a/main.go b/main.go index b0360f5..4a43c7e 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,10 @@ func main() { "disk", "../dos33.dsk", "file to load on the first disk drive") + cpuClock := flag.Float64( + "mhz", + apple2.CpuClockMhz, + "cpu speed in Mhz, use 0 for full speed") charRomFile := flag.String( "charRom", "apple2/romdumps/Apple2rev7CharGen.rom", @@ -42,14 +46,6 @@ func main() { ) flag.Parse() - //romFile := "apple2/romdumps/Apple2.rom" - //romFile := "apple2/romdumps/Apple2_Plus.rom" - //romFile := "apple2/romdumps/Apple2e.rom" - //disk2RomFile := "apple2/romdumps/DISK2.rom" - //diskImage := "../dos33.dsk" - //diskImage := "../Apex II - Apple II Diagnostic (v4.7-1986).DSK" - //diskImage := "../A2Diag.v4.1.SDK" - if *dumpChars { cg := apple2.NewCharacterGenerator(*charRomFile) cg.Dump() @@ -57,7 +53,7 @@ func main() { } log := false - a := apple2.NewApple2(*romFile, *charRomFile, *panicSS) + a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, *panicSS) a.AddDisk2(*disk2RomFile, *diskImage) if *useSdl { a.ConfigureStdConsole(false, *stdoutScreen)