Erstellt: 3. März 2013 (zuletzt geändert: 5. April 2021)

Mit dem Rechner rechnen

Die Rechenbefehle

C64 Studio, AMCE & TASM

Wir haben bereits einige Rechenbefehle kennengelernt, z. B. INC oder DEX, hier geht es jetzt darum, beliebige Werte zu addieren oder subtrahieren. Falls noch nicht geschehen, solltet ihr nochmal bei „Wie der Rechner rechnet“ reinschauen. Das dort vermittelte Wissen werde ich hier nicht komplett wiederholen, wir werden dafür genauer betrachten, wie die Flags beeinflusst werden.

Um uns Beispiele anzuschauen, könnten wir einen Debugger bemühen und unser Programm dort testen. Ich möchte aber das Programm aus dem letzten Beitrag „Nach unten und zurück“ etwas umbauen. Wir implementieren unseren eigenen Micro-Debugger 😉 um uns die Flags anzeigen zu lassen.

SCREENPOS = $05e5        ;Position des Zeichens für die Ausgabe
CHROUT    = $ffd2        ;Jump-Table Adr.: Zeichenausgabe
SETCURSOR = $fff0        ;Jump-Table Adr.: get/set cursor pos

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

;*** Start des Assemblerprogrammes
 lda #$15				 ;Zur Sicherheit auf
 sta $d018				 ;Großbuchstaben umschalten
 
 lda SCREENPOS           ;Zeichen zwischen ( ) in den Akku laden
 pha                     ;und auf dem Stack merken

 lda #$93                ;BS-Löschen (wie PRINT CHR$(147) in Basic)
 jsr CHROUT              ;Jump-Table: Zeichenausgabe
 
 ldx #$0c                ;Zeile und
 ldy #$00                ;Spalte für SETCURSOR
 jsr runtextout          ;unseren Starttext 'RUN:( ) ' ausgeben

 pla                     ;Unseren Akku wiederherstellen
 sta SCREENPOS           ;und zurück zwischen die ( ) schreiben

 ldx #$0c                ;Zeile und
 ldy #$08                ;Spalte für SETCURSOR
 jsr binaryout           ;zur Binärausgabe (Akku) springen

 ldx #$0c                ;Zeile und
 ldy #$12                ;Spalte für SETCURSOR
 jsr hexout              ;zur Hex-Ausgabe springen

 rts                     ;zurück zum BASIC

;************************************************************
;*** Den Text an der Adresse runtext ausgeben
;************************************************************
;*** Übergabe: X = Zeile in der die Ausgabe beginnt
;***           Y = Spalte in der die Ausgabe beginnt
;***           Das Textende wird durch $00 gekennzeichnet
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
runtextout
 clc                     ;C=0 für set / C=1 für get Cursor
 jsr SETCURSOR           ;Jump-Table: get/set cursor
 ldx #$00                ;Pos. im Text

runtextcharin 
 lda runtext,x           ;Aktuelles Zeichen in den Akku laden
 beq done                ;wenn 0 dann sind wir fertig 
 jsr CHROUT              ;Jump-Table Zeichenausgeben
 inx                     ;X erhöhen, für nächstes Zeichen
 jmp runtextcharin       ;und wieder hochspringen

done                     ;Ziel, sobald wir eine 0 haben
 rts                     ;zurück zum Aufrufer (jsr runtextout)

;*** unser Starttext, Textende wird durch BYTE $00 erkannt!
runtext 
 !text "RUN:( ) "
 !byte $00

;************************************************************
;*** Den Inhalt des Akkus als Binärzahl auf dem BS ausgeben
;************************************************************
;*** Übergabe: A = Zahl, die ausgegeben wird
;***           X = Zeile in der die Ausgabe beginnt
;***           Y = Spalte in der die Ausgabe beginnt
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   X,Y,SR
;************************************************************
binaryout
 pha                     ;Akku auf dem Stack merken (wg. SETCURSOR)
 clc                     ;C=0 für set / C=1 für get Cursor
 jsr SETCURSOR           ;Jump-Table get/set cursor
 lda #"%"                ;Prozent-Zeichen in den Akku
 jsr CHROUT              ;und ausgeben
 pla                     ;Akku wieder vom Stack holen
 pha                     ;und direkt nochmal merken
                         ;(bis zum Rücksprung)

 ldy #$07                ;Schleife rückwärts von Bit 7 bis 0

binoutloop
 ldx #"0"                ;Zeichen "0" ins X-Register
 asl                     ;Akku nach <-links verschieben
 bcc out                 ;Wenn es eine 0 ist, direkt zur Ausgabe, 
 inx                     ;sonst erhöhen, damit wir eine 1 haben.
out                      ;Sprungziel, wenn wir eine 0 haben
 pha                     ;Akku merken, er wird gleich überschrieben
 txa                     ;X -> Akku; Zeichen in den Akku
 jsr CHROUT              ;Jump-Table: Zeichenausgeben
 pla                     ;Akku fürs nächste Bit wiederholen
 dey                     ;Schleife runterzählen
 bpl binoutloop          ;bis 8-Bit verarbetet sind -> binoutloop
 pla                     ;sonst, den 'alten' Akku wiederherstellen
 rts                     ;und zurück zum Aufrufer (jsr binaryout) 
 
