Erstellt: 17. Februar 2013 (zuletzt geändert: 28. November 2018)

Stapeln und schieben

Eine Binärzahl ausgeben

C64 Studio, AMCE & TASM

Dieses Mal wollen wir eine Binärzahl auf dem Bildschirm ausgeben. Wir werden dabei eine ganze Reihe neuer Befehle kennenlernen und auch den Stack verwenden.

Aber bevor wir mit dem Programm beginnen, sollten wir uns zunächst mal Gedanken machen, wie wir das Problem lösen.
Ich weiß, es juckt euch in den Fingern direkt drauflos zu programmieren, aber lasst uns kurz überlegen (auch ohne alle Assemblerbefehle zu kennen) wie das Problem gelöst werden kann.

Aufgabenstellung: Binärzahl ausgeben

Eine 8-Bit Zahl soll als Abfolge von Nullen und Einsen auf dem BS ausgegeben werden. Die Zahl legen wir im Programm fest in einer Variablen ab und fürs Erste geben wir die Binärzahl direkt oben links in der Ecke des BS aus, also einfach am Beginn des Bildschirmspeichers.

Überlegungen zu Lösung:

Bereits in den vergangenen Beispielen haben wir Pik-Zeichen ( ihr erinnert euch?) auf dem BS ausgegeben. Die Ausgabe eines Zeichens sollte also nicht allzu kompliziert sein, aber wie erkennen wir, wann eine 0 und wann eine 1 benötigt wird?
Uns fällt es mittlerweile nicht schwer, z. B. die Hex-Zahl $c9 binär aufzuschreiben. Seit unseren Übungen bei den Zahlensystemen wissen wir, dass $c dezimal 12 ist (A=10, B=11, C=12,…). Binär wäre es %1100 (1*2^3 + 1*2^2 + 0*2^1 + 0*2^0) und $9 = %1001 ist, also kommen wir auf %11001001.

1. Idee (Brut-Force)

Wir schauen uns, wie oben, jede Ziffer der Hex-Zahl getrennt an und geben den Text direkt für das jeweilige Nibble aus.

Pseudo-Code: $c9 binär ausgeben
-------------------------------
nimm die 1. Ziffer: $c

wenn 0, dann
 sta $0400,'0'
 sta $0401,'0'
 sta $0402,'0'
 sta $0403,'0'
 jmp ziffer2

wenn 1, dann
 sta $0400,'0'
 sta $0401,'0'
 sta $0402,'0'
 sta $0403,'1'
 jmp ziffer2

...

wenn $e, dann
 sta $0400,'1'
 sta $0401,'1'
 sta $0402,'1'
 sta $0403,'0'
 jmp ziffer2

wenn $f, dann
 sta $0400,'1'
 sta $0401,'1'
 sta $0402,'1'
 sta $0403,'1'
 jmp ziffer2

ziffer2:
das Gleiche noch mal für die 2. Ziffer

<ENDE>

Dies würde zwar funktionieren, aber „schön“ ist was Anderes. Wir müllen uns den Speicher ziemlich zu und pflegeleicht ist so ein Source-Monstrum auch nicht gerade. Für unsere Zwecke würde das zwar reichen, aber wir wollen doch lieber mal überlegen, ob sich nicht eine schlanke und dennoch effizente Lösung finden lässt.

Verschweigen möchte ich an dieser Stelle nicht, dass eine Abfolge von Befehlen immer schneller ist, als eine Schleife. Um das zu beweisen, setzen wir unser bisheriges Wissen und die Liste der Mnemonics ein. Die Anzahl der Ausgaben (10) ist bewusst gering gehalten, ihr könnt das aber gerne mal für 100 oder 200 nachrechnen 😉 :

Ausgabe von 10 Zeichen auf dem BS:
----------------------------------
 lda #$01      ;2 Byte /  2 TZ
 ldx #$0a      ;2 Byte /  2 TZ
loop
 sta $03ff,x   ;3 Byte /  5 TZ * 10
 dex           ;1 Byte /  2 TZ * 10
 bne loop      ;2 Byte /  3 TZ *  9 | für die Sprünge
               ;       +  2 TZ      | beim letzten Mal KEIN Sprung
-----------------------------------
               10 Byte /103 TZ

Beim BNE gehen wir davon aus, dass dieser nicht über die Page-Grenze springen muss.

Jetzt OHNE Schleife:
-------------------------
 lda #$01  ;2 Byte / 2 TZ
 sta $0400 ;3 Byte / 4 TZ
 sta $0401 ;3 Byte / 4 TZ
 sta $0402 ;3 Byte / 4 TZ
 sta $0403 ;3 Byte / 4 TZ
 sta $0404 ;3 Byte / 4 TZ
 sta $0405 ;3 Byte / 4 TZ
 sta $0406 ;3 Byte / 4 TZ
 sta $0407 ;3 Byte / 4 TZ
 sta $0408 ;3 Byte / 4 TZ
 sta $0409 ;3 Byte / 4 TZ
