Der erste Schritt

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

Hier dreht sich alles um den Befehlssatz des 6510. Wie erwähnt gilt dieser auch für den 6502. In Zukunft werde ich nur vom 6510 reden und einzig bei Bedarf den 6502 erwähnen. Wir werden uns Stück für Stück den Befehlssatz erarbeiten.

Da der 6510 ein 8-Bit Prozessor ist, steht ihm je Befehl ein Byte zur Verfügung. Somit kommt man auf max. 256 unterschiedliche Befehle. Aber längst nicht jedes Byte steht für einen gültigen Befehl. Viele Befehle haben außerdem unterschiedliche Adressierungsarten, die unterschiedliche Bytecodes besitzen. So gibt es z. B. allein acht verschiedene „Versionen„ vom LDA-Befehl, je nachdem, wie die Adressierung vorgenommen wird. „Wirkliche„ Befehle gibt es nur 56 und außerdem noch einige undokumentierte.

Wir werden uns das ASM-Programm vom Basic-Vergleich noch mal anschauen.

Startet das CBM Program Studio (das sollte mittlerweile ja so wie im Video zu sehen eingerichtet sein) und gebt das folgende Programm ein.

Nehmen wir uns dieses kleine Programm mal Zeile für Zeile vor, zum Schluß gibt es noch ein Video in dem wir das Programm mit im CBM Program Studio um Kommentare und Variablen erweitern und einen Blick auf den Assembly Dump werfen.

Diese Angabe ist kein Befehl für den Prozessor, sondern teilt dem Assembler mit, an welcher Speicheradresse die nächsten Anweisungen stehen sollen. Dafür gebt ihr ein *=  gefolgt von der Adresse (ich benutze die Hex-Schreibweise, aber das CBM prg Studio versteht auch Dezimalzahlen) ein.
Ab $0800 beginnt zwar das BASIC-RAM (s. „Kleine Hardwarekunde“), wie ihr aber eben gesehen habt, wollen wir die nächsten BYTEs erst ab $0801 ablegen. Dies ist die Adresse, an der die erste BASIC-Zeile erwartet wird. $0800 ist für uns tabu! Dort muss immer eine 0 stehen, sonst kommt es zu einer Fehlermeldung, wenn man RUN  eingibt, um das Programm zu starten. Daher beginnen wir mit $0801.

BYTE ist auch kein Befehl, es ist ein Schlüsselwort für den Assembler. Es weist den Assembler an, die nächsten Bytes direkt im Speicher abzulegen. Hier generieren wir unsere BASIC-Zeile für den Programmstart. Wichtig ist, dass die BYTE-Anweisung nicht am Zeilenanfang steht, sonst wirft das CBM prg Studio einen Fehler.
Wer nach Laden des Programms mal LIST eingibt bekommt unsere Startzeile zusehen:

Auch wenn das CBM prg Studio diese BYTE-Zeile automatisch erzeugen kann, schauen wir uns die mal genauer an.

$0B, $08: Hier haben wir die Adresse der nächsten BASIC-Zeile. Bei uns gibt es zwar keine, aber wir müssen diese Adresse trotzdem angeben. Ausrechnen können wir die Adresse in dem wir zu unserer Startadresse $0801 (s. o.) die Anzahl der von uns für die BASIC-Zeile benötigten Bytes (inkl. den beiden Bytes für diese Adresse, aber ohne die drei $00 am Schluss) addieren.
Wie üblich wird bei einem Word (16-Bit-Wert, hier ist es unsere Adresse für die nächste BASIC-Zeile) das LSB zuerst im Speicher abgelegt, dann folgt das MSBhelpErklärungLSB = Least Significant Byte
MSB = Most Significant Byte
Die Groß-/Kleinschreibung ist wichtig, große Buchstaben stehen für BYTE, kleine für Bit.
. Also zeigen wir hier auf die Adresse $080B = $0801 + 10 Bytes.

