From 9cf24ab9044c1940b37252307c973e6e762e54bb Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sat, 14 Sep 2024 22:01:53 +0200 Subject: [PATCH] Support DMA requests from expansion cards --- apple2.go | 7 ++++++- apple2Run.go | 39 +++++++++++++++++++++++++-------------- apple2Tester.go | 4 ++-- boardApple2.go | 8 ++++---- boardApple2e.go | 2 +- cardBase.go | 17 +++++++++++++++++ cardBrainBoard_test.go | 2 +- cardDisk2.go | 14 +++++++------- cardDisk2Sequencer.go | 2 +- command.go | 3 ++- e2e_woz_test.go | 4 ++-- memoryManager.go | 2 +- traceBuilder.go | 5 ++++- traceProDOS.go | 4 ---- 14 files changed, 73 insertions(+), 40 deletions(-) diff --git a/apple2.go b/apple2.go index e3c6fad..358be2c 100644 --- a/apple2.go +++ b/apple2.go @@ -25,6 +25,10 @@ type Apple2 struct { isFourColors bool // An Apple II without the 6 color mod commandChannel chan command + dmaActive bool + dmaSlot int + + cycles uint64 cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz fastRequestsCounter int32 cycleBreakpoint uint64 @@ -32,6 +36,7 @@ type Apple2 struct { profile bool showSpeed bool paused bool + cpuTrace bool forceCaps bool removableMediaDrives []drive } @@ -67,7 +72,7 @@ func (a *Apple2) IsPaused() bool { } func (a *Apple2) GetCycles() uint64 { - return a.cpu.GetCycles() + return a.cycles } // SetCycleBreakpoint sets a cycle number to pause the emulator. 0 to disable diff --git a/apple2Run.go b/apple2Run.go index 67522ae..1398622 100644 --- a/apple2Run.go +++ b/apple2Run.go @@ -25,6 +25,8 @@ func (a *Apple2) Run() { func (a *Apple2) Start(paused bool) { // Start the processor a.cpu.Reset() + a.cycles = a.cpu.GetCycles() + referenceTime := time.Now() speedReferenceTime := referenceTime speedReferenceCycles := uint64(0) @@ -34,19 +36,29 @@ func (a *Apple2) Start(paused bool) { for { // Run 6502 steps if !a.paused { - for i := 0; i < cpuSpinLoops; i++ { - // Conditional tracing - // pc, _ := a.cpu.GetPCAndSP() - // a.cpu.SetTrace(pc >= 0xc700 && pc < 0xc800) + if !a.dmaActive { + spinStartCycles := a.cpu.GetCycles() + for i := 0; i < cpuSpinLoops && !a.dmaActive; i++ { + // Conditional tracing + // pc, _ := a.cpu.GetPCAndSP() + // a.cpu.SetTrace(pc >= 0xc700 && pc < 0xc800) - // Execution - a.cpu.ExecuteInstruction() + // Execution + a.cpu.ExecuteInstruction() - // Special tracing - a.executionTrace() + // Special tracing + a.executionTrace() + } + a.cycles += a.cpu.GetCycles() - spinStartCycles + } else { + card := a.cards[a.dmaSlot] + for i := 0; i < cpuSpinLoops && a.dmaActive; i++ { + card.runDMACycle() + a.cycles++ + } } - if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint { + if a.cycleBreakpoint != 0 && a.cycles >= a.cycleBreakpoint { a.breakPoint = true a.cycleBreakpoint = 0 a.paused = true @@ -89,7 +101,7 @@ func (a *Apple2) Start(paused bool) { if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 { // Wait until next 6502 step has to run clockDuration := time.Since(referenceTime) - simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs) + simulatedDuration := time.Duration(float64(a.cycles) * 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 @@ -101,15 +113,14 @@ func (a *Apple2) Start(paused bool) { } } - if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 { + if a.showSpeed && a.cycles-speedReferenceCycles > 1000000 { // Calculate speed in MHz every million cycles newTime := time.Now() - newCycles := a.cpu.GetCycles() - elapsedCycles := float64(newCycles - speedReferenceCycles) + elapsedCycles := float64(a.cycles - speedReferenceCycles) freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds()) fmt.Printf("Freq: %f Mhz\n", freq) speedReferenceTime = newTime - speedReferenceCycles = newCycles + speedReferenceCycles = a.cycles } } } diff --git a/apple2Tester.go b/apple2Tester.go index 7e7ced0..e5d5f60 100644 --- a/apple2Tester.go +++ b/apple2Tester.go @@ -69,7 +69,7 @@ func (at *apple2Tester) getText(textMode testTextModeFunc) string { /* func buildTerminateConditionCycles(cycles uint64) terminateConditionFunc { return func(a *Apple2) bool { - return a.cpu.GetCycles() > cycles + return a.cycles() > cycles } } */ @@ -85,7 +85,7 @@ func buildTerminateConditionTexts(needles []string, textMode testTextModeFunc, t lastCheck := uint64(0) found := false return func(a *Apple2) bool { - cycles := a.cpu.GetCycles() + cycles := a.GetCycles() if cycles > timeoutCycles { return true } diff --git a/boardApple2.go b/boardApple2.go index e30b30d..760f4ee 100644 --- a/boardApple2.go +++ b/boardApple2.go @@ -85,7 +85,7 @@ func addApple2SoftSwitches(io *ioC0Page) { func buildNotImplementedSoftSwitchR(io *ioC0Page) softSwitchR { return func() uint8 { // Return random info. Some games (Serpentine) used CASSETTE and get stuck if not changing. - return uint8(io.apple2.cpu.GetCycles()) + return uint8(io.apple2.GetCycles()) } } @@ -121,7 +121,7 @@ func getSoftSwitch(io *ioC0Page, ioFlag uint8, isSet bool) softSwitchR { func buildSpeakerSoftSwitch(io *ioC0Page) softSwitchR { return func() uint8 { if io.speaker != nil { - io.speaker.Click(io.apple2.cpu.GetCycles()) + io.speaker.Click(io.apple2.GetCycles()) } return 0 } @@ -179,7 +179,7 @@ func buildPaddleSoftSwitch(io *ioC0Page, i int) softSwitchR { return 255 // Capacitors never discharge if there is not joystick } cyclesNeeded := uint64(reading) * paddleToCyclesFactor - cyclesElapsed := io.apple2.cpu.GetCycles() - io.paddlesStrobeCycle + cyclesElapsed := io.apple2.GetCycles() - io.paddlesStrobeCycle if cyclesElapsed < cyclesNeeded { // The capacitor is not charged yet return 128 @@ -191,7 +191,7 @@ func buildPaddleSoftSwitch(io *ioC0Page, i int) softSwitchR { func buildStrobePaddlesSoftSwitch(io *ioC0Page) softSwitchR { return func() uint8 { // On the real machine this discharges the capacitors. - io.paddlesStrobeCycle = io.apple2.cpu.GetCycles() + io.paddlesStrobeCycle = io.apple2.GetCycles() return 0 } } diff --git a/boardApple2e.go b/boardApple2e.go index e0bb1ed..aa1adf7 100644 --- a/boardApple2e.go +++ b/boardApple2e.go @@ -50,7 +50,7 @@ func addApple2ESoftSwitches(io *ioC0Page) { // 12480 cycles drawing lines, VERTBLANK = $00 // 4550 cycles doing the return to position (0,0), VERTBLANK = $80 // Vert blank takes 12480 cycles every page redraw - cycles := io.apple2.cpu.GetCycles() % screenDrawCycles + cycles := io.apple2.GetCycles() % screenDrawCycles if cycles <= screenVertBlankingCycles { return ssOn } diff --git a/cardBase.go b/cardBase.go index 967c4e0..96ca1fe 100644 --- a/cardBase.go +++ b/cardBase.go @@ -9,6 +9,7 @@ type Card interface { loadRom(data []uint8, layout cardRomLayout) error assign(a *Apple2, slot int) reset() + runDMACycle() setName(name string) setDebug(debug bool) @@ -154,6 +155,22 @@ func (c *cardBase) assign(a *Apple2, slot int) { } } +func (c *cardBase) runDMACycle() { + // No DMA +} + +func (c *cardBase) activateDMA() { + if c.a.dmaActive { + panic("DMA chain not supported") + } + c.a.dmaActive = true + c.a.dmaSlot = c.slot +} + +func (c *cardBase) deactivateDMA() { + c.a.dmaActive = false +} + func (c *cardBase) addCardSoftSwitchR(address uint8, ss softSwitchR, name string) { c._ssr[address] = ss c._ssrName[address] = name diff --git a/cardBrainBoard_test.go b/cardBrainBoard_test.go index 5d8219e..ed138aa 100644 --- a/cardBrainBoard_test.go +++ b/cardBrainBoard_test.go @@ -25,7 +25,7 @@ func TestBrainBoardCardWozaniam(t *testing.T) { at := buildBrainBoardTester(t, "brainboard,switch=up") at.terminateCondition = func(a *Apple2) bool { - return a.cpu.GetCycles() > 10_000_000 + return a.GetCycles() > 10_000_000 } at.run() diff --git a/cardDisk2.go b/cardDisk2.go index e0fd0ef..078e191 100644 --- a/cardDisk2.go +++ b/cardDisk2.go @@ -212,7 +212,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) { } drive := &c.drive[c.selected] if drive.diskette != nil { - drive.diskette.PowerOff(c.a.cpu.GetCycles()) + drive.diskette.PowerOff(c.a.GetCycles()) } } else if value && !c.power { // Turn on @@ -222,7 +222,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) { } drive := &c.drive[c.selected] if drive.diskette != nil { - drive.diskette.PowerOn(c.a.cpu.GetCycles()) + drive.diskette.PowerOn(c.a.GetCycles()) } } } @@ -231,10 +231,10 @@ func (c *CardDisk2) softSwitchQ5(selected int) { if c.power && c.selected != selected { // Selected changed with power on, power goes to the other disk if c.drive[c.selected].diskette != nil { - c.drive[c.selected].diskette.PowerOff(c.a.cpu.GetCycles()) + c.drive[c.selected].diskette.PowerOff(c.a.GetCycles()) } if c.drive[selected].diskette != nil { - c.drive[selected].diskette.PowerOn(c.a.cpu.GetCycles()) + c.drive[selected].diskette.PowerOn(c.a.GetCycles()) } } @@ -271,9 +271,9 @@ func (c *CardDisk2) processQ6Q7(in uint8) { } if !c.q6 { // shift if !c.q7 { // Q6L-Q7L: Read - c.dataLatch = d.diskette.Read(d.trackStep, c.a.cpu.GetCycles()) + c.dataLatch = d.diskette.Read(d.trackStep, c.a.GetCycles()) } else { // Q6L-Q7H: Write the dataLatch value to disk. Shift data out - d.diskette.Write(d.trackStep, c.dataLatch, c.a.cpu.GetCycles()) + d.diskette.Write(d.trackStep, c.dataLatch, c.a.GetCycles()) } } else { // load if !c.q7 { // Q6H-Q7L: Sense write protect / prewrite state @@ -286,7 +286,7 @@ func (c *CardDisk2) processQ6Q7(in uint8) { /* if c.dataLatch >= 0x80 { - fmt.Printf("Datalach: 0x%.2x in cycle %v\n", c.dataLatch, c.a.cpu.GetCycles()) + fmt.Printf("Datalach: 0x%.2x in cycle %v\n", c.dataLatch, c.a.GetCycles()) } */ } diff --git a/cardDisk2Sequencer.go b/cardDisk2Sequencer.go index 46da0e2..dd89e80 100644 --- a/cardDisk2Sequencer.go +++ b/cardDisk2Sequencer.go @@ -166,7 +166,7 @@ func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) { } func (c *CardDisk2Sequencer) catchUp(data uint8) { - currentCycle := c.a.cpu.GetCycles() << 1 // Disk2 cycles are x2 cpu cycle + currentCycle := c.a.GetCycles() << 1 // Disk2 cycles are x2 cpu cycle motorOn := c.step(data, true) if motorOn && currentCycle > c.lastCycle+disk2CyclestoLoseSsync { diff --git a/command.go b/command.go index c66f8fd..05252e1 100644 --- a/command.go +++ b/command.go @@ -84,7 +84,8 @@ func (a *Apple2) executeCommand(command command) { a.cg.nextPage() fmt.Printf("Chargen page %v\n", a.cg.page) case CommandToggleCPUTrace: - a.cpu.SetTrace(!a.cpu.GetTrace()) + a.cpuTrace = !a.cpuTrace + a.cpu.SetTrace(a.cpuTrace) case CommandReset: a.reset() case CommandComplex: diff --git a/e2e_woz_test.go b/e2e_woz_test.go index 6c98967..8880960 100644 --- a/e2e_woz_test.go +++ b/e2e_woz_test.go @@ -30,7 +30,7 @@ func testWoz(t *testing.T, sequencer bool, file string, expectedTracks []int, cy tracksMayMatch := len(tt.quarterTracks) >= expectedLen && tt.quarterTracks[expectedLen-1] == expectedTracks[expectedLen-1] - return tracksMayMatch || a.cpu.GetCycles() > cycleLimit + return tracksMayMatch || a.GetCycles() > cycleLimit } at.run() @@ -38,7 +38,7 @@ func testWoz(t *testing.T, sequencer bool, file string, expectedTracks []int, cy t.Errorf("Quarter tracks, expected %#v, got %#v", expectedTracks, tt.quarterTracks) } - // t.Errorf("Cycles: %d vs %d", at.a.cpu.GetCycles(), cycleLimit) + // t.Errorf("Cycles: %d vs %d", at.a.GetCycles(), cycleLimit) } const ( diff --git a/memoryManager.go b/memoryManager.go index 619d182..8767efd 100644 --- a/memoryManager.go +++ b/memoryManager.go @@ -27,7 +27,7 @@ type memoryManager struct { physicalExtAltRAM []*memoryRange // 0xd000 to 0xdfff, 4Kb. Up to 256 banks. // Configuration switches, Language cards - lcSelectedBlock uint8 // Language card block selected. Usually, allways 0. But Saturn has 8 + lcSelectedBlock uint8 // Language card block selected. Usually, always 0. But Saturn has 8 lcActiveRead bool // Upper RAM active for read lcActiveWrite bool // Upper RAM active for write lcAltBank bool // Alternate diff --git a/traceBuilder.go b/traceBuilder.go index e3934ff..cb20319 100644 --- a/traceBuilder.go +++ b/traceBuilder.go @@ -52,7 +52,10 @@ func getTracerFactory() map[string]*traceBuilder { tracerFactory["cpu"] = &traceBuilder{ name: "cpu", description: "Trace CPU execution", - connectFunc: func(a *Apple2) { a.cpu.SetTrace(true) }, + connectFunc: func(a *Apple2) { + a.cpuTrace = true + a.cpu.SetTrace(true) + }, } tracerFactory["ss"] = &traceBuilder{ name: "ss", diff --git a/traceProDOS.go b/traceProDOS.go index e66ed04..fbe1bb4 100644 --- a/traceProDOS.go +++ b/traceProDOS.go @@ -57,11 +57,9 @@ func (t *traceProDOS) inspect() { t.dumpMLICall() t.refreshDeviceDrives() t.callPending = true - // t.a.cpu.SetTrace(true) } else if t.callPending && pc == t.returnAddress { t.dumpMLIReturn() t.callPending = false - // t.a.cpu.SetTrace(false) } else if pc == biAddress { t.dumpBIExec() } else if /*t.callPending &&*/ t.isDriverAddress(pc) { @@ -189,8 +187,6 @@ func (t *traceProDOS) dumpMLIReturn() { default: fmt.Printf("Ok\n") } - - t.a.cpu.SetTrace(false) } }