;************************************************************
;*** Den Inhalt des Akkus als Hexzahl auf dem BS ausgeben
;************************************************************
;*** Übergabe: A = Zahl, die ausgegeben wird
;***           X = Zeile in der die Ausgabe beginnt
;***           Y = Spalte in der die Ausgabe beginnt
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   X,Y,SR
;************************************************************
hexout
 pha                     ;Akku auf dem Stack merken (wg. SETCURSOR)
 clc                     ;C=0 für set / C=1 für get Cursor      
 jsr SETCURSOR           ;Jump-Table: get/set cursor
 lda #"$"                ;Dollar-Zeichen in den Akku
 jsr CHROUT              ;und ausgeben
 pla                     ;Akku wiederherstellen
 pha                     ;und merken bis zum Rücksprung
 pha                     ;gleich nochmal wg. LSR und CHROUT
 lsr                     ;jetzt msb -> ins lsb verschieben
 lsr                     ;dafür 4* LSR
 lsr
 lsr
 tax                     ;Akku ins X-Register,
 lda possiblehexchars,x  ;das Zeichen fürs obere Nibble holen
 jsr CHROUT              ;und auszugeben
 pla                     ;Akku vom Stack holen
 and #$0f                ;oberes Nibble ausmaskieren
 tax                     ;Akku wieder nach X
 lda possiblehexchars,x  ;um das Zeichen fürs untere Nibble zu holen
 jsr CHROUT              ;und wieder ausgeben
 pla                     ;Ursprünglichen Akkuwert vom Stack holen
 rts                     ;und zurück zum Aufrufer (jsr hexout)

;*** die Hex-Ziffern
possiblehexchars
 !text "0123456789ABCDEF"

Beachtet beim Turbo Assembler wieder, dass oben und im folgenden Verlauf, Label mit mehr als 15 Zeichen vorkommen! Denkt daran, diese zu kürzen.

Vorbereitung

Für unsere Rechenoperationen fügen wir als Erstes die Variablen TEXTADR (Zero-Page-Adresse für den auszugebenen Text) und BINPOS (Spalte in der unsere Ausgabe beginnt) hinzu. Dann brauchen wir noch N1 und N2, in diesen werden wir die Zahlen ablegen, mit denen wir rechnen. Die Variable SCREENPOS könnt ihr löschen.

TEXTADR   = $fb          ;Zero-Page-Adr. für Text-Quelle
BINPOS    = $05          ;Spalte in der unsere Ausgabe beginnt
N1        = %00000001    ;erste Zahl zum rechnen
N2        = %00000001    ;zweite Zahl zum rechnen

Jetzt löschen wir alles zwischen

;*** Start des Assemblerprogramms

und

 rts                     ;zurück zum BASIC

und fügen dazwischen folgendes ein:

 jsr showmask           ;Hauptmaske anzeigen

 lda #$01               ;#$01 = Addition
 jsr showoperator       ;Operant anzeigen

 ;*** rechnen
 lda #N1                ;1. Zahl in den Akku 
 ;<Operation>
 jsr result             ;Ergebnis ausgeben

Soweit nichts Neues für uns. Wir geben zu Beginn unsere (unspektakuläre) Programmaske showmask aus. Anschließend springen wir zur Ausgabe des Textes für die Rechenoperation, dafür laden wir den Akku mit der gewünschten Text-Nr. und springen zu showoperator. Um etwas zu rechnen, laden wir N1 in den Akku. ;<Operation> dient wieder als Platzhalter, das ersetzen wir später durch unseren Rechenbefehl und zum Schluß geben wir das Ergebnis über das Unterprogramm result aus.

Textausgabe

Unsere bisherige Textausgabe ab runtextout könnt ihr komplett löschen. Wir verwenden jetzt diese allgemeingültige Textausgabe-Routine:

;************************************************************
;*** Beliebigen Null-Terminierten Text ausgeben
;************************************************************
;*** Übergabe: X = Zeile in der die Ausgabe beginnt
;***           Y = Spalte in der die Ausgabe beginnt
;***           TEXTADR = An dieser Zero-Page-Adresse wird die
;***                     Adresse des Textes hinterlegt.
;***
;***           Das Textende wird durch $00 gekennzeichnet! 
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
textout
 clc                     ;C=0 für set / C=1 für get Cursor      
 jsr SETCURSOR           ;Jump-Table: get/set cursor
 ldy #$00                ;Pos. im Text

textcharin 
 lda (TEXTADR),Y         ;Aktuelles Zeichen in den Akku laden
 beq textdone            ;wenn 0 dann sind wir fertig
 jsr CHROUT              ;Jump-Table: Zeichenausgeben
 iny                     ;Y erhöhen, für nächstes Zeichen
 jmp textcharin          ;und wieder hochspringen

textdone                 ;Ziel, sobald wir eine 0 haben
 rts                     ;zurück zum Aufrufer (jsr textout)

;*** unsere Texte
textadd
 !text "  +"
 !byte $00

textflags
 !text "NV-BDIZC"
 !byte $00

textline
 !text "========="
 !byte $00

Unsere neue Textausgabe soll (wie bisher) den Cursor an die im X– & Y-Register angegebenen Position setzten und einen Text bis zum $00 ausgeben. Jetzt soll aber die Adresse des Textes übergeben werden, damit wir beliebige Texte ausgeben können. Dazu hinterlegen wir vor dem Sprung zu textout die entsprechende Adresse auf der Zero-Page bei TEXTADR.
Das einzig wirklich Neue ist, dass wir jetzt hinter textcharin, unseren Text über eine auf der Zero-Page hinterlegte Adresse finden. Wir laden diese mit der Y-nach-indizierten-Adressierung in den Akku. Der Rest sollte eigentlich kein Problem für euch sein.
Zum Schluß findet ihr noch verschiedene Labels mit den Texten für unser erstes Beispiel.

