Ladebefehle XY-Gelöst

Für diesen Beitrag wurde das CBM prg Studio verwendet.
weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

CBM prg StudioDie XY-Ladebefehle

Wir haben ja schon die beiden unmittelbaren Ladebefehle für das X- und Y-Register verwendet. Kommen wir hier zu den restlichen.

Dieser Beitrag baut auf dem vorherigen ‚Die Akku-Ladebefehle‚ auf und führt das dort entwickelte Programm weiter.

Unter Verwendung des bisher gelernten verändern wir zunächst unser Programm (farbige Pik-Symbole ausgeben) so, dass jetzt der komplette BS gefüllt wird.
Schleifen, die 255 Zeichen auf dem BS ausgeben, haben wir ja schon einige verwendet, aber wie können wir nun den gesamten BS mit Pik-Zeichen füllen? Der BS besteht aus 25 Zeilen mit je 40 Zeichen, also müssen wir 1000 Zeichen anzeigen. Da das X- / Y-Register nur 8-Bit breit ist, werden wir nur mit einem Register keine Schleife bauen können, die 1000mal durchlaufen wird. Also muss eine andere Lösung her. Bekanntlich führen viele Wege nach Rom, wir werden die Zero-Page verwenden um diese Schleife zu realisieren.

Nun aber zum ersten Code-Abschnitt.

Neu sind hier nur die beiden Variablen:

Diese beiden Variablen werden wir verwenden, um die Basisadresse des BS-Speichers und des Farb-RAMs in der Zero-Page abzulegen. Machen wir das doch nun einfach:

Hier laden wir das LSB der Adresse des BS-Speichers in den Akku. Das kennen wir schon vom letzten Beitrag, dort haben wir nur statt einer Variablen (SCREENRAM) ein Label (farbadr:) verwendet. Man kann also auch von Variablen das LSB und MSB bilden lassen.

Nun legen wir das LSB aus dem Akku an der in SCREENZEROADR hinterlegten Adresse ($FB) ab.

Da unsere Schleife wieder rückwärts laufen soll, müssen wir zur Startadresse des BS-Speichers noch den Speicherbedarf für drei weitere Pages addieren. Der Assembler des CBM Prg Studios wertet vor der Ermittlung des MSB (gilt natürlich auch fürs LSB, wenn wir dort etwas Rechnen) zuerst die vorhandenen Rechen-Operationen aus. Da jede Page, wie wir bereits wissen, 256 Bytes groß ist, müssen wir also 3*256 = $0300 addieren ($03 reicht wie eben erwähnt nicht!). Anschließend holt sich der Assembler das MSB ($07, da $0400 + $0300 = $0700).

Achtung: Ab CBM prg Studio 2.7.0 wurde dieses Verhalten verändert! Es läßt sich nun einstellen, was zuerst beachtet wird. Leider weicht der neue Standard vom bisherigen Verhalten ab, so dass es zu Fehlermeldungen beim Erstellen ‚alter‚ Sourcen kommt.
Benutzt ihr 2.8.0 oder höher, dann fügt bitte einfach ganz am Anfang des Programms die Assemblerdirektive Operator Calc  ein, damit ist das Programm unabhängig von den Optionen.
Näheres findet ihr unter ‚Fehler im CBM prg Studio 2.7.0‚.
Behaltet das Problem im Hinterkopf, es könnte euch noch an anderen Stellen im Tutorial begegnen. Bei Problemen, bitte einfach einen Hinweis an mich, dann passe ich die Stellen nachträglich an.

Als nächstes speichern wir das MSB vom Akku an die nächste Adresse auf der Zero-Page. Dazu addieren wir einfach 1 zur Variablen SCREENZEROADR und landen somit bei $FC.

Da das Farb-RAM nahezu identisch angesprochen wird, machen wir das ganze nochmal, nur mit anderen Variablen.

Jetzt müssen wir unseren Schleifen-Zähler initialisieren.

