- Never forget, I may have taught you everything you know, but I have not taught you everything I know.
- -- Kitty Montgomery, "Dharma & Greg", Season 1, Episode 8: "Mr. Montgomery Goes to Washington"
Michael Steil hat mich darum gebeten, folgendes für sein Blog
"Pagetable" zu beschreiben. Da ich aber auch gerne solche Sachen in meinem eigenen Blog veröffentliche, gibt es hier eine deutschsprachige Version meines
Beitrags dort.
Im Folgenden geht es wieder um den 6502 Prozessor, diesmal um folgende Aufgabenstellung: es sollen abhängig vom Wert eines Registers, in diesem Fall des X-Registers, eine bestimmte Anzahl von Taktzyklen "verschwendet" werden. Im Prinzip läuft es nach folgendem Schema: man schreibt eine lange Reihe von Befehlen, die nichts bewirken in den Speicher. Je weiter "vorne" man einspringt, desto mehr Taktzyklen werden benötigt, um zum eigentlichen Code zu kommen. Springt man weiter "hinten" ein, um so schneller ist man beim eigentlichen Code angekommen.
So schön diese Theorie auch ist, sie krankt beim 6502 am folgenden Umstand: der Prozessor benötigt mindestens zwei Takte pro Befehl. So wird das mit einer feineren Granulierung schon um einiges schwieriger. Die erste Hälfte dieses Tricks habe ich in Code von
Eckhard Stollberg gefunden, der als Atari 2600 VCS Homebrew Urgestein gilt. Dort habe ich folgende Bytefolge gefunden:
C9 C9 C9 C9 C9 C9 C9 C9 C9 C9 C9 C9 C5 EA
Disassembliert man diese Bytefolge, so erhält man:
; CODE1
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP $EA ; 3
Um den kompletten Code zu durchlaufen, benötigt man also 15 Taktzyklen, und außer ein paar Prozessorstatusregister ändert sich nichts. Springt man ein Byte weiter "hinten" ein, so wird folgender Code abgearbeitet:
; CODE2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C9 ; 2
CMP #$C5 ; 2
NOP ; 2
Das macht 14 Taktzyklen und wieder werden nur die Prozessorstatusregister verändert. Springt man wieder ein Byte später ein, dann startet man bei CODE1 in der zweiten Zeile, ein weiteres Byte später bei der zweiten Zeile von CODE2, danach in der dritten Zeile von CODE1, und so weiter. So schafft man es also wirklich auf einen Taktzyklus genau zu bestimmen, wie viel Zeit "vergeudet" werden soll. Einzige Ausnahme: es handelt sich dabei um 2 + X Taktzyklen. Genau nur einen einzigen Taktzyklus zu "vergeuden" ist nicht möglich.
Jetzt muss noch die Frage geklärt werden, wie an den "Einsteig" unserer "Rutsche" gesprungen wird. Bei einem C=64 würde das mit selbstmodifizierendem Code gelöst werden. Der Operand des JMP $XXXX Sprungbefehls wird einfach mit der entsprechenden Adresse überschrieben. Beim 2600 geht das nicht, weil der Code grundsätzlich im ROM ausgeführt wird. Möglich wäre zum Beispiel JMP ($0080), einen indirekten Sprung, wobei die Adressen $0080 und $0081 das Ziel für den Sprungbefehl enthalten.
Mein Ansatz weicht an dieser Stelle etwas von der üblichen Vorgehensweise ab. RAM ist knapp auf dem Atari, und ich möchte nicht zwei der 128 Bytes RAM des Systems auf Dauer nur für diese eine Funktion "verbraten", wenn es auch anders geht: wenn die CPU auf einen JSR $XXXX (Sprung in Unterroutine) abarbeitet, legt er die Adresse, von der aus er kommt, auf dem Stack ab. Genau genommen ist es nicht die Adresse von der er kommt, sondern die Adresse + 2, was aber eigentlich die Rücksprungadresse - 1 ist. Und genau das mache ich mir hier zu nutze: ich schreibe einfach meinen Einsprungspunkt als Rücksprungsadresse - 1 auf den Stack und benutze den Befehl RTS (Rückkehren aus der Unterroutine) um den Sprung auszulösen. So brauche ich zwar immer noch zwei Byte im RAM, aber nur kurzzeitig, ohne dass ich mir Gedanken machen muss, ob ich die beiden Bytes nun gerade für was anderes brauche oder nicht.
; das X-Register beinhaltet wie viele der 15 möglichen Taktzyklen übersprungen werden sollen
LDA #>clockslide
PHA
TXA
CLC
ADC #<clockslide
PHA
STA WSYNC ; <= dieser Befehl wartet bis zum Beginn der nächsten Scanline
clockslide:
RTS
CMP #$C9
CMP #$C9
CMP #$C9
CMP #$C9
CMP #$C9
CMP #$C9
CMP $EA
realcode:
; hier geht es dann mit dem eigentlichen Code weiter
Ein Problem hat der Code noch: zwischen "clockslide" und "realcode" danach darf keine Seitenüberschreitung stattfinden, weil ich sonst das Highbyte auf dem Stack noch einmal nachträglich um eins erhöhen müsste. Da sich die Lage dieses Codesegments aber beim Erzeugen des Codes von mir beeinflussen lässt, habe ich diesen Klimmzug ausgelassen. Den kann gerne jemand anders basteln. ;-)
Kommentare
Dieser Artikel zeigt nicht nur einen gut ausgearbeiteten Trick auf, sondern ist auch noch gut erklärt.
Übrigens - wenn hier jmd. Demos oder sowas auf echten alten Computern für die Allgemeinheit laufen lassen möchte: Das Oldenburger Computermuseum (OL ist meine Heimat) hat jede Menge 8 Bit Computer und freut sich nicht nur über Hardware sondern auch Software zum Vorführen: http://www.computermuseum-oldenburg.de/
Da fühle ich mich ja doch ein wenig geadelt.
Ich plane übrigens über Ostern zur Revision Party nach Saarbrücken zu fahren, und dort auch ein bisschen 6502 zu coden. Vielleicht sieht man sich ja dort.