-------------------------
           32 Byte /42 TZ

Es gilt immer Programmier- & Pflegeaufwand, sowie Speicherbedarf und Laufzeit gegeneinander abzuwägen. Wie wir sehen können, braucht die 2. Version nur 40% der Ausführungszeit der oberen, aber dafür wird auch mehr als dreimal soviel Speicher benötigt. Wäre diese Stelle super zeitkritisch und wir hätten die paar Bytes über, könnte die 2. Version durchaus interessant sein, aber wenn nun statt 10 doch 100 Ausgaben gewünscht sind, haben wir ein Problem.

Da ich zu Beginn schon wieder die -Symbole erwähnt habe, möchte ich noch kurz aufzeigen, dass der Ansatz von eben (einzelne Befehle anzugeben), sinnvoll eingesetzt, zu einer insg. besseren Lösung führen kann.
Statt unserer verschachtelten Schleife (4 mal 256 Durchläufe / den erwähnten Überhang von 24 Bytes lassen wir mal außer Acht) hätten wir (analog zu eben) auch einfach schreiben können:

 lda #$41
 sta $0400
 sta $0401
 ...
 sta $07fe
 sta $07ff
 rts

Diese Lösung wäre zwar die schnellste (1024 * 4 TZ + 2 TZ LDA + 6 TZ RTS = 4104 TZ), aber zu welchem Preis? Wer möchte 1024 mal STA eingeben und damit über 3KB Speicher (1024 * 3Byte / vgl. absolute Adressierung + 2 Byte LDA + 1Byte RTS) nur fürs Füllen des BS mit lauter -Zeichen opfern? Wenn wir dann noch, wie gleich, das setzen der Farbe hinzunehmen, müssen wir die Werte verdoppeln, also über 6KB (auch die 4 Bytes auf der Zero-Page dürfen wir nicht vergessen) und 8202 TZ (nur 1x 6 TZ für RTS, das natürlich auch nur 1 Byte verbraucht).

Da ist unsere bisherige Version doch schon schicker, oder?

Aber noch besser finde ich folgenden Ansatz:

;*** Variablen
SCREENRAM       = $0400 ;Start des Bildschirmspeichers
CHAR            = $41   ;Pik-Zeichen für die Ausgabe
COLORRAM        = $d800 ;Start des Farb-RAMs
COLORNO         = $00   ;Schwarz ($00) als Zeichenfarbe 

;*** Startadresse & BASIC-Zeile
*=$0801
 !byte $0c,$08,$e2,$07,$9e,$20,$32,$30,$36,$32,$00,$00,$00

;*** Start des Assemblerprogrammes
 ldy #$00               ;mit 0 beginnen, für 256 Durchläufe
 ldx #COLORNO           ;Farbe ins X-Register

loop
 lda #CHAR              ;Zeichen in den Akku
 sta SCREENRAM,y        ;Akku in die 1. Page des BS-Speichers
 sta SCREENRAM+$0100,y  ;Akku in die 2. Page des BS-Speichers
 sta SCREENRAM+$0200,y  ;Akku in die 3. Page des BS-Speichers
 sta SCREENRAM+$0300,y  ;Akku in die 4. Page des BS-Speichers

 txa                    ;X-Reg. in den Akku (ist schneller als lda)
 sta COLORRAM,y         ;Farbe in 1. Page des Farb-RAMs
 sta COLORRAM+$0100,y   ;Farbe in 2. Page des Farb-RAMs
 sta COLORRAM+$0200,y   ;Farbe in 3. Page des Farb-RAMs
 sta COLORRAM+$0300,y   ;Farbe in 4. Page des Farb-RAMs

 dey                    ;Y-Register verringern
 bne loop               ;solange nicht 0 wieder zum loop: springen

 rts

Das Programm macht exakt das Selbe, wie unsere bisherige Version (nach der 1. Korrektur auf 256 Zeichen). Es ist aber nur noch 32 Source-Zeilen, statt bisher 50, lang. Wer Speicherbedarf und Laufzeit überprüft, wird feststellen, dass hier sogar beide geringer, als beim bisherigen Beispiel sind und auf die Zero-Page verzichten wir auch noch.

Version
Bytes
%-Änderung
Taktzyklen
%-Änderung
Tausende STA-Zeilen
6153
18645%
8202
100%
Bisherige Version
39
118%
23643
288%
Neue Version: Eine Schleife mit 8x STA
33
100%
12553

153%

