Hallo Besucher, der Thread wurde 7,5k mal aufgerufen und enthält 58 Antworten

letzter Beitrag von Zirias/Excess am

Mit Branch-Befehl eine Subroutine abbrechen - okay oder böse?

  • ...man braucht genau 4 NOPs (oder eben andere 2-cycle Instruktionen wie bei dir, eben an genau der richtigen Stelle) um einen 7-cycle Jitter "aufzufangen". Wenn ich jetzt keinen Denkfehler habe sollten 3 NOPs sogar reichen WENN man garantiert keine "unintended Opcodes" verwendet -- die dokumentierten Opcodes brauchen maximal 7 cycles, damit ist die Wartezeit auf den IRQ irgendwo zwischen 8 (letzter Cycle einer Instruktion + eben 7 Cycles nächste Instruktion) und 2 (zwei letzte Cycles einer Instruktion) Cycles, der maximale Jitter also nur 6 Cycles...

    Ich erinnere mich an eine krasse Diskussion auf der Csdb. Ich glaube, ein Teil eines Branches vor so einem Befehl kann auch noch mit dazuzählen.

  • "If the IRQ occurs in the _last_ cycle of any instruction, the CPU will also execute the next one!"

    Das ist mir bekannt und darauf beruht auch meine Berechnung weiter oben. Der VIC-II läuft ja synchron mit dem 6510, damit kann ein Raster-IRQ immer nur zu Beginn eines Cycles anliegen. Die längstmögliche Wartezeit bis zur Behandlung ist also ein Cycle der aktuellen Instruktion + 8 für die nächste, weil manche unintended Opcodes so lange brauchen, also 9 Cycles. Die kürzeste Wartezeit ist 2 Cycles, entweder die beiden letzten einer Instruktion oder der IRQ liegt genau zu Beginn einer 2-cycle Instruktion an. Macht dann also einen maximalen Jitter von 9-2 = 7 cycles. Mit nur dokumentierten Opcodes (maximal 7 Cycles) käme ich mit dieser Rechnung auf einen maximalen Jitter von 6.


    Werde mir aber den Thread mal ansehen, ob es da noch andere Effekte geben kann :)

  • Vielleicht geht es auch nur noch um das sprachliche Problem, dass eine Verzögerung bis 7 cycles 8 verschiedene Verzögerungen bedeutet...

    Ich glaube da bin ich tatsächlich reingefallen! Also mit nur dokumentierten Opcodes hätte man bei einem Jitter von 6 entsprechend 7 verschiedene Werte :) Allerdings ist es vielleicht nicht mein einziger Denkfehler (oder ich habe "aus Versehen" Recht?) -- denn auch damit komme ich darauf, dass 3 NOPs an genau der richtigen Stelle reichen, wenn man auf unintended Opcodes verzichtet: Damit die Slide tut, was sie soll (Jitter auf 1 reduzieren), darf der IRQ frühestens 2 Cycles vor dem ersten NOP auftreten (dann wird gar keines ausgeführt) und spätestens direkt vor dem letzten NOP (dann werden alle NOPs ausgeführt). Bei 3 NOPs wären das genau 7 mögliche Zeitpunkte, die dann garantiert zu einer Wartezeit von 2 oder 3 Cycles bis zur IRQ-Behandlung führen.


    edit: so, jetzt drehe ich völlig durch und behaupte: 3 NOPs reichen sogar MIT unintended Opcodes. Und zwar weil der IRQ sogar frühestens 3 Cycles vor dem ersten NOP auftreten darf. Das garantiert immer noch, dass die letzte Instruktion vor dem NOP ausgeführt wird (entweder es ist eine 2-Cycle Instruktion, die wird ausgeführt, weil der IRQ erst im letzten Cycle der vorhergehenden Instruktion anliegt, oder es ist eine Instruktion mit mehr Cycles, dann passt es sowieso). Nunja, ich schätze ich werde jetzt doch mal ein bisschen coden, um diese These zu überprüfen ;)

  • Tja, ich bin verwirrt -- die Praxis möchte meiner Theorie nicht zustimmen :( Folgender Testcode:


    Die Cycles in den Kommentaren summieren auf an welcher Stelle der Rasterzeile wir im spätesten Fall sind. Damit sollte der zweite IRQ frühestens direkt vor dem tsx auftreten können (PAL). Es sollte also auch noch gehen, wenn das ganze ein Cycle langsamer wird (z.B. das inc $2 durch drei mal nop ersetzen), damit sollte der IRQ ja frühestens im zweiten Cycle von lda #<isr auftreten. Tja, tut es aber nicht, wenn man diese Änderung macht passiert Müll :(


    So wie es jetzt ist braucht man tatsächlich 4 nops. Man kann trotzdem eines loswerden mit einem anderen Trick: Wenn man das txs an den Anfang von isr_stab zieht. Das txs braucht selbst 2 Cycles und wird dann gegebenenfalls doppelt ausgeführt (vor UND nach dem 2. IRQ) -- die erste Ausführung schadet nichts ;)

  • "Müll" heißt, dass die erste Routine 2mal durchlaufen wird, weil das lda#<ISR nicht durchlaufen wird? Aber das würde doch auch "nur" A/X im Hauptprogramm kaputtmachen, was Dir gar nicht auffallen dürfte?
    Oder fällt gar manchmal das TXS aus?


    Für eine Testroutine hätte ich was Konservativeres programmiert, eher sowas:

  • "Müll" heißt, dass die erste Routine 2mal durchlaufen wird, weil das lda#<ISR nicht durchlaufen wird? Aber das würde doch auch "nur" A/X im Hauptprogramm kaputtmachen, was Dir gar nicht auffallen dürfte?

    Nö, das würde auch dafür sorgen, dass $d012 "wandert", weil es ja eventuell nicht korrekt decremented wird. Ich müsste jetzt nochmal genau nachsehen, was für ein "Müll" passiert, aber ich glaube es war genau das. Ist "fatal", denn damit:

    Oder fällt gar manchmal das TXS aus?

    passiert natürlich AUCH das (TSX natürlich) weil das ja nach dem LDA steht. Das ist Murks, fällt in meinem Code aber nicht auf, weil X nirgends modifiziert wird :( Da muss ich nochmal ran um irgendwas zu zeigen.

  • Dafür, dass ich bloß eine einfache Frage gestellt habe, lerne ich hier eine Menge über Assembler. :roll2:


    Bin mal gespannt, wie das wird, wenn ich meine nächste Frage stelle.

    Stell mal ne Grafik-Frage, dann kriegen wir 64 Farben :Peace

  • Hmm der maximale Jitter ist tatsächlich 8. Habe jetzt endlich alle (?) Infos gefunden, die mir gefehlt haben, und zwar bei den "Kollegen" vom NES: https://wiki.nesdev.com/w/index.php/CPU_interrupts :)


    Es ist also so, dass in der Regel der letzte Cycle einer Instruktion prüft, ob ein internes Flag gesetzt ist, dieses Flag wird dann gesetzt wenn am Ende des vorhergehenden Cycles von außen der IRQ anliegt. Damit dauert es in einer "NOP-slide" 2 oder 3 Cycles, bis der IRQ wirklich behandelt wird (entweder der erste Cycle eines NOP ist Cycle 0 der Rasterline, dann ist im zweiten Cycle das interne Flag gesetzt, oder andernfalls ist das Flag erst im ersten Cycle des nächsten NOP gesetzt und wird in dessen zweiten Cycle geprüft). Einfacher ausgedrückt: im ungünstigsten Fall muss man ein Cycle plus die Cycles der nächsten Instruktion warten, bis der IRQ behandelt wird.


    Es sind aber, wie man auch auf der Seite nachlesen kann, die Branch-Befehle, die zusätzlich in die Suppe spucken, so wie von @Hoogo schon angedeutet :) Diese testen auf einen Interrupt immer im zweiten Cycle, wenn der Branch aber "genommen" wird folgt danach noch ein dritter Cycle. Wenn also der VIC-II im zweiten Cycle eines Branch einen IRQ anlegt ist das interne Flag erst im dritten Cycle gesetzt und wird dort nicht mehr bemerkt. Damit ist die maximale Verzögerung bei einem unintended Opcode mit 8 Cycles ganze 10 Cycles, wenn direkt davor ein Branch genommen wurde.


    Ich habe mein Progrämmchen von oben mal zuende geschrieben (Korrektur des 1-Cycle Jitter in der zweiten Rasterzeile hinzugefügt), die Instruktionen so umsortiert, dass bei nur 2 Cycles Wartezeit alle jetzt 3 weißen Linien genau untereinander sind, und die Kommentare ergänzt, so dass in jeder Zeile steht, in welchen Cycles der Rasterzeile diese begonnen werden können und wie viele Cycles sie brauchen. Außerdem verwende ich den schon erwähnten Trick, dass das TXS gleichzeitig als letztes "NOP" der slide fungiert, damit reichen 3 NOPs zur zuverlässigen Stabilisierung:


    Was mich immer noch erstaunt ist, dass nach meiner Rechnung das cmp $d012 entweder in Cycle #63 oder #64 begonnen wird, #63 sollte schon der neue Cycle #0 sein, also der, in dem sich der Wert von $d012 ändert, trotzdem ergibt da der Vergleich noch "equal" :nixwiss:


    Hier mal ein Screenshot bei maximaler Verzögerung des ersten IRQ:


    x.png


    Die Linien sind alle 6 Cycles lang (plus Ghost dot), so sieht man schön, dass die oberste Linie ganze 8 Cycles "zu spät" startet.


    Hänge mal das PRG assembliert nach $C000 (sys49152) an.

  • Jungs, euren Anstrengungen alle Ehre aber das hatt doch alles nichts mehr mit der urspruenglichenchen Frage zu tun.
    Daher bitte ich an dieser Stelle mal um Verschiebung in einen eigenen Fred.


    ansonsten weiter so. ;)


    ich galaube hogo wars der die anwort gegeben hatt, einfach ein flag setzen und "immer" per rts zurueck.
    das flag auswerten und jumpen bnen beqn oder so und gut.


    was auch gehen wuerde weahre natuerlich eine jmp tabelle die je nach flag eine andere routine anspringt die dann
    wiederum mir rts endet.

  • @Haubitze da hast du schon recht, ich glaube allerdings auch, dass zum ursprünglichen Thema alles sinnvolle schon geschrieben wurde :) (inklusive Einordnung was eher "dreckig" ist und wann man das vielleicht tun kann und wann man es lieber lassen soll)


    Das Thema ist irgendwie "schrittweise abgedriftet", daher ist es wohl nicht so ganz einfach, es sinnvoll zu splitten, aber wenn das jemand tut habe ich sicher nichts dagegen :)

  • Was mich immer noch erstaunt ist, dass nach meiner Rechnung das cmp $d012 entweder in Cycle #63 oder #64 begonnen wird, #63 sollte schon der neue Cycle #0 sein, also der, in dem sich der Wert von $d012 ändert, trotzdem ergibt da der Vergleich noch "equal"

    Es wird ja auch nicht nach "equal" sondern auf ungleich geprüft. Und da CMP immer mit der nächsten Zeile vergleicht kommt es drauf an was LDA (im letzten Taktzyklus) gelesen hat. :search:


    Mit anderen Worten man könnte auch mit CMP # vergleichen und hätte dann nur 5 Takte Verzögerung bis Stabil.