von HiassofT » Sa 18. Sep 2010, 02:00
Mit etwas Verspätung nun die Auflösung des Highspeed SIO / Pokey Divisor 0 Rätsels:
Zuerst fasse ich noch mal die bisherigen Erkenntnisse zusammen:
- Ein Byte muss mindestens 141 Zyklen lang sein, damit der Pokey es verarbeiten kann
- Die bisherigen Tests haben ergeben, daß der Code genau diese 141 Zyklen Zeit hat um das Byte entgegenzunehmen (Byte lesen, IRQ zurücksetzen, IRQ wieder einschalten - das ist der kritische Teil, die Verarbeitung des Bytes kann auch danach erfolgen)
- Meine Tests haben aber gezeigt, daß es mit einem 93 Zyklen VBI (korrekte Berechnung dann max. 133 Zyklen für den kritischen Pfad) klappte, mit einem 94 Zyklen VBI (insgesamt max. 134 Zyklen im kritischen Pfad) aber nicht. Dabei sei noch angemerkt, daß die Fehler bei insgesamt 134 Zyklen sehr selten waren, so etwa ein Fehler alle 20-60 Minuten. Im Fehlerfall fehlte ein Byte und die SIO rannte in den (7 Sekunden) Timeout. Ein Alptraum zum Testen...
- Irgendwo gehen da also 8 Zyklen ab....
Vor knapp 3 Wochen dachte ich mir "schaust Dir das kurz mal genauer an". Hätte ich vorher gewusst was dabei rauskommt (eine Woche extrem intensive Arbeit), hätte ich es wahrscheinlich bleiben lassen. Hier nun die Geschichte dazu:
Mit ziemlich viel Mühe habe ich es geschafft, einen Testcase zu finden (SIO bei einer bestimmten Scanline gestartet), in dem der Fehler etwa alle 20-200 Zugriffe auftrat (besser als 20-60 Minuten).
Durch einige Überlegungen konnte ich den Timeout auf 2 VBIs (also 0.1 Sek.) verringern, das gab mir die Chance mit dem Logik-Analyzer mitzuprotokollieren was genau schiefging. Analyzer geclockt durch PHI2, Trigger extern, ausgelöst im Timeout-Fall (der Einfachheit halber hab' ich da ein STA $D600 gemacht und den ext. Trigger an den 6-er Ausgang am '138 im Atari gehängt), Pre-Trigger auf Maximum.
Das Ergebnis war aber alles andere als das was ich erwartet hatte. In erster Linie zweifelte ich an der Funktionstüchtigkeit des Analyzers, in zweiter Linie an meiner geistigen Gesundheit (oder meiner Fähigkeit die Daten richtig zu interpretieren). Glücklicherweise war's aber weder das eine noch das andere, sondern der Pokey war wieder mal schuld :-)
Ich hatte erwartet, daß alle 141 oder 142 Zyklen ein Interrupt (Byte empfangen) auftritt. Gesendet hatte ich mit 125494 bit/sec, das wären also etwa 141.3 Zyklen pro Byte.
Die Interrupts waren aber nicht regelmässig sondern traten im Abstand 134, 148, 142, 134, 148, 142, ... auf (mit gelegentlichen kleinen Abweichungen).
Werte von 141-143 hätte ich ja noch erwartet, aber 134 war doch deutlich zu wenig. Sehr seltsam diese Sache.
Ich vermutete, daß der Pokey sich bei Byte-Streams evtl. anders verhält als bei einzelnen Bytes, also schrieb ich ein neues Testprogramm:
http://www.horus.com/~hias/tmp/pokeytest-1.2.zipDas Ergebnis war äusserst interessant:

- 800xl-2-3-byte.jpg (71.97 KiB) 3648-mal betrachtet
Im ersten Test (byte 1 len vs. irqst byte 2) testete ich, wann der Empfang des 2. Bytes signalisiert wurde, in Abhängigkeit von der Länge von Byte 1 (in Zyklen).
Ergebnis: Normalerweise in Zyklus 143 (nach Beginn des 2. Bytes), ausser das 1. Byte ist exakt 141 Zyklen lang, dann wird der Empfang 7 Zyklen früher, in Zyklus 136 signalisiert!
Als nächstes dann das Ergebnis des 3. Tests: hier habe ich überprüft, zu welchem Zeitpunkt der Pokey die Bits im 2. Byte sampelt: normalerweise im Zyklus 12 von 14 jedes Bits, ausser das 1. Byte war 141 Zyklen lang, dann ist ebenfalls das Sampling um 7 Zyklen nach vorne verschoben.
Der 2. Test war dann eine etwas schräge Sache, aber ich wollte es einfach wissen :-)
Hier habe ich überprüft, ob das 2. Byte evtl. kürzer als 141 Zyklen sein kann, und trotzdem das 3. Byte richtig empfangen wird (bzw wann genau der Empfang des 3. Bytes gemeldet wird). Das 1. Byte habe ich 141 Zyklen lang gemacht und dann beim 2. Byte 133, 134 und 135 Zyklen getestet.
Ergebnis: 133 Zyklen für Byte 2 klappte erwartungsgemäß nicht. Aber mit 134 Zyklen funktionierte es, und der Empfang des 3. Bytes wurde ebenfalls 136 Zyklen nach Beginn des 3. Bytes gemeldet. War das 2. Byte 135 Zyklen lang, wurde der Empfang nach den (normalen) 143 Zyklen gemeldet.
Anmerkung: ja, das heisst man könnte den Pokey geringfügig "übertakten" (134 Zyklen ab Byte 2 statt 141), aber sobald man nur geringfügig abweicht geht das alles schief - der Sender müsste mit dem PHI2 vom Atari synchronisiert werden.
Die absoluten Zyklen-Werte sind für die Highspeed Routine nicht so wichtig (IRQ kommt ja 2 Zyklen nach Ende des Bytes), releavant sind die Abstände zwischen den IRQs.
Betrachtet man diese Werte, und die Ergebnisse bisher, machen die Beobachteten Abstände wieder Sinn:
- War das 1. Byte 141 Zyklen, kommt der IRQ des 2. im Abstand 134 Zyklen
- Das 2. Byte ist nun aber nicht 134 Zyklen lang (sondern 141 oder 142), deshalb kommt der 3. IRQ 14 Zyklen später, also im Abstand 148
- Das 3. Byte ist nun zB 142 Zyklen lang (141.3 aufsummiert und gerundet), also Abstand 142
Kurzum: man muss damit rechnen, daß man bei Divisor 0 nicht 141 sondern nur 134 Zyklen Zeit hat.
Schön und gut, aber hatte ich nicht geschrieben, daß meine Worst-Case Berechnung von 134 Zyklen zu Fehlern führte? Ja, habe ich, da geht immer noch irgendein Detail ab.
Das liess mir natürlich auch keine Ruhe, und was nun folgt ist wirklich schräg (beim Pokey wundert mich eh schon nichts mehr, der ist aber auch noch kaum erforscht):
Normalerweise startet die CPU ja den NMI (oder IRQ) sobald sie mit einem Befehl fertig ist (mit einer Verzögerung von 2 Zyklen, so lange muss IRQ bzw NMI auf Low sein bevor die CPU den Interrupt erkennt).
Im Logic-Analyzer Trace sah ich aber, das die CPU den "BNE" in der "IRQST check Schleife" begann, dann ging im nächsten Zyklus NMI auf Low und eigentlich sollte die CPU 2 Zyklen später (am Ende des 3-Zyklen BNE) mit der NMI Bearbeitung beginnen. Tat sie aber nicht, sonder führte gemütlich den nächsten Befehl aus.
Dieses Verhalten war mir bisher gänzlich unbekannt, und wie sich herausstellte auch so gut wie allen anderen (nur ein paar wenige waren zuvor darüber gestolpert, das war aber nirgendwo dokumentiert).
Details dazu gibt's in diesem Thread auf 6502.org:
http://forum.6502.org/viewtopic.php?t=1634Das Testprogramm (inkl. IRQ Test, der ist aber durch das knappe Timing nicht 100% exakt, kann also immer "1 instructions delay" ausspucken) gibt's hier:
http://www.horus.com/~hias/tmp/inttest-1.0.zipDiese Interrupt-Verzögerung tritt nur genau dann auf, wenn ein Branch innerhalb der gleichen Page genommen wird (der Befehl also genau 3 Zyklen braucht) und der Interrupt im 2. Zyklus des Branches auftritt (also der Interrupt direkt nach dem Branch ausgeführt werden sollte). Nicht genommene Branches, Branches zu einer anderen Page oder alle anderen Befehle die evtl. einen Zyklus mehr brauchen wenn eine Page-Grenze überwunden werden muss sind nicht betroffen.
Durch diese neue Erkenntnis machte nun auch der Logic Analyzer Trace Sinn, und der Worst-Case sieht nun so aus:
- Code: Alles auswählen
CEC2 D0 F9 BNE $CEBD ; 3
; NMI occurs at cycle 2 of BNE, should be started after BNE (scanline cycle 10)
CEBD 2C 0E D2 BIT $D20E ; 4
; IRQ occurs here
; NMI code executed ; 94
CEC0 10 DC BPL $CE9E ; 2
CEC2 D0 F9 BNE $CEBD ; 3
CEBD 2C 0E D2 BIT $D20E ; 4
CEC0 10 DC BPL $CE9E ; 2
CEC2 D0 F9 BNE $CEBD ; 2
CEC4 AE 0D D2 LDX $D20D ; 4
CEC7 A9 DF LDA #$DF ; 2
CEC9 8D 0E D2 STA $D20E ; 4
CECC A9 A0 LDA #$A0 ; 2
CECE 8D 0E D2 STA $D20E ; 4
; sum = 123
Diese 122 Zyklen sind reine CPU Zyklen, ohne die "gestohlenen" Refresh Zyklen. Berechnet man die auch noch mit ein kommt man auf folgendes:
Der NMI Code beginnt in Scanline Zyklus 14, bekommt 9 Refresh Zyklen ab und dauert bis inkl. Zyklus 2 der nächsten Scanline.
Das erste "STA $D20E" würde in Zyklus 25 ausgeführt werden, da gibt's aber den 10. Refresh also dauert es bis Zyklus 26.
"LDA #$A0" beginnt in Zyklus 27 und dauert bis Zyklus 28. Ausnahmsweise kein Refresh hier :-)
Der 11. Refresh kommt aber nun in Zyklus 29, bevor das 2. "STA $D20E" ausgeführt wird.
Dieses "STA $D20E" startet in Zyklus 30, in Zyklus 33 würde der Wert in den Pokey geschrieben werden, aber hier kommt der 12. Refresh und der Wert wird erst in Zyklus 34 (135 Zyklen nach Empfang des Bytes geschrieben).
Peng, da haben wir's, 123 Zyklen plus 12 Refresh macht insgesamt 135 Zyklen, nicht 134 wie vorher berechnet.
Ohne die NMI Verschiebung bzw mit einem Zyklus weniger im NMI schafft's der Code in 133 Zyklen, es ist der zusätzliche Refresh, der alles vermasselt :-)
In der Praxis ist das Auftreten dieses Worst Case extrem unwahrscheinlich. Der NMI müsste bei meinem Code wirklich 94 Zyklen brauchen (braucht er aber nie, maximum ist beim OldOS mit allen Zählern übergelaufen 93 Zyklen), dann muss der NMI genau zum richtigen Zeitpunkt eintreffen und das Byte zuvor muss exakt 141 Zyklen lang gewesen sein. Alles in allem äusserst unwahrscheinlich, aber trotzdem sehr beruhigend zu wissen, daß mein Code auch in diesem Worst Case 100% stabil funktioniert :-)
so long,
Hias
PS: ich teste zZt noch meinen neuen Highspeed Code, sobald ich damit fertig bin und die Anleitung angepasst habe gibt's ein Update hier im Forum.