Ich würde unsere neue Version als sehr gelungen bezeichnen. Ein Blick auf die Tabelle verrät uns, dass sie (auf diese drei Beispiele bezogen) eigentlich die beste Lösung darstellt. Sie lässt sich gut pflegen, ist überschaubar und bietet ein vernünftiges Verhältnis zwischen Speicherbedarf und Ausführungszeit.

Machen wir nun aber lieber weiter, mit unserer eigentlichen Aufgabe.

2. Idee

Wir schauen uns wieder jede Hex-Ziffer getrennt an und geben je Nibble die passende 0/1-Kombination aus, die wir als Text fest im Speicher ablegen.

Pseudo-Code: $c9 binär ausgeben
-------------------------------
nimm die 1. Ziffer: $c
suche den Text für: $c unter 'bintexte:'
gib den gefundenen Text aus
nimm die 2. Ziffer: $9
suche den Text für: $9 'bintexte:'
gib den gefundenen Text aus
<ENDE>
bintexte
 "0000"
 "0001"
 "0010"
 ...
 "1110"
 "1111"

Schon besser, aber wir benötigen allein 64 Byte für die Texte zu den jeweiligen Nibble. Es wäre doch schöner, direkt das entsprechende Bit auszugeben…

3. Idee

Wir prüfen jedes einzelne Bit, ob es Null oder Eins ist und geben dann die entsprechende Ziffer aus.

Pseudocode: $c9 binär ausgeben
------------------------------
 n = 0
start
 prüfe ob Bit-n in $c9 auf 0 oder 1 gesetzt ist
 wenn 1 gehe zum Label eins
 sonst Zeichen '0' laden und zum Label ausgabe springen

eins
 zeichen '1' laden

ausgabe
 geladenes Zeichen ausgeben
 n=n+1
 wenn kleiner 8 zu start springen 
 <ENDE>

Ich denke diese Idee ist es wert, einmal umgesetzt zu werden.

SCREEN    = $0400       ;Start des Bildschirmspeichers
OUTPUTVAL = $c9         ;Diese Zahl wird ausgegeben

;*** Startadresse BASIC-Zeile
*=$0801
 !byte $0c,$08,$e2,$07,$9e,$20,$32,$30,$36,$32,$00,$00,$00

;*** Start des Assemblerprogrammes
 lda #%10000000         ;Bit das geprüft wird
 ldy #$00               ;Position auf dem BS

binoutloop

Kurz gefasst, in OUTPUTVAL hinterlegen wir unsere Zahl, die ausgegeben werden soll. In den Akku laden wir die Nr. des Bits, das wir prüfen möchten und ins Y-Register kommt die Ausgabeposition für den BS.
Unsere Zahl, die wir ausgeben möchten, steht hier übrigens, wie wir gleich sehen, beim Label number am Ende des Programms.

 bit number  ;bitweises UND von Akku & Adresse

BIT: BIT test (Bits testen)
BIT absolut ($2C, 3B, 4T, NVZ)
Der BIT-Befehl führt im Prinzip eine bitweise UND-Verknüpfung (die wir schon vom AND kennen) durch. Es gibt aber einige Besonderheiten im Vergleich zum AND. Wie ihr bei den Flags erkennen könnt, setzt BIT nicht nur das Negativ- und Zero-Flag, wie AND, sondern evtl. auch die OVerflow Flagge. Das liegt daran, dass der BIT-Befehl zuerst Bit-7 & 6 des Bytes an der angegebenen Adresse prüft, dadurch werden N und V beeinflusst, abschließend wird die UND-Verknüpfung durchgeführt, was wiederum Z beeinflusst. Es ist sogar möglich, dass alle drei Flags gleichzeitung auf 1 gesetzt werden, beim AND können N und Z nie gleichzeitig auf 1 stehen. Der nächste Unterschied, liegt darin, dass im Gegensatz zum AND, kein Ergebnis gespeichert wird, es werden also nur die Flags gesetzt.

Schauen wir uns mal an, was beim ersten Durchlauf passiert.

Akku......: %10000000  $80
number....: %11001001  $c9
            ---------
BIT number: %10000000      | N=1, V=1, da Bit-7 & 6 bei $c9 = 1 sind
                           | und Z=0, da das Ergbnis <> 0 ist

Da zuerst Bit-7 und 6 des Bytes an der angegebenen Speicherstelle geprüft werden, werden durch obiges Beispiel die N– & V-Flagge auf 1 gesetzt. Durch die anschließende AND-Verknüpfung, kommt BIT schließlich zum Ergebnis %10000000, wodurch die Z-Flagge auf 0 gesetzt wird.

 bne one  ;Wenn es eine 1 ist, dann zum Label one springen 
 ldx #"0" ;sonst, das Zeichen "0" nach X laden
 jmp out  ;und zur Ausgabe springen

