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
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

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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()

View File

@ -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())
}
*/
}

View File

@ -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 {

View File

@ -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:

View File

@ -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 (

View File

@ -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

View File

@ -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",

View File

@ -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)
}
}