Maske

Kommen wir zur Ausgabe unserer Maske.

;************************************************************
;*** Unsere Maske ausgeben
;************************************************************
;*** Übergabe: -
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
showmask
 ;BS löschen        
 lda #$93
 jsr CHROUT

 ;1. Zahl ausgeben
 lda #N1
 ldx #$08
 ldy #BINPOS
 jsr binaryout

 ;2. Zahl ausgeben
 lda #N2
 ldx #$09
 ldy #BINPOS
 jsr binaryout

 ;Überschrift für Flags
 lda #<textflags
 sta TEXTADR
 lda #>textflags
 sta TEXTADR+1

 ldx #$0a
 ldy #BINPOS+11
 jsr textout

 ;========= ausgeben 
 lda #<textline
 sta TEXTADR
 lda #>textline
 sta TEXTADR+1

 ldx #$0a
 ldy #BINPOS
 jsr textout

 rts

Die wenigen Kommentare sagen doch eigentlich schon alles, oder? Nach dem Löschen des BS, geben wir unsere beiden Zahlen über binaryout aus. Wie ihr seht, können wir aus einem Unterprogramm, auch in ein anderes springen. Wir müssen natürlich darauf achten, dass das Unter-Unterprogramm nicht unser erstes Unterprogramm beeinflusst, indem z. B. Register oder Flags geändert werden. Zum Schluß geben wir noch zwei Texte aus, indem wir die jeweilige Adresse in die Zero-Page nach TEXTADR schreiben und das X– & Y-Register mit der gewünschten Cursor-Position füllen.

Operanten ausgeben

Das wars dann auch schon, kommen wir zur Funktion um unseren Operanten auszugeben.

;************************************************************
;*** Operator ausgeben
;************************************************************
;*** Übergabe: A = Nr. des Operanten
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
showoperator
 cmp #$01

CMP: CoMPare (vergleiche mit Akku)
CMP unmittelbar ($C9, 2B, 2T, NZC)
Mit dem CMP-Befehl vergleichen wir den Akkuinhalt, mit dem angegebenen Wert. Dabei wird vom Akku der Wert des CMPs (also was hinter diesem steht) abgezogen. Es werden nur die Flags gesetzt, es gibt keine Veränderung im Akku oder im Speicher! Wir können mit dem Befehl erkennen, ob der Wert im Akku kleiner, größer oder gleich dem beim CMP angegebenen Wert (oder dem an der Adresse s. u.) ist.
Nehmen wir einfach mal an, im Akku sei #$20 gespeichert, dann werden die betroffenen Flags bei unterschiedlichen CMP-Werten wie folgt gesetzt.

      Flags
 CMP  N Z C    #$20 im Akku
#$10  0 0 1    Akku ist größer als  > CMP #$10
#$20  0 1 1    Akku ist gleich      = CMP #$20
#$30  1 0 0    Akku ist kleiner als < CMP #$30

Wir prüfen direkt hinter showoperator mit CMP #$01, ob im Akku eine #$01 liegt.

 bne showoperatornext_1
 lda #<textadd
 sta TEXTADR
 lda #>textadd
 sta TEXTADR+1
 jmp showoperatortextout

showoperatornext_1
 rts

showoperatortextout 
 ldx #$09
 ldy #$01
 jsr textout
 rts

Wenn im Akku keine #$01 liegt, springen wir zur nächsten Prüfung showoperatornext_1 (hier ergänzen wir später, z. Zt. verlassen wir einfach das Unterprogramm). Haben wir aber eine #$01 im Akku, dann laden wir den Text für die Addition textadd und springen zur Ausgabe showoperatortextout. Dort laden wir die Cursorposition ins X– und Y-Register und springen für die Textausgabe ins Unterprogramm textout. Danach wird unser Unterprogramm direkt verlassen.

Ergebnis anzeigen

Kommen wir zum letzten Unterprogramm, die Ausgabe des Ergebnisses.

;************************************************************
;*** Das Ergebnis ausgeben
;************************************************************
;*** Übergabe: A = Ergebnis der Operation
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
result
 php                    ;Flags für Anzeige auf den Stack

PHP: PusH Processor-Status to stack (Statusregister {SR} auf den Stack legen)
PHP implizit ($08, 1B, 3T, <keine>)
Neben dem uns bereits bekannten Befehl PHA, um den Akku auf den Stack zu legen, gibt es auch die Möglichkeit, die Flags auf dem Stack zu speichern. Das ist wichtig für den Fall, dass man zwischen dem Setzen und Auswerten der Flags noch etwas Anderes erledigen möchte.
Wir springen direkt nach unserer Operation zu result, um das Ergebnis, das ja im Akku steht, anzuzeigen. Da wir uns aber auch die Flags ausgeben möchten, müssen wir diese erstmal auf dem Stack zwischenparken.

 ;*** Ergebnis im Akku ausgeben
 ldx #$0b
 ldy #BINPOS
 jsr binaryout

 ;*** Flags ausgeben
 pla
 ldx #$0b
 ldy #BINPOS+10
 jsr binaryout

 rts