$DD, $07: Das ist unsere Zeilen-Nr. als Hex-Zahl, da diese 16-Bit groß ist, wird auch hier wieder erst das LSB, dann das MSB im Speicher abgelegt. Wer $07DD ins Dezimalsystem umrechnet kommt auf unsere Zeilen-Nr. 2013.

$9E: Das nächste Byte steht für den BASIC-Befehl SYS, der BASIC-Interpreter weis dadruch zu welcher Routine erspringen muss. Diese Byte bitte nicht mit einem Opcode verwechseln!

$20, $32, $30, $36, $32: Diese fünf Bytes stehen einfach für den Text hinter dem SYS-Befehl. Wenn ihr eine PETSCII-Tabelle zu Rate zieht, kommt ihr auf folgende Zeichen:

$20 = Leerzeichen
$32 = 2
$30 = 0
$36 = 6
$32 = 2

$00, $00, $00: Diese drei Bytes definieren das Ende des BASIC-Programms. Sie beginnen bei unserer oben errechneten Adresse $080B in der eigentlich die nächste BASIC-Zeile steht. Aber durch diese drei $00 erkennt der Interpreter das Ende des BASIC-Programms und verhindert, dass es bei der Ausführung am Schluss evtl. einen Fehler gibt bzw. dass wir beim LIST Unfug angezeigt bekommen. Ohne dieses Ende würde unser Programm, das im Speicher ja direkt auf die erste BASIC-Zeile folgt, als weitere BASIC-Zeilen interpretiert werden.
Wenn man diese drei Bytes mal weg lässt, läuft unser Programm nicht mehr richtig:

Das Programm gibt das falsche Symbol aus und wird mit einem Fehler beendet.Nach einem LIST sehen wir auch warum...
Das Programm gibt das falsche Symbol aus und wird mit einem Fehler beendet.
Nach einem LIST sehen wir auch warum…

Leerzeilen dienen nur der Übersicht, diese könnt ihr je nach eurem Geschmack zur Formatierung des Programms einsetzen.

Hier ist es endlich, unser erstes Mnemonic: LDA (die Groß- & Kleinschreibung ist im CBM prg Studio übrigens egal).

LDA: LoaD Accumulator (lade / setze einen Wert in den Akku).
Um den Akku zu füllen gibt es, wie eingangs erwähnt, acht unterschiedliche Möglichkeiten. Die hier verwendete nennt sich unmittelbare Adressierung, weil wir einen festen Wert #$41 (für das Pik-Symbol) direkt in den Akku schreiben. Das #$ ist hier wichtig, vergesst ihr die Raute # dann erkennt der Assember $41 als Adresse! Bei einer unmittelbaren Adressierung benötigt LDA 2 Byte Speicher und die CPU braucht zwei Taktzyklen um den Befehl zu verarbeiten. Der eindeutige OpCode für LDA mit unmittelbarer Adressierung lautet $A9.  Außerdem werden das Negativ- und Zero-Flag beeinflusst.

In Zukunft gebe ich bei den Befehlen Opcode, Speicherbedarf, Taktzyklen und die betroffenen Flags in Kurzform an.
Hier lautet die Kurzform: LDA unmittelbar ($A9, 2B, 2T, NZ). Alle Details hierzu findet ihr auf der Seite Mnemonics.

LDX: LoaD X-Register (lade / setze einen Wert im X-Register / Indexregister-X)
LDX unmittelbar ($A2, 2B, 2T, NZ)
Der C64 besitzt zwei Indexregister die X– und Y-Register genannt werden. Diese Register werden häufig (so wie hier) für Schleifen verwendet, da sich deren Wert einfach verringern läßt, wie wir gleich noch sehen. Die Indexregister sind dem Akku von der Handhabung sehr ähnlich. Wir verwenden hier, wie beim LDA die unmittelbare Adressierung um im X-Register unsere gewünschte Schleifenanzahl #$FF (= 255) zu hinterlegen (auch hier wieder auf #$ achten).

