Support DMA requests from expansion cards

This commit is contained in:
Ivan Izaguirre
2024-09-14 22:01:53 +02:00
parent 1cbb97d55f
commit 9cf24ab904
14 changed files with 73 additions and 40 deletions

View File

@@ -25,6 +25,10 @@ type Apple2 struct {
isFourColors bool // An Apple II without the 6 color mod isFourColors bool // An Apple II without the 6 color mod
commandChannel chan command commandChannel chan command
dmaActive bool
dmaSlot int
cycles uint64
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
fastRequestsCounter int32 fastRequestsCounter int32
cycleBreakpoint uint64 cycleBreakpoint uint64
@@ -32,6 +36,7 @@ type Apple2 struct {
profile bool profile bool
showSpeed bool showSpeed bool
paused bool paused bool
cpuTrace bool
forceCaps bool forceCaps bool
removableMediaDrives []drive removableMediaDrives []drive
} }
@@ -67,7 +72,7 @@ func (a *Apple2) IsPaused() bool {
} }
func (a *Apple2) GetCycles() uint64 { func (a *Apple2) GetCycles() uint64 {
return a.cpu.GetCycles() return a.cycles
} }
// SetCycleBreakpoint sets a cycle number to pause the emulator. 0 to disable // SetCycleBreakpoint sets a cycle number to pause the emulator. 0 to disable

View File