one       ;Ziel Label, falls es eine 1 ist
 ldx #"1" ;das Zeichen "1" ins X-Register laden

out       ;BS-Ausgabe

Der Abschnitt erklärt sich eigentlich von selbst, wenn nach unserer BIT-Prüfung das Zero-Flag auf 0 steht, springen wir zum Label one, um das Zeichen für eine Eins ins X-Register zu laden und anschließend direkt zur Ausgabe „weiter zulaufen“. Ist Z=1, dann haben wir eine Null gefunden und springen nicht! Wir laden dann das Zeichen für die Null ins X-Register und springen anschließend über den Abschnitt one zum Label out, um mit der Ausgabe zu beginnen.

 pha                    ;Akku auf dem Stack merken

PHA: PusH Akku on stack (Akku auf den Stack schieben)
PHA implizit ($48, 1B, 3T, <keine>)
Mit PHA legen wir den Akku-Inhalt auf dem Stack ab. Da wir den Akku gleich für etwas Anderes benötigen, merken wir uns seinen Inhalt vorläufig auf dem bereits erwähnten Stack.

Wat is‘n Stack? Da stelle mer uns ma janz dumm…
…und stellen uns den Stack einfach als einen Stapel vor, vgl. Kleine Hardwarekunde. Dort habe ich den Stack mit einem Bücherstapel verglichen. Nun sind wir aber schon viel weiter und schauen uns den Stack mal von der technischen Seite an.
Der Stack dient also zum Zwischenspeichern von Werten, die wir später noch mal benötigen. Wie ihr beim RTS gesehen habt (und weiter unten nochmal genauer seht) kann der Stack sogar als Quelle für ein Sprungziel dienen.

 

Füllrichtung des Stacks.
Füllrichtung des Stacks.

Um unsere Werte zu speichern, benötigt der Stack natürlich Speicher. Die 1. Page $0100 – $01ff des Speichers, ist für den Stack reserviert. Der Stack wird von $01ff absteigend bis $0100 gefüllt.

 

 

 

 

 

 

 

Beispiel des Stacks vor PHA.
Beispiel des Stacks vor PHA.

Schauen wir uns mit Hilfe der Grafik rechts mal an, was passiert, wenn unser PHA ausgeführt wird.
Wir gehen davon aus, dass der Stackpointer (SP) nach dem Programmstart auf $ee (ein Byte reicht, da der Speicher wie bereits erwähnt auf der 1. Page liegt) zeigt. Der SP verweist also auf die nächste freie Speicherstelle für den Stack.
Damit der Computer weiß, wo das nächste freie Byte auf dem Stack zu finden ist, gibt es den Stackpointer (SP). Der genaue Wert des Stackpointers und der Inhalt des Stacks hängt natürlich von den vorherigen Programmen und Befehlen ab. Auch die Bytes der aktuell freien Plätze auf dem Stack sind nicht vorhersagbar, sie hängen wiederum von der letzten Benutzung ab.

 

Nach dem PHA
Nach dem PHA

Wird nun PHA ausgeführt, dann wird zunächst der Inhalt des Akkus (bei uns $80, wir testen zur Zeit ja das höchste Bit) an der im SP stehenden Adresse abgelegt. Also landet an der Speicherstelle $01ee die $80 aus dem Akku. Anschließend wird der Stackpointer um eins verringert, damit er auf die nächste freie Stelle zeigt.

Wie wir den Wert wieder vom Stack bekommen, sehen wir nach zwei bekannten Befehlen.

 

 

Als nächstes geben wir unser Zeichen auf dem BS aus:

 txa                    ;X (Zeichen "0" / "1") in den Akku kopieren
 sta SCREEN,Y           ;Akku auf dem BS ausgeben

Im X-Register liegt ja das Zeichen, dass wir ausgeben möchten. Da es keine absolute Y-indizierte Adressierung für den STX-Befehl gibt, müssen wir unser Zeichen in den Akku kopieren (jetzt kennt ihr auch denn Grund fürs PHA von eben), um es auszugeben. Das Kopieren erreichen wir bekanntlich durch TXA. Direkt danach, geben wir das Zeichen wie gewohnt auf dem BS aus.

 pla                    ;gemerkten Akku vom Stack holen

PLA: PuLl Akku from stack (Akku vom Stack holen)
PLA implizit ($68, 1B, 4T, NZ)

Auswirkung des PLA
Auswirkung des PLA