Wir haben jetzt also unser Pik-Symblo im Akku und unsere Schleifenanzahl im X-Register. Als nächstes folgt der Hauptblock unseres Programms, wir geben die Zeichen mit einer Schleife auf dem BS aus.

loop: Ist wieder mal kein Assemblerbefehl, sondern ein Label. Diese Label werden vom Assembler verwendet um eine bestimmte Adresse im Speicher zu finden. Ob ihr hinter dem Labelnamen einen Doppelpunkt angebt oder nicht ist euch überlassen, der jeweilige Labelname muss aber überall gleich geschrieben werden. Ihr könnt das Label auch direkt vor den entsprechenden Befehl schreiben, ich empfinde das aber als unübersichtlich. Ich benutze den Doppelpunkt, um auf den ersten Blick zu erkennen, dass es sich um ein Label handelt und nicht um eine Variable (s. Video). Wir könnten Adressen auch selbst ausrechnen, dass ist aber sehr arbeitsaufwändig (wenn ihr Befehle hinzufügt verschieben sich die Speicherpositionen) und fehleranfällig (wie leicht vertipp man sich und schon sucht man ewig nach einem Fehler im Programm).
Hier kennzeichnet loop: den Beginn unserer Schleife. Solange die von uns gewünschte Anzahl an Durchläufen (siehe LDX) noch nicht erreicht ist, wollen wir immer wieder mit dem folgenden Befehl fortfahren.