Im Source nichts Neues. Wir merken uns im X-Register die benötigte Page-Anzahl. Dann brauchen wir ein Label (pageloop:) zu dem wir für jede Page zurückspringen und im Y-Register merken wir uns die Anzahl der auszugebenen Zeichen für die jeweilige Page. Dies steht hinter dem Label (pageloop:), da wir bei jeder Page wieder die selbe Anzahl Zeichen ausgeben müssen.

Nachdem wir unser Zeichen in den Akku geholt haben, müssen wir dieses jetzt ausgeben.

STA indirekt Y-nach-indiziert ($91, 2B, 6T, <keine>)
Die indirekte Adressierung mit Y-Register kennen wir schon vom LDA-Befehl. Wie wir hier sehen gibt es auch eine STA-Variante. Sie funktioniert genauso: Es wird von der angegebenen Zero-Page-Adresse (hier $FB aus SCRENNZEROADR) die dort zufindene Adresse (hier $0700, beachtet dass wir rückwärts gehen und oben auf $0400 noch #$0300 addiert haben) geholt, dazu wird der Inhalt des Y-Registers addiert und dann an der so ermittelten Adresse der Inhalt des Akkus abgelegt. Da wir das Y-Register bei jedem Durchlauf verringern, füllen wir somit unsere letzte Seite von hinten nach vorne.

Statt wie bisher zwei getrennte Schleifen für Zeichen und Farbe zu verwenden, packen wir jetzt beides in eine. Daher laden wir oben unsere Farbe in den Akku und geben diese dann wieder mit dem eben gelernten STA-Befehl (indirekt Y-nach-indiziert) aus. Diesmal schreiben wir den Akku-Inhalt aber natürlich ins Farb-RAM.

Anschließend muss das Y-Register um eins verringert werden.

DEY: DEcrement Y-Register (den Inhalt vom Y-Register um 1 verringern)
DEY implizit ($88, 1B, 2T, NZ)
Der Befehl macht exakt dasselbe wie der DEX-Befehl. Nur eben mit dem Y-Register. Wir zählen hier also das Y-Register ums eins herunter, um im nächsten Durchlauf unser Zeichen und unsere Farbe an der nächst niedriegeren BS-Position auszugeben.

Wenn das Y-Register nicht 0 ist, dann springen wir zum Label (loop:) und geben das nächste Zeichen aus.

Kommen wir zum letzten Decrement-Befehl.

DEC: DECrement (Byte an der angegebenen Adresse um 1 verringern)
DEC Zero-Page ($C6, 2B, 5T, NZ)
Im Gegensatz zum DEX– und DEY-Befehl benötigt DEC eine Adresse, da wir hier direkt auf den Speicher zugreifen. Daher stimmt die obige Aussage auch nicht ganz, denn es gibt insgesamt vier Varianten des DEC-Befehls (die restlichen drei sehen wir uns später an). Hier verringern wir das MSB unserer Bildschirmspeicher-Adresse um 1.
Wie schon weiter oben beim STA-Befehl erklärt, finden wir an der Zero-Page Adresse $FB (SCREENZEROADR) die Adresse der letzten Page des Bildschirmspeichers ($0700). Da wir mittlerweile gute Fortschritte gemacht haben, wissen wir auch dass im Speicher erst das LSB (an $FB) und anschließend das MSB (an $FC, daher SCREENZEROADR+1) gespeichert wird. Also bezieht sich der DEC-Befehl auf die Zero-Page-Adresse $FC. Durch das DEC wird daher aus der Adresse $0700 jetzt $0600 und wir haben unsere nächste Page im Bildschirmspeicher.

Um die nächste Page im Farb-RAM anzusteuern benutzen wir den DEC-Befehl erneut, diesmal natürlich mit der Position des MSB der Farb-RAM-Adresse auf der Zero-Page. Danach verringern wir das X-Register und springen solange dies noch nicht Null ist wieder zum Label (pageloop:). Sind alle Pages verarbeitet worden, springen wir mit RTS zurück zum BASIC-Aufrufer.

Bevor wir endlich zu den Lade-Befehlen kommen, nochmal das komplette Listing:

 

Endlich haben wir es geschafft, unser Programm läuft!
Oder????

Da fehlt doch was...
Da fehlt doch was…

Hmmm, da scheint etwas nicht zu stimmen.
Das READY. ist klar, das kommt daher, dass unser Programm am Schluß zurück zum BASIC-Aufrufer springt. Aber die anderen vier Lücken, sollten nicht sein.

Habt ihr das Problem bereits erkannt?

Unser Programm soll ja nacheinander vier Pages füllen. Wir füllen jede Page, indem wir ins Y-Register #$FF schreiben und herunterzählen, bis das Y-Register Null ist. Somit wird unsere Schleife 255 mal durchlaufen, es fehlt also das letzte Byte.

Jetzt könnten wir natürlich zum Schluß, direkt hinter dem bne loop: das fehlenden Zeichen in die jeweilige Page schreiben:

Das wäre aber mit Kanonen auf Spatzen schießen.

Die Lösung ist viel einfacher. Wir schreiben zu Beginn jeder Seite einfach #$00 statt #$FF ins Y-Register. Die Prüfung, ob das Y-Register Null ist, findet ja erst nach dem DEY (s. oben) statt. Wenn jetzt beim ersten mal #$00 um eins verringert wird, erhalten wir #$FF (s. ‚Wie der Rechner rechnet‚) und unsere Schleife wird wie vorgesehen insgesamt 256 mal durchlaufen. Das hierdurch zuerst das erste Zeichen der Page (da das Y-Register ja den Wert #$00 hat) und anschließen alle restlichen (nach #$00 folgt wie beschrieben durchs DEY ja #$FF) ‚rückwärts‚ gesetzt werden stellt für uns kein Problem dar.

Wenn ihr das Programm jetzt startet läuft alles wie es soll.

2. ODER???
Unsere BS-Ausgabe sieht doch wie geplant aus. Also, was will der Typ dann noch?

Wer sich alles nochmal genau anschaut und überdenkt, kommt evtl. auf das noch offene Problem.
Es liegt im nicht sichtbaren Bereich hinter dem Bildschirmspeicher. Wir beschreiben 4 Pages mit je 256 Bytes (also 1024 Bytes) der BS-Speicher benötigt aber nur 1000Bytes, somit überschreiben wir 24 Bytes zu viel. Für unser Beispiel ist das kein Problem, aber hinter dem BS-Speicher folgen z. B. acht Register in denen der Speicherblock eingetragen wird, an dem die Spritedaten für den VIC-II zu finden sind. Da das Farb-RAM von uns identisch beschrieben wird, tritt dort natürlich ein ähnliches Problem auf.

Lösen wir das Problem einfach nach guter alter Brute-ForceinfoInfoEin Problem lösen, ohne auf die Effizienz zu achten. Es klappt dann zwar, ist aber evtl. nicht so schön.-Manier.
Beim ersten Lauf (dort wird ja die 4. Page gefüllt), schreiben wir einfach 24 Bytes weniger weg. Dafür ziehen wir von $FF $18 (=24) ab. Außerdem müssen wir, da nun das Zeichen an Y-Register = 0 nicht mehr geschrieben wird, dieses noch manuell machen und schließlich muss das Y-Register für alle anderen Seiten wieder auf #$00 gesetzt werden. Hier der dazugehöriger Code-Abschnitt (die Änderungen sind farbig hervorgehoben):

Unser Label (pageloop:) ist nun eigentlich überflüssig, aber da es nur ein Platzhalter für den Assembler ist, hat es auf unser fertiges Programm keine negativen Auswirkungen.
Wer kann mir denn jetzt noch sagen, warum wir auf das ldy #$00 verzichteninfoLösungBei jedem Durchlauf verringert dey den Ihnhalt des Y-Registers, erreicht es 0 springt bne loop: nicht mehr nach oben. Somit behält das Y-Register seinen Wert von 0 und wir können auf das erneute Setzen verzichten. können?

Die beiden neuen Label (pagecount: und charcount:) mit den BYTEs verwenden wir gleich für unsere Übungen.

Soviel (OK es war schon sehr viel) zu den Vorbereitungen.


Da die LDX– und LDY-Befehle nahezu identisch sind, stelle ich sie jeweils im Doppelpack vor.

Statt die #$04 direkt ins X-Register zu laden, verwenden wir nun:

LDX absolut ($AE, 3B, 4T, NZ)
Wir laden das X-Register mit dem Byte, dass an der angegebenen Adresse zu finden ist. Genau so, wie beim LDA mit absoluter Adressierung. Um unsere #$04 zu erhalten, müssen wir noch eins zur Adresse hinzuzählen.

Um unser Y-Register für den ersten Durchlauf zu füllen:

LDY absolut ($AC, 3B, 4T, NZ)
Macht das Selbe wie der LDX-Befehl von eben, nur mit dem Y-Register. Auch hier müssen wir etwas zur Adresse hinzuaddieren (+2) um auf unseren bisherigen Wert (#$E7 = #$FF-#$18) zu kommen.


Neben der absoluten Adressierung gibt es, wie beim LDA, auch eine indizierte Adressierung. Dabei kann das X-Register mit Hilfe des Y-Registers geladen werden und umgekehrt.
Ein Laden mit Hilfe des Akkumulators ist nicht möglich!

LDX absolut Y-indiziert ($BE, 3B, 4-5T, NZ)
Füllt das X-Register mit dem Wert, der an der angegebenen Adresse zzgl. dem Inhalt des Y-Registers zu finden ist. Auch das kennen wir schon vom LDA-Befehl. Normal werden 4 Taktzyklen benötigt, bei Überschreitung der Page-Grenze ist es einer mehr.

Um das Y-Register mit X-indiziert zu füllen müssen wir uns den Inhalt des X-Registers merken, schließlich haben wir uns eben mühevoll den richtigen Wert herausgesucht.

TXA: Transfer X-Register to Akku (Inhalt des X-Registers in den Akku kopieren)
TXA implizit ($8A, 1B, 2T, NZ)
Wir merken uns unseren im X-Register gespeicherten Wert, indem wir ihn in den Akku kopieren (OK, wir hätten oben auch direkt den Akku füllen können, aber schließlich ist das hier eine Übung). Es gibt insg. sechs dieser Transferbefehle. Sie dienen dazu den Inhalt verschiedener Register untereinander auszutauschen.

Nun können wir das X-Register verwenden, um mit seiner Hilfe das Y-Register zu setzen.

LDY absolut X-indiziert ($BC, 3B, 4-5T, NZ)
Macht das Gleiche wie sein LDX Gegenstück von eben. Es wird das Y-Register mit dem Wert, der an der angegebenen Adresse plus Inhalt des X-Registers zufinden ist, gefüllt. Auch hier gilt, beim Überschreiten der Page-Grenze sind es 5 Taktzyklen, sonst nur 4.

Damit unser X-Register wieder den gewünschten Wert enthält, müssen wir jetzt noch den Akku-Inhalt zurück ins X-Register kopieren.

TAX: Transfer Akku to X-Register (Inhalt des Akkus ins X-Register kopieren)
TAX implizit ($AA, 1B, 2T, NZ)
Alles wie beim TXA-Befehl, nur dass diesmal vom Akku zum X-Register kopiert wird.


Auch für die Index-Register besteht die Möglichkeit direkt den Inhalt einer Zero-Page-Adresse zu laden.

LDY Zero-Page ($A4, 2B, 3T, NZ)
Wir laden das Y-Register mit dem Wert (hier #$01), der an der angegebenen Zero-Page-Adresse (bei uns $FF) steht. Zum Vergleich evtl. noch mal beim LDA-Befehl nachschauen.

Für ein Beispiel mit LDX:

LDX Zero-Page ($A6, 2B, 3T, NZ)
Wir laden das X-Register mit dem Wert (hier #$02), der an der angegebenen Zero-Page-Adresse (bei uns $FF) steht. Also exakt das Gleiche, wie eben beim LDY-Befehl.


Kommen wir zu den letzten beiden Befehlen, es handelt sich sich um die indizierte Zero-Page-Adressierung.

LDX Zero-Page Y-indiziert ($B6, 2B, 4T, NZ)
Wir laden das X-Register mit dem Wert (hier #$02), der an der angegebenen Zero-Page-Adresse (bei uns $F0) plus dem Inhalt des Y-Registers (#$0F) steht, also wieder von der Zero-Page-Adresse $FF.

Das abschließende Beispiel für die LDY-Variante spare ich mir, oder? Wer mag kann ja an die Zero-Page-Adresse $FF eine #$01 schreiben, das X-Register mit #$0F füllen und dann den folgenden Befehl verwenden:

LDY Zero-Page X-indiziert ($B4, 2B, 4T, NZ)
Der macht wieder exakt das Gleiche, wie eben.


Lasst uns abschließend noch eine Tastaturabfrage einbauen, damit wir unsere Ausgabe in Ruhe betrachten können.
Fügt die nächsten Zeilen bitte am Programmende nach dem BNE pageloop: und vor dem RTS-Befehl ein.

SEI: SEt Interrupt-Flag (Interrupt-Flag setzen, auf 1 setzen)
SEI implizit ($78, 1B, 2T, I)
Wir setzen das Interrupt-Flag, um zu verhindern, dass unsere Tastatur-Abfrage evtl. durch eine andere, per Interrupt ausgelöste, gestört wird. Durch Interrupts können Programmfunktionen abhängig von Systemereignissen angesprungen werden. Sobald wir das Interrupt-Flag mit SEI setzen, wird kein Interrupt mehr ausgeführt.

Mit dem bekannten LDA-Befehl laden wir die zu prüfende Spalte der Tastaturmatrix in den Akku. Die gewünschte Spalte muss auf 0 gesetzt werden. Durch die binäre Schreibweise können wir im Source besser erkennen, welche Spalte wir gewählt haben. Da unsere RETURN-Taste in der Spalte 0 liegt, müssen wir diese auswählen.

STA absolut ($8D, 3B, 4T, <keine>)
Wir legen den Akku-Inhalt (unsere zu prüfende Spalte der Tastaturmatrix) an der Adresse $DC00 ab. Dies ist das erste Register des CIA-1, der Teil der Ein-/Ausgabe ist.

Der CIA-1 legt an der Adresse $DC01 die Info ab, welche Taste (der in $DC00 gespeicherten Spalte) gedrückt ist. Wir laden diesen Wert in den Akku, um gleich zu kontrollieren, ob unsere RETURN-Taste dabei ist.

AND: boolean AND (bitweises UND)
AND unmittelbar ($29, 2B, 2T, NZ)
Eine bitweise UND-Verknüpfung (s. Boolsche Algebra) mit dem Akku und dem übergebenen Wert durchführen. Ist das Ergebnis null oder negativ, so wird das entsprechende Flag gesetzt.
Im Akku steht jetzt in welcher Zeile, der zu prüfenden Spalte, eine Taste gedrückt wurde. Da die RETURN-Taste in der zweiten Zeile liegt, prüfen wir mit #%00000010. Wurde keine Taste gedrückt, so steht das entsprechende BIT auf 1, wurde eine gedrückt, nimmt das BIT Null an und unser AND-Befehl ergibt dann auch 0.

So lange das AND kein Ergebnis von 0 im Akku hinterlässt springen wir wieder zum Label (getkeyloop:) und warten weiter auf das Drücken der RETURN-Taste.

CLI: CLear Interrupt-Flag (Interrupt-Flag löschen, auf 0 setzen)
CLI implizit ($58, 1B, 2T, I)
Wir müssen natürlich die Interrupts wieder aktivieren. Dazu löschen wir mit CLI das Interrupt-Flag und schon sind die Interrupts wieder möglich.

Startet das Programm und ihr könnt unsere Ausgabe ungestört betrachten, bis ihr die RETURN-Taste drückt.


Wenn ihr bis hier durchgehalten habt, danke!

Ich weiß nicht wie es euch geht, aber ich kann keine Pik-Symbole mehr sehen! Daher werden die nächsten Befehle mit etwas anspruchsvolleren Beispielen erklärt.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (12 Bewertungen | Ø 5,00 von 5 | 100,00%)

Loading...


 

<<< zurück | weiter >>>

weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

8 Gedanken zu „Ladebefehle XY-Gelöst“

  1. „Wenn ihr bis hier durchgehalten habt, danke!“

    Ich hab zu danken, ich kapier es 🙂 Ok, ich habe auch etwas Erfahrung im x86 Assembler. Aber das Ganze hier verdeutlicht, wie dämlich die Idee ist einen 8 Bit Prozessor zu bauen, der nicht mit seinen Registern den eigenen Addressbus abdecken kann 😀 Ich mache die Beispiele immer schön mit. Diese Zerstückelung von wegen: „Addresse in den Ram legen um mit dem Ram den Ram zu addressieren“, … ich sags mal so, ich hoffe mal, dass die Verwirrung mit der Zeit nachlässt.

    1. Freut mich, wenn es etwas geholfen hat.

      Geringfügig besser wird es mit dem 65816 aus der SuperCPU, der verwendet alle 256 möglichen BYTES für Befehle und bietet dann eine Reihe neuer Adressierungsarten.

  2. „Endlich haben wir es geschafft, unser Programm läuft!
    Oder????“

    Leider nein. Ich bekomme die folgenden Errors und weiß nicht, was ich dagegen tun kann:

    [Error ] Line 19:Invalid operand, label or variable „lda #>SCREENRAM+$0300 ;auf der letzten Seite beginnen“ – D:\projekte\games\C64\NewProject\test2.asm
    [Error ] Line 25:Invalid operand, label or variable „lda #>COLORRAM+$0300“ – D:\projekte\games\C64\NewProject\test2.asm

    Ich bin gerade ein bisschen ratlos. Hoffentlich kannst du mir weiterhelfen, ich will nämlich mit diesem großartigen Tutorial fortfahren. 😉

    Cyber-Magische Grüße

    Jacob

    1. Hallo Jacob,
      danke für das ‚Großartig‘.

      Sehr wahrscheinlich verwendest du das CBM prg Studio 2.7.0 oder höher.

      Dort wurde leider das Vorgehen für die Berechnung der Hi-/Lo-Werte geändert (s. CBM prg Studio 2.7.0).
      Ab 2.8.0 kannst du das Problem mit der Assemblerdirektive ‚Operator Calc‘ umgehen, sonst hilft nur das Umstellen der Optionen (s. Link).

      Ich habe oben auch noch einen Hinweis hinzugefügt.

      Gruß,
      Jörn

  3. Hallo

    Ich habe grossen Spass daran, diese Seiten durchzulesen und mich in die Assemblerprogrammierung des C64 einzuarbeiten.

    Ich habe dabei einen kleinen Fehler gefunden: Zwischen dem „Oder????“ und dem „2. Oder????“ wird einige Male auf das X-Register verwiesen, das angeblich von $FF auf Null zählt. Tatsächlich übernimmt das Y-Register diese Aufgabe.

    Gruss
    Thomas

    1. Hallo Thomas,
      da hast du recht, im X-Register wird die Page gezählt, Y zählt die einzelnen Zeichen.

      Vielen Dank für den Hinweis, das wurde eben korrigiert.

      Gruß,
      Jörn

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Protected by WP Anti Spam