Mit PLA werden wir nun wieder unseren bisherigen Wert zurück in den Akku holen, um gleich das nächste Bit zu testen.
Schauen wir uns das nochmal exemplarisch mit der Grafik rechts an. Aktuell steht der SP ja auf Adresse $ed (s. oben). Trifft die CPU nun auf den PLA-Befehl, wird zunächst der SP um eins erhöht und zeigt somit also auf $ee. Danach wird das Byte von dieser Speicherstelle (unsere $80) in den Akku kopiert. Im Gegensatz zum PHA beeinflusst der PLA auch die N– und Z-Flaggen.
Da nur kopiert wird, bleibt die $80 natürlich auch auf dem Stack liegen.

 iny                ;Y für nächste BS-Position (nächstes Bit) erhöhen

Jetzt das Y-Register um eins erhöhen, damit wir zur nächsten BS-Position kommen.

 

 lsr                    ;Bit im Akku nach rechts verschieben

LSR: Logical Shift Right (bitweise Verschiebung nach rechts)
LSR Akku ($4A, 1B, 2T, NZC)
Wir haben uns ja vorgenommen, alle Bits nacheinander zu prüfen. Zum Glück gibt es den LSR-Befehl. Dieser verschiebt den Inhalt das Akkumulators um jeweils ein Bit nach rechts. Dabei wird von links eine 0 eingeschoben und das Bit, dass rechts „herausfällt“, landet im Carry-Flag. Auch die Negativ- und Zero-Flagge werden beeinflusst.

Nur zur Info:
Einige Assembler nutzen die Schreibweise LSR A. Das C64 Studio versteht beides, der Turbo Assembler wandelt bei der Eingabe LSR automatisch in LSR A um und ACME kennt nur LSR.

Schauen wir uns nun mal an, was während der acht Durchläufe in unserem Programm beim LSR passiert.

LSR
LSR
Akku          %10000000  C
LSR -> (0) -> %01000000 (0) -> C=0; N=0; Z=0
LSR -> (0) -> %00100000 (0) -> C=0; N=0; Z=0
LSR -> (0) -> %00010000 (0) -> C=0; N=0; Z=0
LSR -> (0) -> %00001000 (0) -> C=0; N=0; Z=0
LSR -> (0) -> %00000100 (0) -> C=0; N=0; Z=0
LSR -> (0) -> %00000010 (0) -> C=0; N=0; Z=0
LSR -> (0) -> %00000001 (0) -> C=0; N=0; Z=0
LSR -> (0) -> %00000000 (1) -> C=1; N=0; Z=1

Uns interessiert hier das Zero-Flag. Wir werden unsere Schleife solange laufen lassen, bis im Akku eine Null steht.

 bne binoutloop  ;solange Akku <> 0, nächstes Bit prüfen

 rts             ;sonst, zurück zum BASIC

number
 !byte OUTPUTVAL ;hier steht die Zahl, die der BIT-Befehl überprüft

Mit dem BNE springen wir, wie eben erwähnt, solange zum Label binoutloop, bis der Inhalt des Akkus Null ist.

Rücksprung zum Basic.
Rücksprung zum Basic.

Zum Schluß springt der RTS-Befehl wieder zurück zum BASIC. Das haben wir ja schon häufig gemacht. Aber diesmal betrachten wir den Stack, einmal näher. Der Befehl RTS holt sich zwei Bytes vom Stack (LSB & MSB) um die Rücksprungadresse zu erhalten. Bei uns sind das $e9 und $a7. Der SP wird dabei jeweils um eins verringert und zeigt zum Schluß in unserem Beispiel auf $f0. Die Adresse vom Stack wird noch um eins erhöht, so dass letztendlich zur Adresse $a7e9 gesprungen wird (sprich der PC wird auf diese Adresse gesetzt).

Das Label number hinter dem RTS, nimmt unsere Zahl auf, die wir ausgeben wollen, damit der BIT-Befehl diese prüfen kann.

Unser Programm ist nun fertig.

Hier, wie so häufig, das komplette Listing:

SCREEN    = $0400       ;Start des Bildschirmspeichers
OUTPUTVAL = $c9         ;Diese Zahl wird ausgegeben

;*** Startadresse BASIC-Zeile
*=$0801
 !byte $0c,$08,$e2,$07,$9e,$20,$32,$30,$36,$32,$00,$00,$00

;*** Start des Assemblerprogrammes
 lda #%10000000         ;Bit das geprüft wird
 ldy #$00               ;Position auf dem BS

binoutloop
 bit number             ;bitweises UND von Akku & Adresse
 bne one                ;Wenn es eine 1 ist,
                        ;dann zum Label one: springen 
 ldx #"0"               ;sonst, das Zeichen "0" nach X laden
 jmp out                ;und zur Ausgabe springen

one                     ;Ziel Label, falls es eine 1 ist
 ldx #"1"               ;Zeichen "1" ins X-Register
 