Dann füllen wir das X– und Y-Register mit unserer Cursorposition und geben das Ergebnis aus dem Akku über binaryout aus. Jetzt wird es Zeit unsere Flags anzuzeigen. Da unsere Ausgaberoutine für Binärzahlen, die Zahl im Akku erwartet, holen wir die Flags mit PLA vom Stack in den Akku. Wie ihr seht, ist es egal, wie die Daten auf dem Stack gelandet sind, der jeweilige Befehl holt sich die Daten einfach. Dann noch die Cursoposition festlegen und nochmal zu binaryout springen.

Ein erster Start

Und hier unser komplettes Programm, bevor wir den ersten Operator einfügen.

CHROUT    = $ffd2        ;Jump-Table Adr.: Zeichenausgabe
SETCURSOR = $fff0        ;Jump-Table Adr.: get/set cursor pos
TEXTADR   = $fb          ;Zero-Page-Adr. für Text-Quelle
BINPOS    = $05          ;Spalte in der unsere Ausgabe beginnt
N1        = %00000001    ;erste Zahl zum rechnen
N2        = %00000001    ;zweite Zahl zum rechnen

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

;*** Start des Assemblerprogrammes
 jsr showmask             ;Hauptmaske anzeigen

 lda #$01                 ;#$01 = Addition
 jsr showoperator         ;Operant anzeigen

 ;*** rechnen
 lda #N1                  ;1. Zahl in den Akku 
 ;<Operation>
 jsr result               ;Ergebnis ausgeben
 
 rts                      ;zurück zum BASIC

 
;************************************************************
;*** Beliebigen Null-Terminierten Text ausgeben
;************************************************************
;*** Übergabe: X = Zeile in der die Ausgabe beginnt
;***           Y = Spalte in der die Ausgabe beginnt
;***           TEXTADR = An dieser Zero-Page-Adresse wird die
;***                     Adresse des Textes hinterlegt.
;***
;***           Das Textende wird durch $00 gekennzeichnet! 
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
textout
 clc                     ;C=0 für set / C=1 für get Cursor      
 jsr SETCURSOR           ;Jump-Table: get/set cursor
 ldy #$00                ;Pos. im Text

textcharin 
 lda (TEXTADR),Y         ;Aktuelles Zeichen in den Akku laden
 beq textdone            ;wenn 0 dann sind wir fertig
 jsr CHROUT              ;Jump-Table: Zeichenausgeben
 iny                     ;Y erhöhen, für nächstes Zeichen
 jmp textcharin          ;und wieder hochspringen

textdone                 ;Ziel, sobald wir eine 0 haben
 rts                     ;zurück zum Aufrufer (jsr textout)

;*** unsere Texte
textadd
 !text "  +"
 !byte $00

textflags
 !text "NV-BDIZC"
 !byte $00

textline
 !text "========="
 !byte $00
 
 
;************************************************************
;*** Unsere Maske ausgeben
;************************************************************
;*** Übergabe: -
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
showmask
 ;BS löschen        
 lda #$93
 jsr CHROUT

 ;1. Zahl ausgeben
 lda #N1
 ldx #$08
 ldy #BINPOS
 jsr binaryout

 ;2. Zahl ausgeben
 lda #N2
 ldx #$09
 ldy #BINPOS
 jsr binaryout

 ;Überschrift für Flags
 lda #<textflags
 sta TEXTADR
 lda #>textflags
 sta TEXTADR+1

 ldx #$0a
 ldy #BINPOS+11
 jsr textout

 ;========= ausgeben 
 lda #<textline
 sta TEXTADR
 lda #>textline
 sta TEXTADR+1

 ldx #$0a
 ldy #BINPOS
 jsr textout

 rts
 
 
;************************************************************
;*** Operator ausgeben
;************************************************************
;*** Übergabe: A = Nr. des Operanten
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
showoperator
 cmp #$01
 bne showoperatornext_1
 lda #<textadd
 sta TEXTADR
 lda #>textadd
 sta TEXTADR+1
 jmp showoperatortextout

showoperatornext_1
 rts

showoperatortextout 
 ldx #$09
 ldy #$01
 jsr textout
 rts

 
;************************************************************
;*** Das Ergebnis ausgeben
;************************************************************
;*** Übergabe: A = Ergebnis der Operation
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   A,X,Y,SR
;************************************************************
result
 php                    ;Flags für Anzeige auf den Stack
 ;*** Ergebnis im Akku ausgeben
 ldx #$0b
 ldy #BINPOS
 jsr binaryout

 ;*** Flags ausgeben
 pla
 ldx #$0b
 ldy #BINPOS+10
 jsr binaryout

 rts 
 
 
;************************************************************
;*** Den Inhalt des Akkus als Binärzahl auf dem BS ausgeben
;************************************************************
;*** Übergabe: A = Zahl, die ausgegeben wird
;***           X = Zeile in der die Ausgabe beginnt
;***           Y = Spalte in der die Ausgabe beginnt
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   X,Y,SR
;************************************************************
binaryout
 pha                     ;Akku auf dem Stack merken (wg. SETCURSOR)
 clc                     ;C=0 für set / C=1 für get Cursor
 jsr SETCURSOR           ;Jump-Table get/set cursor
 lda #"%"                ;Prozent-Zeichen in den Akku
 jsr CHROUT              ;und ausgeben
 pla                     ;Akku wieder vom Stack holen
 pha                     ;und direkt nochmal merken
                         ;(bis zum Rücksprung)

 ldy #$07                ;Schleife rückwärts von Bit 7 bis 0

binoutloop
 ldx #"0"                ;Zeichen "0" ins X-Register
 asl                     ;Akku nach <-links verschieben
 bcc out                 ;Wenn es eine 0 ist, direkt zur Ausgabe, 
 inx                     ;sonst erhöhen, damit wir eine 1 haben.