@@ -25,6 +25,8 @@ func (a *Apple2) Run() {
func (a *Apple2) Start(paused bool) { func (a *Apple2) Start(paused bool) {
// Start the processor // Start the processor
a.cpu.Reset() a.cpu.Reset()
a.cycles = a.cpu.GetCycles()
referenceTime := time.Now() referenceTime := time.Now()
speedReferenceTime := referenceTime speedReferenceTime := referenceTime
speedReferenceCycles := uint64(0) speedReferenceCycles := uint64(0)
@@ -34,19 +36,29 @@ func (a *Apple2) Start(paused bool) {
for { for {
// Run 6502 steps // Run 6502 steps
if !a.paused { if !a.paused {
for i := 0; i < cpuSpinLoops; i++ { if !a.dmaActive {
// Conditional tracing spinStartCycles := a.cpu.GetCycles()
// pc, _ := a.cpu.GetPCAndSP() for i := 0; i < cpuSpinLoops && !a.dmaActive; i++ {
// a.cpu.SetTrace(pc >= 0xc700 && pc < 0xc800) // Conditional tracing
// pc, _ := a.cpu.GetPCAndSP()
// a.cpu.SetTrace(pc >= 0xc700 && pc < 0xc800)
// Execution // Execution
a.cpu.ExecuteInstruction() a.cpu.ExecuteInstruction()
// Special tracing // Special tracing
a.executionTrace() 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.breakPoint = true
a.cycleBreakpoint = 0 a.cycleBreakpoint = 0
a.paused = true a.paused = true
@@ -89,7 +101,7 @@ func (a *Apple2) Start(paused bool) {
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 { if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
// Wait until next 6502 step has to run // Wait until next 6502 step has to run
clockDuration := time.Since(referenceTime) clockDuration := time.Since(referenceTime)
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs) simulatedDuration := time.Duration(float64(a.cycles) * a.cycleDurationNs)
waitDuration := simulatedDuration - clockDuration waitDuration := simulatedDuration - clockDuration
if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration { if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
// We have to wait too long or are too much behind. Let's fast forward // 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 // Calculate speed in MHz every million cycles
newTime := time.Now() newTime := time.Now()
newCycles := a.cpu.GetCycles() elapsedCycles := float64(a.cycles - speedReferenceCycles)
elapsedCycles := float64(newCycles - speedReferenceCycles)
freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds()) freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
fmt.Printf("Freq: %f Mhz\n", freq) fmt.Printf("Freq: %f Mhz\n", freq)
speedReferenceTime = newTime speedReferenceTime = newTime
speedReferenceCycles = newCycles speedReferenceCycles = a.cycles
} }
} }
} }

View File

@@ -69,7 +69,7 @@ func (at *apple2Tester) getText(textMode testTextModeFunc) string {
/* /*
func buildTerminateConditionCycles(cycles uint64) terminateConditionFunc { func buildTerminateConditionCycles(cycles uint64) terminateConditionFunc {
return func(a *Apple2) bool { 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) lastCheck := uint64(0)
found := false found := false
return func(a *Apple2) bool { return func(a *Apple2) bool {
cycles := a.cpu.GetCycles() cycles := a.GetCycles()
if cycles > timeoutCycles { if cycles > timeoutCycles {
return true return true
} }

View File

@@ -85,7 +85,7 @@ func addApple2SoftSwitches(io *ioC0Page) {
func buildNotImplementedSoftSwitchR(io *ioC0Page) softSwitchR { func buildNotImplementedSoftSwitchR(io *ioC0Page) softSwitchR {
return func() uint8 { return func() uint8 {
// Return random info. Some games (Serpentine) used CASSETTE and get stuck if not changing. // 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 { func buildSpeakerSoftSwitch(io *ioC0Page) softSwitchR {
return func() uint8 { return func() uint8 {
if io.speaker != nil { if io.speaker != nil {
io.speaker.Click(io.apple2.cpu.GetCycles()) io.speaker.Click(io.apple2.GetCycles())
} }
return 0 return 0
} }
@@ -179,7 +179,7 @@ func buildPaddleSoftSwitch(io *ioC0Page, i int) softSwitchR {
return 255 // Capacitors never discharge if there is not joystick return 255 // Capacitors never discharge if there is not joystick
} }
cyclesNeeded := uint64(reading) * paddleToCyclesFactor cyclesNeeded := uint64(reading) * paddleToCyclesFactor
cyclesElapsed := io.apple2.cpu.GetCycles() - io.paddlesStrobeCycle cyclesElapsed := io.apple2.GetCycles() - io.paddlesStrobeCycle
if cyclesElapsed < cyclesNeeded { if cyclesElapsed < cyclesNeeded {
// The capacitor is not charged yet // The capacitor is not charged yet
return 128 return 128
@@ -191,7 +191,7 @@ func buildPaddleSoftSwitch(io *ioC0Page, i int) softSwitchR {
func buildStrobePaddlesSoftSwitch(io *ioC0Page) softSwitchR { func buildStrobePaddlesSoftSwitch(io *ioC0Page) softSwitchR {
return func() uint8 { return func() uint8 {
// On the real machine this discharges the capacitors. // On the real machine this discharges the capacitors.
io.paddlesStrobeCycle = io.apple2.cpu.GetCycles() io.paddlesStrobeCycle = io.apple2.GetCycles()
return 0 return 0
} }
} }

View File

@@ -50,7 +50,7 @@ func addApple2ESoftSwitches(io *ioC0Page) {
// 12480 cycles drawing lines, VERTBLANK = $00 // 12480 cycles drawing lines, VERTBLANK = $00
// 4550 cycles doing the return to position (0,0), VERTBLANK = $80 // 4550 cycles doing the return to position (0,0), VERTBLANK = $80
// Vert blank takes 12480 cycles every page redraw // Vert blank takes 12480 cycles every page redraw
cycles := io.apple2.cpu.GetCycles() % screenDrawCycles cycles := io.apple2.GetCycles() % screenDrawCycles
if cycles <= screenVertBlankingCycles { if cycles <= screenVertBlankingCycles {
return ssOn return ssOn
} }

View File

@@ -9,6 +9,7 @@ type Card interface {
loadRom(data []uint8, layout cardRomLayout) error loadRom(data []uint8, layout cardRomLayout) error
assign(a *Apple2, slot int) assign(a *Apple2, slot int)
reset() reset()
runDMACycle()
setName(name string) setName(name string)
setDebug(debug bool) 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) { func (c *cardBase) addCardSoftSwitchR(address uint8, ss softSwitchR, name string) {
c._ssr[address] = ss c._ssr[address] = ss
c._ssrName[address] = name c._ssrName[address] = name

View File

@@ -25,7 +25,7 @@ func TestBrainBoardCardWozaniam(t *testing.T) {
at := buildBrainBoardTester(t, "brainboard,switch=up") at := buildBrainBoardTester(t, "brainboard,switch=up")
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 10_000_000 return a.GetCycles() > 10_000_000
} }
at.run() at.run()

View File

@@ -212,7 +212,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
} }
drive := &c.drive[c.selected] drive := &c.drive[c.selected]
if drive.diskette != nil { if drive.diskette != nil {
drive.diskette.PowerOff(c.a.cpu.GetCycles()) drive.diskette.PowerOff(c.a.GetCycles())
} }
} else if value && !c.power { } else if value && !c.power {
// Turn on // Turn on
@@ -222,7 +222,7 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
} }
drive := &c.drive[c.selected] drive := &c.drive[c.selected]
if drive.diskette != nil { 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 { if c.power && c.selected != selected {
// Selected changed with power on, power goes to the other disk // Selected changed with power on, power goes to the other disk
if c.drive[c.selected].diskette != nil { 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 { 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.q6 { // shift
if !c.q7 { // Q6L-Q7L: Read 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 } 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 } else { // load
if !c.q7 { // Q6H-Q7L: Sense write protect / prewrite state if !c.q7 { // Q6H-Q7L: Sense write protect / prewrite state
@@ -286,7 +286,7 @@ func (c *CardDisk2) processQ6Q7(in uint8) {
/* /*
if c.dataLatch >= 0x80 { 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())
} }
*/ */
} }

View File

@@ -166,7 +166,7 @@ func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
} }
func (c *CardDisk2Sequencer) catchUp(data uint8) { 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) motorOn := c.step(data, true)
if motorOn && currentCycle > c.lastCycle+disk2CyclestoLoseSsync { if motorOn && currentCycle > c.lastCycle+disk2CyclestoLoseSsync {

View File

@@ -84,7 +84,8 @@ func (a *Apple2) executeCommand(command command) {
a.cg.nextPage() a.cg.nextPage()
fmt.Printf("Chargen page %v\n", a.cg.page) fmt.Printf("Chargen page %v\n", a.cg.page)
case CommandToggleCPUTrace: case CommandToggleCPUTrace:
a.cpu.SetTrace(!a.cpu.GetTrace()) a.cpuTrace = !a.cpuTrace
a.cpu.SetTrace(a.cpuTrace)
case CommandReset: case CommandReset:
a.reset() a.reset()
case CommandComplex: case CommandComplex:

View File

@@ -30,7 +30,7 @@ func testWoz(t *testing.T, sequencer bool, file string, expectedTracks []int, cy
tracksMayMatch := len(tt.quarterTracks) >= expectedLen && tracksMayMatch := len(tt.quarterTracks) >= expectedLen &&
tt.quarterTracks[expectedLen-1] == expectedTracks[expectedLen-1] tt.quarterTracks[expectedLen-1] == expectedTracks[expectedLen-1]
return tracksMayMatch || a.cpu.GetCycles() > cycleLimit return tracksMayMatch || a.GetCycles() > cycleLimit
} }
at.run() 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("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 ( const (

View File

@@ -27,7 +27,7 @@ type memoryManager struct {
physicalExtAltRAM []*memoryRange // 0xd000 to 0xdfff, 4Kb. Up to 256 banks. physicalExtAltRAM []*memoryRange // 0xd000 to 0xdfff, 4Kb. Up to 256 banks.
// Configuration switches, Language cards // 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 lcActiveRead bool // Upper RAM active for read
lcActiveWrite bool // Upper RAM active for write lcActiveWrite bool // Upper RAM active for write
lcAltBank bool // Alternate lcAltBank bool // Alternate

View File

@@ -52,7 +52,10 @@ func getTracerFactory() map[string]*traceBuilder {
tracerFactory["cpu"] = &traceBuilder{ tracerFactory["cpu"] = &traceBuilder{
name: "cpu", name: "cpu",
description: "Trace CPU execution", 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{ tracerFactory["ss"] = &traceBuilder{
name: "ss", name: "ss",

View File

@@ -57,11 +57,9 @@ func (t *traceProDOS) inspect() {
t.dumpMLICall() t.dumpMLICall()
t.refreshDeviceDrives() t.refreshDeviceDrives()
t.callPending = true t.callPending = true
// t.a.cpu.SetTrace(true)
} else if t.callPending && pc == t.returnAddress { } else if t.callPending && pc == t.returnAddress {
t.dumpMLIReturn() t.dumpMLIReturn()
t.callPending = false t.callPending = false
// t.a.cpu.SetTrace(false)
} else if pc == biAddress { } else if pc == biAddress {
t.dumpBIExec() t.dumpBIExec()
} else if /*t.callPending &&*/ t.isDriverAddress(pc) { } else if /*t.callPending &&*/ t.isDriverAddress(pc) {
@@ -189,8 +187,6 @@ func (t *traceProDOS) dumpMLIReturn() {
default: default:
fmt.Printf("Ok\n") fmt.Printf("Ok\n")
} }
t.a.cpu.SetTrace(false)
} }
} }