out                     ;BS-Ausgabe
 pha                    ;Akku auf dem Stack merken
 txa                    ;X (Zeichen "0" oder "1") in den Akku
 sta SCREEN,y           ;Akku auf dem BS ausgeben
 pla                    ;gemerkten Akku vom Stack holen
 iny                    ;Y für nächste BS-Position erhöhen
 lsr                    ;Bit im Akku nach rechts verschieben
 bne binoutloop         ;solange Akku <> 0, nächstes Bit prüfen

 rts                    ;sonst, zurück zum Aufrufer (BASIC)

number 
 !byte OUTPUTVAL        ;hier steht die Zahl, die bit überprüft

Oben links hat sich nun unsere binäre Ausgabe von $c9 versteckt.

Unsere Binärausgabe

Gebt ruhig mal andere Zahlen hinter OUTPUTVAL ein, um die Ausgabe zu testen.

Weitere Adressierungsarten

Kommen wir wieder zu den weiteren Adressierungsarten und Befehlsvarianten.

 bit $fb

BIT Zero-Page ($24, 2B, 3T, NVZ)
Der BIT-Befehl verfügt nur noch über eine weitere Adressierungsart und das ist die Zero-Page-Adressierung.


Bei den Stackbefehlen gibt es noch zwei weitere Befehle, um das Statusregister auf dem Stack zu speichern und zurückzuholen.

 php

PHP: PusH Processor Status on Stack (Statusregister auf den Stack legen)
PHP implizit ($08, 1B, 3T, <keine>)
Mit PHP könnt ihr das Statusregister (also unsere Flags) auf dem Stack sichern. Das könnte z. B. notwendig sein, wenn ihr nach einem Anweisungsblock wieder die gleichen Flags wie davor benötigt.

 plp

PLP: PuLl Processor Status from Stack (Statusregister vom Stack holen)
PLP implizit ($28, 1B, 4T, NV-BDIZC)
Wenn ihr die Flags wieder zurück ins Statusregister kopieren wollt, müsst ihr PLP verwenden. Der Befehl beinflusst als einziger aller Flags, was klar ist, da er die Flags an Hand des Bytes auf dem Stack setzt.


Der LSR-Befehl kennt noch die folgenden Adressierungsarten:

 lsr $0821

LSR absolut ($4E, 3B, 6T, NZC)
Verschiebt die Bits des Bytes an der absoluten Adresse nach rechts.

 

 lsr $0821,x

LSR absolut X-indiziert ($5E, 3B, 7T, NZC)
An der angegebenen Adresse + Inhalt des X-Registers, die Bits des dort zu findenden Bytes nach rechts verschieben.

 

 lsr $fb

LSR Zero-Page ($46, 2B, 5T, NZC)
Verschiebt an der angegebenen Zero-Page-Adresse die Bits des dortigen Bytes nach rechts.

 

 lsr $fb,x

LSR Zero-Page X-indiziert ($56, 2B, 6T, NZC)
Die Bits, des Bytes an der Zero-Page-Adresse + Inhalt des X-Registers, nach rechts verschieben.

 

Es gibt auch ein Verschieben in die andere Richtung.

 asl

ASL: Arithmetic Shift Left (bitweises verschieben nach links)
ASL Akku ($0A, 1B, 2T, NZC)
Das Gegenstück zum LSR, nur das hier die Bits nach links verschoben werden. Dabei wird von rechts mit Nullen aufgefüllt und das herausfallende Bit landet im Carry-Flag.

Da wir den Befehl schon im nächsten Beitrag verwenden werden, führe ich den hier nicht weiter aus und vertage das bis dahin.


Im nächsten Beitrag werden wir wie versprochen ein eingegebenes Zeichen (ok es wird nur vom BS gelesen) binär und hexadezimal ausgeben. Außerdem lernen wir Subroutinen kennen und benutzen zur Abwechslung mal ein oder zwei System-Routinen.


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

Loading...


ZurückWeiter