out                      ;Sprungziel, wenn wir eine 0 haben
 pha                     ;Akku merken, er wird gleich überschrieben
 txa                     ;X -> Akku; Zeichen in den Akku
 jsr CHROUT              ;Jump-Table: Zeichenausgeben
 pla                     ;Akku fürs nächste Bit wiederholen
 dey                     ;Schleife runterzählen
 bpl binoutloop          ;bis 8-Bit verarbetet sind -> binoutloop
 pla                     ;sonst, den 'alten' Akku wiederherstellen
 rts                     ;und zurück zum Aufrufer (jsr binaryout) 
 
 
;************************************************************
;*** Den Inhalt des Akkus als Hexzahl auf dem BS ausgeben
;************************************************************
;*** Übergabe: A = Zahl, die ausgegeben wird
;***           X = Zeile in der die Ausgabe beginnt
;***           Y = Spalte in der die Ausgabe beginnt
;************************************************************
;*** Rückgabe: -
;************************************************************
;*** ändert:   X,Y,SR
;************************************************************
hexout
 pha                     ;Akku auf dem Stack merken (wg. SETCURSOR)
 clc                     ;C=0 für set / C=1 für get Cursor      
 jsr SETCURSOR           ;Jump-Table: get/set cursor
 lda #"$"                ;Dollar-Zeichen in den Akku
 jsr CHROUT              ;und ausgeben
 pla                     ;Akku wiederherstellen
 pha                     ;und merken bis zum Rücksprung
 pha                     ;gleich nochmal wg. LSR und CHROUT
 lsr                     ;jetzt msb -> ins lsb verschieben
 lsr                     ;dafür 4* LSR
 lsr
 lsr
 tax                     ;Akku ins X-Register,
 lda possiblehexchars,x  ;das Zeichen fürs obere Nibble holen
 jsr CHROUT              ;und auszugeben
 pla                     ;Akku vom Stack holen
 and #$0f                ;oberes Nibble ausmaskieren
 tax                     ;Akku wieder nach X
 lda possiblehexchars,x  ;um das Zeichen fürs untere Nibble zu holen
 jsr CHROUT              ;und wieder ausgeben
 pla                     ;Ursprünglichen Akkuwert vom Stack holen
 rts                     ;und zurück zum Aufrufer (jsr hexout)

;*** die Hex-Ziffern
possiblehexchars
 !text "0123456789ABCDEF"

Startet das Programm und ihr solltet folgendes sehen:

Achtung! Das Ergebnis stimmt nicht, da noch nicht ‚gerechnet‚ wurde!!!

Wir sehen die 1. und 2. Zahl binär untereinander. Vor der zweiten steht unser Operator (hier +). Unter der Zeile mit dem Gleichzeichen steht unser Ergebnis. Beachtet, dass noch nicht gerechnet wurde und wir daher nur die erste Zahl als Ergebnis sehen, die noch im Akku steht!
Weiter rechts sehen wir unsere Flags. Wir können dort kontrollieren, wie sich unsere Operation auf die Flags ausgewirkt hat. Für uns sind jetzt erstmal die Flags NVZC interessant.

Addition

Dann wollen wir mal rechnen:
Vorher aber nochmal der Hinweis, dass Wie der Rechner rechet immens wichtig fürs Verständnis der nächsten Zeilen ist! Dort wurde bereits erklärt, was genau vorgeht und wie die Flags verändert werden. Hier schauen wir uns das nur nochmal an Beispielen an.

Ersetzt jetzt bitte ;<Operation> mit

 adc #N2

ADC: ADd with Carry (addiere mit Carry-Flag)
ADC unmittelbar ($69, 2B, 2T, NVZC)
Wir haben mit dem ADC-Befehl die Möglichkeit, eine beliebige Zahl zum Akku hinzu zu addieren. Das Ergebnis landet dann wiederum im Akku und es werden die Flags NVZC verändert.

Die genauen Auswirkungen schauen wir uns jetzt mal im Beispiel an.
Wenn ihr das Programm auf einem frisch eingeschaltetem Rechner startet, dann wird %00000001 + %00000001 gerechnet und wir kommen auf %00000010. Die für uns wichtigen Flags NVZC bleiben auf 0.

Warum habe ich jetzt so auf den frisch eingeschalteten Rechner hingewiesen?
Dafür gibt es zwei Gründe:
Der erste ist, dass ADC nicht nur das Carry-Flag setzt, sondern es auch mit addiert! Wir haben bisher mit CLC das C-Flag gelöscht, um z. B. die Cursorposition zu setzen. Daher hat unsere Addition hier das gewünschte Ergebnis geliefert. Was passiert, falls das Carry-Flag gesetzt ist, schauen wir uns jetzt mal an, indem wir vor adc #N2 die folgende Zeile einfügen.

 sec

Damit setzen wir das Carry-Flag auf 1. Startet jetzt das Programm, dann kommt plötzlich %00000011 heraus! Wir sehen also, dass das Carry-Flag mitaddiert wurde. Da wir lieber ganz sicher gehen wollen sollten wir vor einer Addition immer das Carry-Flag löschen. Ändert doch bitte sec in clc, um ab jetzt immer für ein gelöschtes C-Flag vor der Addition zu sorgen.
Der zweite Grund ist das Decimal-Flag. Schauen wir uns das doch auch nochmal an. Wir ändern N1 dafür in %00001001 und starten das Programm. Wir erhalten %00001010, was wir auch erwartet haben.