STA: STore Accumulator (speichere den Inhalt des Akkus in…)
STA absolut X-indiziert ($9D, 3B, 5T, <keine>)
Zu Beginn unserer Schleife wollen wir das Pik-Zeichen auf dem BS ausgeben. Wir verwenden dafür STA, um den Inhalt des Akkus in die angegebene Speicherstelle (hier der BS-Speicher) zu schreiben. Adressen werden (wie oben erwähnt) im Format $03FF angegeben (hier also ohne #). Da wir nicht immer auf die selbe Adresse schreiben wollen, müssen wir noch dafür sorgen, dass wir nacheinander die von uns gewünschten Speicherzellen beschreiben. Das erreichen wir durch die Angabe von ,X hinter unserer Adresse $03FF. Damit weiß der Assembler, dass er die absolute Adressierung mit Hilfe des X-Registers nehmen soll. Wenn der Befehl abgearbeitet wird, addiert die CPU auf die feste Adresse (hier $03FF) den jeweils aktuellen Inhalt des X-Registers. Da wir dieses oben mit #$FF gefüllt haben und es gleich verringern werden, geben wir die Zeichen also ‚rückwärts‚ auf dem BS aus. Wir beginnen bei $03FF + #$FF = $04FE, dann $04FD, $04FC, usw. Das X-Register ist bei uns nie kleiner als 1 (s. unten), wenn STA ausgeführt wird. Somit erklärt sich jetzt auch für alle, die in der Hardwarekunde aufgepasst haben, wieso wir die Adresse $03FF verwenden, obwohl der BS doch bei $0400 beginnt: $03FF + 1 = $0400.

DEX: DEcrement X-Register (den Inhalt vom X-Register um 1 verringern)
DEX implizit ($CA, 1B, 2T, NZ)
Nachdem wir eben einen sehr ‚großen‚ und zeitaufwendigen Befehl hatten, folgt nun ein schön schlanker und einfacher. Der Befehl DEX zieht einfach vom Inhalt des X-Registers 1 ab. Es gibt nur diese eine Schreibweise, da hier keine Adressierungen möglich / nötig sind. Das Zero- und Negativ-Flag werden hier beeinflusst. Ist die Zahl nach dem DEX negativ, dann wird das N-Flag gesetzt (1), anderenfalls wird es gelöscht (0). Ist die Zahl genau 0, dann wird das Z-Flag gesetzt (1), sonst wird es gelöscht (0). Das führt uns direkt zum nächsten Befehl.

BNE: Branch on Not Equal (springe wenn nicht gleich zu…)
BNE relativ ($D0, 2B, 2-4T, <keine>)
Dieser Befehl ist wieder etwas komplexer. BNE ist mit einer IF <> THEN GOTO Anweisung aus BASIC vergleichbar. Unter Assembler gibt es insgesamt acht solcher bedingten Sprünge. Damit sind Befehle gemeint, die ein Statusregister prüfen und dann entweder zu einer anderen Speicherstelle springen oder mit der nächsten Anweisung fortfahren. Ob etwas gleich oder ungleich ist wird über das Zero-Flag geprüft (1 bedeutet gleich und 0 ungleich, zieht man z. B. zwei Zahlen von einander ab und das Ergebnis ist 0, dann sind diese Zahlen offensichtlich gleich). Dementsprechend springt BNE zur angegebenen Adresse, wenn das Zero-Flag gelöscht (Z=0), die letzte Anweisung also nicht gleich (bzw. null) ist. Da wir gelernt haben, dass eine Adresse 16-Bit lang ist, wundert ihr euch evtl. warum BNE nur 2 Byte Speicher benötigt. Das liegt daran, dass die sog. relative Adressierung verwendet wird. Der OpCode benötigt wie gewohnt ein Byte. Das zweite Byte wird für die relative Adressierung verwendet. Dabei gibt das vorzeichenbehaftete Byte an, um wieviele Bytes vor oder zurück gesprungen werden soll. Ihr könnt also max. um 127 Byte nach vorne (zu höheren Adressen) oder um -128 Byte zurück (zu niedrigeren Adressen) springen. Wer die Taktzyklen zählen möchte, der muss noch weitere Besonderheiten beachten. Wie ihr oben sehen könnt habe ich die Ausführungszeit mit 2-4T angegeben. 2T werden benötigt, wenn der Befehl nicht springen muss und es einfach mit der nächsten Einweisung weitergeht. Muss ein Sprung ausgeführt werden, dann werden 2T+1T benötigt, erfolgt der Sprung über eine Page(Seiten)-Grenze, dann wird ein weiterer Zyklus benötigt, also 2T+1T+1T. Solltet ihr euch jetzt fragen, was springen eigentlich bedeutet, nun das ist ganz einfach. Damit ist nichts Anderes gemeint, als den Programcounter (PC / Programmzähler) auf die angegebene Zieladresse zusetzen, damit es mit dem dortigen Befehl weitergeht.

Jetzt haben wir es fast geschafft, es folgt der letzte Befehl aus unserem Programm.

RTS: ReTurn from Subroutine (springe aus der Unter(Hilfs)-Funktion zurück zum Aufrufer)
RTS implizit ($60, 1B, 6T, <keine>)
Der Befehl springt zu der aktuell auf dem Stack zu findenen Adresse (implizite Adressierung) zurück. Normalerweise wird diese Adresse vom JSR-Befehl (folgt später) auf dem Stack abgelegt. Wie ein Blick aufs Listing verrät, gibt es den in unserem Programm aber nicht. Bei uns steht dort eine Rücksprung-Adresse zum BASIC, die beim Programmstart auf dem Stack abgelegt wurde. Wir landen nach dem RTS also wieder mit dem blinkenden Cursor in der Eingabe des C64. Da RTS einfach die aktuelle Adresse vom Stack nimmt, ist es immens wichtig, dass wir dafür sorgen, dass der Stack die richtige Adresse enthält. Damit beschäftigen wir uns später beim JSR-Befehl.


So das wars fürs Erste an Befehlen. Ihr solltet damit etwas experimentieren. Keine Angst, der C64 nimmt keinen Schaden, falls ihr mal Kraut und Rüben produziert. Es kommt höhstens mal vor, dass ihr Reset drücken oder den Rechner gar ausschalten müsst.

Als nächstes könnt ihr euch im folgenden Video ansehen, wie der Code durch Kommentare lesbarer und durch Variablen flexibler wird. Außerdem erklärt das Video den Assembly Dump des CBM Program Studios.

Sobald ihr die Schritte aus dem Video nachvollzogen habt, sollte euer Programm ungefähr so aussehen:

 

Ist euch oben der kleine Unterschied zum Video aufgefallen?


Schrott!!Naja...Geht so...Ganz gut...SUPER! (23 Bewertungen | Ø 4,83 von 5 | 96,52%)

Loading...


 

<<< zurück | weiter >>>

 

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

15 Gedanken zu „Der erste Schritt“

  1. Pingback: Linksammlung C64
  2. „[…]Der Befehl DEX zieht einfach vom Inhalt des X-Registers 1 ab.[…]
    Ist die Zahl genau 0, dann wird das Z-Flag gesetzt (1), sonst wird es gelöscht (0). “

    //Also ist X=1, dann DEX …, dann ist X=0 und Z-Flag=1 ?

    „[…]Ob etwas gleich oder ungleich ist wird über das Zero-Flag geprüft
    (0 bedeutet gleich und 1 ungleich, zieht man z. B. zwei Zahlen von einander ab und das Ergebnis ist 0, dann sind diese Zahlen offensichtlich gleich).[…]“

    //Also wenn ich X=1 und DEX mache, ist das Ergebnis 0. Weil die Zahlen sind ‚gleich‘
    //Aber das Z-Flag wird gesetzt, also Z-Flag=1. Aber dann bedeutet Z-Flag=1 ja ‚ungleich‘?!?
    //Aber die Zahlen waren doch grade noch ‚gleich‘

    „[…]Dementsprechend springt BNE zur angegebenen Adresse, wenn das Zero-Flag gesetzt[…]ist.“

    //Aber das Z-Flag wird doch nur gesetzt wenn das X nach dem DEX 0 ist?!

    Mir ist schon klar, dass der Source-Code so funktioniert wie er da steht, aber diese Erklärung verknotet mir die Gehirnwindungen.
    Übersehe ich was oder missverstehe ich was? @-@“‘

    1. Oh Mann, ich fall vom Glauben ab! Was habe ICH denn da für einen Schwachsinn geschrieben?!!

      Sorry!
      Du hast nichts übersehen, sondern bist über einen Fehler gestolpert, der so seit über drei Jahren dort stand!
      Das so etwas ausgerechnet beim ersten Beitrag passiert und solange nicht auffällt, trifft mich echt hart.

      „[…]Der Befehl DEX zieht einfach vom Inhalt des X-Registers 1 ab.[…]
      Ist die Zahl genau 0, dann wird das Z-Flag gesetzt (1), sonst wird es gelöscht (0). “
      //Also ist X=1, dann DEX …, dann ist X=0 und Z-Flag=1 ?
      Ja, soweit ist es noch richtig!

      „[…]Ob etwas gleich oder ungleich ist wird über das Zero-Flag geprüft
      (0 bedeutet gleich und 1 ungleich, zieht man z. B. zwei Zahlen von einander ab und das Ergebnis ist 0, dann sind diese Zahlen offensichtlich gleich).[…]“
      //Also wenn ich X=1 und DEX mache, ist das Ergebnis 0. Weil die Zahlen sind ‚gleich‘
      //Aber das Z-Flag wird gesetzt, also Z-Flag=1. Aber dann bedeutet Z-Flag=1 ja ‚ungleich‘?!?
      //Aber die Zahlen waren doch grade noch ‚gleich‘
      Hier stand von mir verzapfter Unfug!
      Es ist natürlich genau umgekehrt: Z=
      1 bedeutet gleich und 0 ungleich
      Das Z-Flag wird immer auf 1 gesetzt, um anzuzeigen, dass die letzte Aktion NULL war, egal ob durch DEX, INY, LDA, CMP usw.

      „[…]Dementsprechend springt BNE zur angegebenen Adresse, wenn das Zero-Flag gesetzt[…]ist.“
      //Aber das Z-Flag wird doch nur gesetzt wenn das X nach dem DEX 0 ist?!
      Nochmal Blödsinn von mir.
      BNE (Springe wenn ungleich) verzweigt natürlich, wenn Z=0 also gelöscht ist!

      Mir ist schon klar, dass der Source-Code so funktioniert wie er da steht, aber diese Erklärung verknotet mir die Gehirnwindungen.
      Übersehe ich was oder missverstehe ich was?
      Nein, du hast nichts missverstanden, ich habe dich durch meine falsche Erklärung vollkommen verwirrt.
      Ich kann mich nur nochmal entschuldigen!

      Reichen die obigen Erklärungen oder kann ich dir noch weiterhelfen?

      Gruß,
      Jörn

    1. Da liegst du falsch! Das BASIC-RAM beginnt bei $0800! Die erste BASIC Zeile beginnt zwar bei $0801, das ändert aber nichts daran, dass das BASIC RAM schon bei $0800 beginnt.

      Falls du mir nicht glaubst, hier noch weitere Quellen:

      • C64-Wiki ($0800-$9FFF 2048-40959 Page 8-159 Free BASIC program storage area (38911 bytes) )
      • Compute’s Mapping the Commodores 64 & 64C Seite 82

        2048-40959 $800-$9FFF
        BASIC Program Text
        This is the area where the actual BASIC program text is stored. The text of a BASIC program consists of linked lines of program tokens…

      PS:
      Ich fand es aber trotzdem etwas missverständlich und habe den Abschnitt um eine Erklärung ergänzt:
      Ab $0800 beginnt zwar das BASIC-RAM (s. „Kleine Hardwarekunde“), wie ihr aber eben gesehen habt, wollen wir die nächsten BYTEs erst ab $0801 ablegen. Dies ist die Adresse, an der die erste BASIC-Zeile erwartet wird. $0800 ist für uns tabu! Dort muss immer eine 0 stehen, sonst kommt es zu einer Fehlermeldung, wenn man RUN eingibt, um das Programm zu starten. Daher beginnen wir mit $0801.

      PPS:
      Danke für deine Anmerkungen. Nur durch entsprechende Rückmeldungen, kann ich die Texte weiter optimieren und Fehler, sowie Ungereimtheiten eliminieren.

      1. Hallo Jörn,

        du hast natürlich recht, ich hab selber auch nochmal im C64 für Insider nachgesehen. Lag wohl an der Macht der Gewohnheit daß ich auf $0801 kam.

  3. Hallo Jörn, tolle Seiten, ich bin begeistert. Da ich grad selber wieder auf dem C64 Trip bin arbeite ich die Seiten mal durch und habe viel Spaß dabei, tolle Arbeit. Bei den Taktzyklen des DEX Befehls habe ich gestutzt, Du hast den mit 1T angegeben. Ich meine mich zu erinnern, das es keinen Befehl mit einem Taktzyklus gab, der müsste zwei haben. Da ich kürzlich gerade mal wieder den CRE177 Beitrag von Tim Pritlove und Michael Steil gehört habe, wird das hier bestätigt (selbst NOP hat zwei 🙂 Übrigens ist der Beitrag für C64 Fans sehr zu empfehlen.
    Gruß und danke HP

    1. Igitt!
      Da hast du vollkommen Recht!!

      Da bin ich wohl mit der BYTE-Anzahl durcheinander gekommen. Ich habe das eben korrigiert. Beim DEY war es übrigens auch falsch, aber unter Mnemonics stand es für beide richtig. INX, INY und NOP sind auch korrekt.

      Danke für den Hinweis,
      Jörn

      PS: Der Text in deinem Post war irgendwie doppelt, ich habe ihn daher bereinigt. Außerdem habe ich dort noch einen Link zum CRE177-Beitrag eingefügt.

  4. Pingback: Linksammlung C64 | Stefan.Waidele.info

Schreibe einen Kommentar

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

Protected by WP Anti Spam