14 Gedanken zu „Stapeln und schieben“

  1. Mir ist aufgefallen, daß ja das LSB (Bit hier) beim LSR immer im Carry landed. Dementsprechend kann man als abbruchbedingung für die Schleife auch nehmen, wenn das Carry Gesetzt ist. Oder besser ausgedrückt, so lange Das Carry Bit Clear ist (BCC) wird geloopt.

    Das würde dann so aussehen:
    lsr ;Bit im Akku nach rechts verschieben
    bcc binoutloop ;solange Akku 0, nächstes Bit prüfen

    Es ist immer interessant zu sehen, wie die Befehle auf die verschiedenen Register verwenden. Hier macht das keinen Unterschied, ein Weg, der geht ist ja genug. Aber man kann eben hier verschiedene Wege erkennen, wie man ohne einen Zähler zum Ziel kommt. (Nein, so clever war ich nicht, ich habe einen Zähler verwendet)…

  2. Das ist ja echt der Hammer:
    inc out:+1
    Wir incrementieren das LSB der ScreenAddresse die im assemblierten Code ja als echte Addresse da steht. (8D 00 40 im ersten run, und dann wird die 00 zu 01 usw.) Funktioniert hier gut.

    Schöner Code 🙂

  3. Hallo Jörn,
    mir geht es ähnlich wie dir und Alexander Reyer. Auch ich hatte zwar zwischen Grundschule und Mittelstufe mind. eines oder mehr C64 Assembler Bücher und wollte es immer lernen, aber irgendwie war mir das damals zu kompliziert und ich hab nie kapiert, für was man das alles braucht. (Dann kam der Amiga, ich hab mir DevPack Assembler gekauft und es war noch komplizierter und ich hab nur noch gezockt 😀 )

    Irgendwann vor Kurzem hat es mich dann wieder gepackt und ich will jetzt endlich meinen Kindheitstraum verwirklichen – ein C64 Spiel erstellen 🙂
    Dein Kurs ist klasse, weil du das meiste sofort an einem Beispiel erklärst und nicht erst alle Befehle durch nudelst wie die Bücher früher.

    Allerdings versteh ich bei diesem Beispiel nicht, warum du den Umweg über das number Label gehst. Ich habe einfach bit %10000000 geschrieben und es funktioniert anscheinend genauso?!

    Und dann noch was anderes, da mir am bisherigen Kurs völlig entgangen ist, dass man X auch mit Zeichen laden kann. Das erwähnst du hier beiläufig zum ersten mal glaube ich.

    Ich habe ein Hello World PRG geschrieben und mir alle Zeichen einzeln aus der PETSCII Tabelle raus gesucht, in den Akku geladen und von dort aus auf den BS Speicher kopiert.

    Jetzt hab ich es mit der “Lade ein Zeichen in X” – Methode probiert, es kommt aber ein ganz anderes raus.

    Also das geht:

    lda #$08 ; H
    sta $05ed

    aber wenn ich das versuche, kommt ein Grafikzeichen raus:
    ldx #”H”
    txa
    sta $05ed

    Woran könnte das liegen?

    Danke und viele Grüße, Gerrit

    1. Hallo Gerrit,
      spielst du eigentlich Lotto? 😉
      Dein BIT %10000000 funktioniert nur, weil du damit auf die Speicherstelle 128 bzw. $80 zugreifst (BIT kennt nur absolute und Zero-Page Adressierung!). Dort steht nach einem Reset $C9! Also genau der Wert, den ich hier als Beispiel verwende.


      Du erwähnst leider nicht, welchen Assembler du nutzt, ich gehe mal vom C64 Studio aus.
      Du mischt hier zwei Welten.
      Wenn du direkt in den Bildschirmspeicher schreibst, dann brauchst du die Codes aus dem Char-ROM. Daher geht lda #$08, denn im Char-ROM steht hinter der acht, das H. Mit ldx #”H” lädt der Assembler aber die Nummer aus der PETSCII-Tabelle. Nach dem assemblieren steht da dann ldx #$48 bzw. ldx #72. Schaust du jetzt wieder ins Char-ROM findest du dort, hinter der Nummer 72, den senkrechten Strich. Mit 0 und 1 klappt das hier nur, weil die im PETSCII und Char-ROM an den gleichen Stellen stehen.
      Es gibt andere Assembler, die z. B. zwischen “” und ” unterscheiden. Im ersten Fall würde das Zeichen aus dem PETSCII, im zweiten aus dem Char-ROM geladen. Beim C64 Studio ist dies aber nicht der Fall. PETSCII-Zeichen kannst du über jsr $ffd2 ausgeben, das wird später auch mal beschrieben.

  4. Ich habe das eben folgendermaßen gelöst. Dann braucht man das BIT und TXA nicht.
    Den Vorteil von BIT erkenne ich auch nicht besonders.
    Der Kurs hier ist super. Den hätte ich in der 6ten Klasse der Realschule benötigt als
    die Begeisterung für c64-Assembler noch wesentlich stärker war.
    Das ist besser als so manches Buch was ich gelesen habe.

    *=$0801
    byte $0B,$08, $01,$00, $9E, $20, $32, $30, $36, $32, $00, $00, $00

    screenadr = $0400
    nr = $c9

    lda #$25
    sta screenadr
    ldx #$08
    lda #nr

    loop:
    clc
    lsr
    pha ; accu auf den Stack
    bcs carry:
    lda #$30
    sta screenadr,X
    jmp next:
    carry:
    lda #$31
    sta screenadr,X
    next:
    pla ; accu vom Stack
    dex
    bne loop:

    rts

    1. Nabend,
      erstmal schön, dass dir der Kurs gefällt und ich finde es gut, dass du deinen eigenen Weg gehst.

      Falls sich deine Bemerkung bzgl. BIT auf das Listing bezieht, es war nur drin, da ich ein Beispiel haben wollte.
      Denk immer daran, dass die Beispielprogramme nur der Veranschaulichung dienen und keine perfekten Routinen sind.

      Weiterhin viel Spass,
      Jörn

  5. Hi Joern, dear mates !
    If I am not wrong (please check, dear mates — I am very tired at the moment) one could
    save 2 cycles in the binary printout in skipping the txa step and pushing the accumulator to stack before the char is loaded in x, see below. The memory footprint is however slightly higher (one byte).

    Thanks for the great page — I learn a lot here.
    Cheers, Stephan

    ————————–8 No txa necessary (saves 2 cycles, but needs more memory because of 2 x pha)

    sta SCREENRAM, y
    pla
    iny
    lsr
    bne loop

    rts

    numberToConvert:
    .byte $00
    ————————–8<—————————–

    1. Hello Stephan,
      you’re right. You can optimize nearly every code snippet on my page.
      It’s not my aim to show always the shortest or fastest way.

      If you really want to speed up things and shorten the code, have a look at this:

      SCREEN = $0400 ;Start des Bildschirmspeichers
      OUTPUTVAL = $C9 ;Diese Zahl wird ausgegeben

      ;*** Startadresse BASIC-Zeile
      *=$0801
      BYTE $0B,$08, $DD,$07, $9E, $20, $32, $30, $36, $32, $00,$00, $00

      ;*** Start des Assemblerprogrammes
      lda #%10000000

      binoutloop:
      ldx #'0'
      bit number:
      beq out:
      inx
      out:
      stx SCREEN
      inc out:+1
      lsr
      bne binoutloop:
      rts

      number:
      BYTE OUTPUTVAL

      Greetings,
      Jörn

  6. Arthur hat das von Seb gemeldete Problem

    lda 'x'
    lda #'x'
    
    jetzt auf seiner Liste und ich habe die entsprechenden Stellen angepasst.

    Sollte ich etwas übersehen haben, wäre ich für einen Hinweis sehr dankbar.

    Das CBM prg Studio hat diese Befehle bisher leider noch nie korrekt verarbeitet!

    Hier nochmal, wie es eigentlich funktionieren sollte und hoffentlich bald von Arthur umgesetzt wird:

    lda #'x' ;soll das BYTE für das Zeichen 'x' laden
    lda 'x'  ;soll das BYTE von der Adresse, die durch
             ;das Zeichen 'x' bestimmt wird, laden
    
  7. Hi,
    CBM Prg Studio 3.0 scheint befehle wie ldx ‘0’ nicht mehr zuzulassen. Oder sollte man dafür was in den Options ändern? Vorher ging das einwandfrei. Danke.

    1. Ist mir bisher noch gar nicht aufgefallen, danke für den Hinweis.
      Ich habe die Frage mal an Arthur weitergegeben.

      Du kannst aber auch ldx #’a’ nehmen, was in meinen Augen auch die richtige Schreibweise ist.

      Eigentlich war ldx ‘a’ noch nie richtig umgesetzt im CBM prg Studio. Es hat sich wie ldx #’a’ verhalten, hätte aber statt des Buchstaben ‘a’ den Wert von der Adresse die durch das ‘a’ bestimmt wird holen müssen.

      Ich werde nochmal kontrollieren, ob es bei Version 2.5.3 Probleme mit ldx #’a’ gab. Ich kann mir sonst nicht erklären, weshalb ich ldx ‘0’ verwendet habe.

      Sobald ich eine Antwort von Arthur habe, werde ich dann ggf. die Sourcecodes auf der Seite anpassen.

      1. Super, viele Dank für die schnelle Antwort. Mit lda #’a’ funktioniert es einwandfrei. Jetzt kann ich mit den Tutorial weiterfahren. 🙂

        1. Gern geschehen.

          Ich habe es eben nochmal kontrolliert, bei 2.5.3 war es die ‘richtige’ Schreibweise ldx #’0′, die nicht ging. Daher steht im Tutorial immer ldx ‘0’. Bis zur BETA 3.0.0 vom Januar war diese falsche Schreibweise möglich. Jetzt wurde es leider ohne ‘Vorwarung’ umgebaut.

          Ich hoffe, dass Arthur dies jetzt beibehält und zusätzlich ldx ‘0’ richtig (als absolute Addressierung) einbaut.

Schreibe einen Kommentar

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

Protected by WP Anti Spam