Fügt jetzt hinter clc die folgende Zeile ein.

 sed

SED: SEt Decimal-Flag (das Dezimal-Flag setzen)
SED implizit ($F8, 1B, 2T, D)
Aktivieren des BCDinfoInfoBinary Coded Digit-Modes mit dem SED-Befehl. Im BCD-Modus werden Dezimalzahlen in jeweils einem Nibble kodiert, d. h. je Nibble sind statt bisher 0-f nur noch die Zahlen 0-9 kodiert. Wir verschwenden da natürlich Speicherplatz. Umso größer die Zahlen sind, desto mehr Speicher verlieren wir. Aber beim Umgang mit Dezimalzahlen, kann der BCD-Mode eine erhebliche Erleichterung sein.
Startet jetzt noch mal das Programm, als Ergebnis wird uns %00010000 ausgeworfen, natürlich ist auch das D-Flag gesetzt. Nach den Ausführungen von eben, sollte euch das Ergebnis jetzt klar sein. Wir erhalten nach %00001001 + %00000001 wieder dezimal 10, nur dass diesmal das Ergebnis im BCD-Format vorliegt. Im ersten Nibble steht die 1 im zweiten die 0.
Wie übel ein fälschlich aktivierter BCD-Modus sein kann, seht ihr, sobald ihr den sed-Befehl mal hinter ;*** Start des Assemblerprogrammes einfügt. Falls ihr direkt auf dem C64 oder im Emulator entwickelt, speichert unbedingt vor dem Start des Programms!!
Es ist daher eine gute Idee, beim Programmbeginn dafür zu sorgen, dass der BCD-Modus deaktiviert ist und ihn nur bei Bedarf anzuschalten.
Löscht alle evtl. vorhandenen sed-Befehle von eben und tragt direkt hinter ;*** Start des Assemblerprogrammes den nächsten Befehl ein:

 cld

CLD: CLear Decimal-Flag (das Dezimal-Flag löschen)
CLD implizit ($D8, 1B, 2T, D)
Um den BCD-Modus zu deaktiveren, verwenden wir den CLD-Befehl.

Überprüft durch einen Start bitte, dass wieder %00001010 als Ergebnis angezeigt wird und das D-Flag tatsächlich gelöscht ist!

Machen wir weiter und schauen uns an, wann welche Flags gesetzt werden.
Ändert N1 in %01110000 und N2 in %00010000. Durch diese Addition werden gleich zwei Flags verändert, wie ihr nach einem Start seht. Das Ergebnis %10000000 ist bekanntlich negativ (Bit-7 ist gesetzt), daher steht auch das N-Flag auf 1. Außerdem hat ein unerwarteter Vorzeichenwechsel stattgefunden (wir addieren schließlich zwei positive Zahlen), deshalb wurde auch noch das OVerflow-Flag gesetzt.

Wenn wir jetzt in N1 mal %11110000 hinterlegen und das Programm erneut starten, sehen wir, dass wieder zwei Flags verändert wurden. Als erstes fällt auf, dass als Ergbnis %00000000 heraus kommt, daher ist das Zero-Flag gesetzt. Aber warum kommt 0 raus? Das Problem ist, dass das Ergbnis nicht mehr in 8-Bit passt. Wenn dieser Umstand eintritt, kommt es zu einem Übertrag und das C-Flag wird gesetzt. Genau das ist hier passiert.

Wie ihr zu Beginn gesehen habt, addiert ADC auch das Carry-Flag. Mit diesem Wissen könnt ihr euch evtl. vorstellen, wie man eine 16-, 24- oder 64-Bit Addition realisieren kann. Man löscht das Carry-Flag vor der ersten Addition und addiert dann vom niedrigsten Byte zum höchsten die beiden Zahlen inkl. C-Flag.

1. Zahl: $78da 
2. Zahl: $1234

Carry-Flag löschen
$da in den Akku laden
$34 addieren => C = 1
Akku-Inhalt speichern = $0e

$78 in den Akku laden
$12 addieren
Akku-Inhalt speichern = $8b, da das C-Flag gesetzt war!

Im Speicher steht nun unser 16-Bit Ergebnis: $8b0e

Das in ein Programm zu überführen, überlasse ich zunächst euch. Um den Rahmen dieses Beitrags nicht zu sprengen verschiebe ich das auf später, nach Abschluß des Assembler Tutorials werden wir darauf zurückkommen.

Zieh ab! Äh, das Abziehen ist unser nächstes Thema: Subtraktion

Lasst uns erstmal unser Programm anpassen, sodass auch das Minuszeichen ausgegeben wird.

Sucht die Stelle, an der hinter textadd das Pluszeichen zufinden ist und fügt dort einen Block fürs Minuszeichen ein (wo genau ist eigentlich egal, aber alle Texte gesammelt zu verwalten ist evtl. hilfreich).

textsub
 !text "  -"
 !byte $00

Damit dieser Text ausgegeben werden kann müssen wir nun das Unterprogramm showoperator anpassen. Fügt zwischen showoperatornext_1 und dem rts die nächsten Zeilen ein (diese beiden Stellen sind unten markiert).

showoperatornext_1
 cmp #$02
 bne showoperatornext_2
 lda #<textsub
 sta TEXTADR
 lda #>textsub
 sta TEXTADR+1
 jmp showoperatortextout

showoperatornext_2
 rts

Wenn cmp #$01 oben nicht zutrifft, dann landen wir bei showoperatornext_1 und können direkt wieder den Akku mit cmp #$02 prüfen, da dieser ja durch CMP nicht verändert wird!

Jetzt müssen wir noch lda #$01 ;#$01 = Addition in lda #$02 ;#$01 = Addition | #$02 = Subtraktion ändern und schon wird unser Minsuzeichen angezeigt.

Fangen wir wieder klein an und setzen unsere Variablen N1 und N2 beide auf %00000001.

Ersetzen wir jetzt adc #N2 durch den Befehl für die Subtraktion:

 sbc #N2

SBC: SUbtract with Carry (subtrahiere mit Carry-Flag)
SBC unmittelbar ($E9, 2B, 2T, NVZC)
Wir haben hier das Gegenstück zum ADC. Daher verhält sich SBC auch sehr ähnlich. Es wird vom Akku der angebenen Wert (oder der an der angegebenen Adresse) abgezogen. Dabei werden auch wieder die Flags NVZC verändert. Natürlich muss beim SBC ebenfalls darauf geachtet werden, ob der BCD-Modus gewünscht ist.

Starten wir doch einfach mal das Programm und ziehen von %00000001 nochmal %00000001 ab. Ok, es kommt %11111111 heraus, daher ist auch das N-Flag gesetzt. Hat ja wunderbar geklappt. Was? Das muss doch Null sein, oder?
Wie erwähnt, ist SBC sehr ähnlich zum ADC, somit hat auch hier das Carry-Flag eine besondere Bedeutung. Beim SBC ist die Verwendung des C-Flags etwas komplizierter. Wir merken uns einfach, dass beim SBC Carry auf 1 gesetzt werden muss und beim ADC auf 0! Ändern wir also clc in sec und erfreuen uns daran, dass nun (nach einem Programmstart) das korrekte Ergebnis %00000000 angezeigt wird und deshalb auch das Zero-Flag gesetzt wurde. Um einen OVerflow zu bekommen könnt ihr jetzt z. B. von %10000000 einfach mal %01111111 abziehen.

Auch hier lassen sich durch eine wiederholte Subtraktion 16-Bit oder größere Zahlen verarbeiten.

Multiplikation & Division

So traurig es ist, aber der 6510 bietet keine Befehle für die Multiplikation bzw. Division. Diese könnt ihr z. B. durch eine wiederholte Addition oder Subtraktion nachbilden. Das werden wir später auch mal machen, aber jetzt ist weder der richtige Zeitpunkt noch Platz.
Gibt es beim C64 wirklich überhaupt keine Multiplikations- und Divisionsbefehle, fragt ihr euch jetzt vielleicht? Wer aufgepasst hat und die bisher gelernten Befehle Revue passieren lässt, dem fällt evtl. auf, dass wir sogar schon die Division verwendet haben. Es gibt zwei Befehle, die eine Multiplikation / Division mit 2 ermöglichen. Na, kommt ihr drauf? Richtig! ASL und LSR. Beim verschieben der Bits nach links verdoppelt sich der Wert, nach rechts wird er halbiert (durchs Carry-Flag können wir sogar erkennen, ob die Division glatt auf geht).

Weitere Adressierungsarten

Und schon sind wir wieder bei der Liste der weiteren Adressierungsarten für die neuen Befehl.

Wir haben ja mit PHP das Statusregister (SR) auf den Stack gelegt, da wir den Wert dann aber in den Akku zurückgeholt haben, fehlt uns noch das Gegenstück zum PHP.

 plp

PLP: PuLl Processor-Status from Stack (SR vom Stack holen)
PLP implizit ($28, 1B, 4T, ALLE)
Mit PLP setzen wir also alle Flags im Statusregister (SR) mit dem Inhalt des Bytes vom Stack. Natürlich bleibt Bit-5 auch jetzt immer auf 1, aber die anderen Flags werden verändert. Wir müssen also vorsichtig mit dem Befehl umgehen. Sonst aktivieren wir versehentlich den BCD-Modus oder sperren Interrupts.


Da ADC und SBC sich sehr ähnlich sind, behandel ich beide gleichzeitig.

 adc $0821

ADC absolut ($6D, 3B, 4T, NVZC)
Addiert zum Akku den Wert des Bytes, an der angegebenen absoluten Adresse.

 sbc $0821

SBC absolut ($ED, 3B, 4T, NVZC)
Subtrahiert das Byte, an der angegebenen absoluten Adresse vom Akku.

 adc $0821,x

ADC absolut X-indiziert ($7D, 3B, 4-5T, NVZC)
Addiert zum Akku den Wert des Bytes, das an der angegebenen absoluten Adresse plus X-Register liegt. Beim Überschreiten der Page-Grenze wird ein extra Takzyklus benötigt.

 sbc $0821,x

SBC absolut X-indiziert ($FD, 3B, 4-5T, NVZC)
Subtrahiert das Byte an der angegebenen absoluten Adresse plus X-Register vom Akku. Ein zusätzlicher Taktzyklus wird beim Überscheiten der Page-Grenze fällig.

 adc $0821,y

ADC absolut Y-indiziert ($79, 3B, 4-5T, NVZC)
Addiert zum Akku den Wert des Bytes das an der angegebenen absoluten Adresse plus Y-Register liegt. Beim Überschreiten der Page-Grenze wird ein extra Takzyklus benötigt.

 sbc $0821,y

SBC absolut Y-indiziert ($F9, 3B, 4-5T, NVZC)
Subtrahiert das Byte an der angegebenen absoluten Adresse plus Y-Register vom Akku. Ein zusätzlicher Taktzyklus wird beim Überscheiten der Page-Grenze fällig.

 adc $fb

ADC Zero-Page ($65, 2B, 3T, NVZC)
Addiert zum Akku den Wert des Bytes das an der angegebenen Zero-Page-Adresse liegt.

 sbc $fb

SBC Zero-Page ($E5, 2B, 3T, NVZC)
Subtrahiert das Byte an der angegebenen Zero-Page-Adresse vom Akku.

 adc $fb,x

ADC Zero-Page X-indiziert ($75, 2B, 4T, NVZC)
Addiert zum Akku den Wert des Bytes das an der angegebenen Zero-Page-Adresse liegt.

 sbc $fb,x

SBC Zero-Page X-indiziert ($F5, 2B, 4T, NVZC)
Subtrahiert das Byte an der angegebenen Zero-Page-Adresse vom Akku.

 adc ($fb,x)

ADC indirekt X-indiziert ($61, 2B, 6T, NVZC)
Addiert zum Akku den Wert des Bytes, das an der Adresse liegt, die sich an der angegebenen Zero-Page-Adresse plus X-Register befindet.

 sbc ($fb,x)

SBC indirekt X-indiziert ($E1, 2B, 6T, NVZC)
Subtrahiert das Byte an der Adresse, die sich an der angegebenen Zero-Page-Adresse plus X-Register befindet, vom Akku.

 adc ($fb),y

ADC indirekt Y-nach-indiziert ($71, 2B, 5-6T, NVZC)
Addiert zum Akku den Wert des Bytes, das an der Adresse plus Y-Register liegt, die sich an der angegebenen Zero-Page-Adresse befindet. Bei Überschreitung der Page-Grenze wird ein weiterer Taktzyklus benötigt.

 sbc ($fb),y

SBC indirekt Y-nach-indiziert ($F1, 2B, 5-6T, NVZC)
Subtrahiert das Byte an der Adresse plus Y-Register, wobei sich die Adresse an der angegebenen Zero-Page-Adresse befindet, vom Akku. Bei einer Page-Grenzen Überschreitung wird ein Taktzyklus mehr benötigt.

Endlich! Das waren eine ganze Menge Kombinationsmöglichkeiten. Aber wir haben jetzt sämtliche Rechenbefehle des 6510 gesehen.


Wir haben mittlerweile die Adressierungsarten oft genug im Detail kennengelernt, daher liste ich ab jetzt nur noch kurz die Befehle auf. Zum späteren Nachschlagen möchte ich euch auf die Mnemonics verweisen.

Kommen wir zu den CMP-Befehlen. Neben dem Vergleich mit dem Akku CMP, gibt es auch die Möglichkeit mit dem XCPX und Y-Register CPY Vergleiche anzustellen.

 cpx #$1a

CPX: ComPare X-Register (vergleiche mit X-Register)
CPX unmittelbar ($E0, 2B, 2T, NZC)
Wie der CMP-Befehl, nur dass hier der Vergleich mit dem X-Register stattfindet.

 cpy #$1a

CPY: ComPare Y-Register (vergleiche mit Y-Register)
CPY unmittelbar ($C0, 2B, 2T, NZC)
Wie CPX , nur mit dem Y-Register.

 cmp $0821

CMP absolut ($CD, 3B, 4T, NZC)

 cpx $0821

CPX absolut ($EC, 3B, 4T, NZC)

 cpy $0821

CPY absolut ($CC, 3B, 4T, NZC)

 cmp $0821,x

CMP absolut X-indiziert ($DD, 3B, 4-5T, NZC)

 cmp $0821,y

CMP absolut Y-indiziert ($D9, 3B, 4-5T, NZC)

 cmp $fb

CMP Zero-Page ($C5, 2B, 3T, NZC)

 cpx $fb

CPX Zero-Page ($E4, 2B, 3T, NZC)

 cpy $fb

CPY Zero-Page ($C4, 2B, 3T, NZC)

 cmp $fb,x

CMP Zero-Page X-indiziert ($D5, 2B, 4T, NZC)

 cmp ($fb,x)

CMP indirekt X-indiziert ($C1, 2B, 6T, NZC)

 cmp ($fb),y

CMP indirekt Y-nach-indiziert ($D1, 2B, 5-6T, NZC)

So, damit haben wir nach den Rechenbefehlen, auch alle Vergleichsbefehle des 6510 kennengelernt.


Für den Moment lassen wir es erstmal gut sein. Weiterführende Rechenoperationen (große Zahlen, Multiplikation, Division und Kommazahlen) sind ein Thema für einen späteren Beitrag.

Wir kümmern uns als nächstes um die booleschen Befehle.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (11 Bewertungen | Ø 4,82 von 5 | 96,36%)

Loading...


ZurückWeiter

3 Gedanken zu „Mit dem Rechner rechnen“

  1. Der decimal-mode wird nur noch von einigen wenigen CPUs unterstützt.
    Dabei ist er enorm wichtig für Banking, Telefonabrechnung, Taschenrechner und Funkuhren und zur korrekten Rundung.
    Ich bitte um ein eigenes Kapitel

  2. Hi,
    kleinen Typo gefunden.
    Du hast 2 mal hintereinander cmp $0821,x als neuen Befehl, das zweite mal sollte es aber cmp $0821,y heißen.

    Grüße,
    David

Schreibe einen Kommentar

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

Protected by WP Anti Spam