Magic Disk 64

home to index
         Assembler-Kurs Teil 1          
        -----------------------         

EINLEITUNG:
Dieser Assembler-Kurs soll allen BASIC-Programmierern den Einstieg in Assembler ermöglichen.
Ich werde mich in der Folge darum bemühen, die komplizierten Fachbegriffe zu umgehen und leicht verständliche Erklärungen zu geben.
Zunächst sollen innerhalb dieses Kurses alle Assembler-Befehle behandelt werden.
In späteren Teilen wird dann auf die Verwendung von Kernal-Routinen und die Interrupt-Technik eingegangen.
Zu jedem Teil des Kurses befindet sich ein Programm auf der zweiten Seite der Diskette (" ASSEMBLER-KURS 1"), das einige Assembler-Routinen enthält und Ihnen deshalb gleich die Auswirkungen der jeweiligen Assembler-Befehle demonstrieren kann.
DAS WERKZEUG:
Zum Programmieren in Assembler benötigen Sie unbedingt ein Programm, das die Befehle in reine Maschinensprache umwandelt. Diese Programme werden Assembler oder Monitore genannt. Ein solches Programm finden Sie unter anderem auf der Rückseite dieser Ausgabe ( WIZ-MON) ; die Beschreibung dazu entnehmen Sie der entsprechenden Rubrik.

LDA, STA und BRK:                       
----------------                        

Und jetzt können wir schon mit den ersten Befehlen loslegen.
Wir fangen an mit LDA ( load accumulator) Der Akkumulator ( oder kurz Akku genannt) ist eine Art Verteilerstelle. Mit diesem Befehl lädt man also einen Wert ( Zahl) in den Akku.
STA ( store accumulator) schreibt diesen Akkuinhalt dann in die angegebene Speicherstelle.
Der Befehl BRK entspricht etwa dem BASIC-Befehl STOP und sollte zunächst immer am Ende Ihrer ersten Programmierveruche mit Assembler stehen.
LDA und STA sind durchaus mit PEEK und POKE aus dem BASIC vergleichbar.

Beispiel: POKE 8192,8                   

Dieser Befehl schreibt also den Wert 8 in die Speicherstelle 8192 . Eine Routine in Assembler, die die gleiche Aufgabe erfüllt, lautet:

          LDA #$08                      
          STA $2000                     
          BRK                           

Lassen Sie sich nicht dadurch verwirren, daß hier Hexadezimalzahlen ( Erklärung im Programm!) verwendet werden.
Das Zeichen "#" in der ersten Zeile zeigt an, daß eine konstante Zahl und nicht der Inhalt einer Speicherzelle in den Akku geladen wird. Die hexadezimale Zahl $2000 entspricht haargenau der dezimalen Zahl 8192 .
Zunächst wird also die Zahl 8( bzw.$08) in den Akku geladen. In der zweiten Zeile wird dieser Wert in der Adresse 8192($2000) abgelegt.
Ein anderes Beispiel:
Nach der Eingabe von POKE 12288, PEEK (8192) hätte die Adresse 12288 ebenfalls den Inhalt 8 .

Die   entsprechenden   Assembler-Befehle
lauten:  LDA $2000                      
         STA $3000                      
         BRK                            

Mit LDA wird diesmal keine Zahl, sondern der Inhalt der Speicherstelle $2000( ähnlich PEEK(8192) in den Akku geladen.
STA legt den Akkuinhalt daraufhin bei $3000( dezimal 12288) ab.

DER UMGANG MIT DEM MONITOR:             

Eingegeben werden die Assembler-Befehle mit einem Monitor durch:
A Speicherstelle Assembler-Befehl z. B. : A C000 LDA #$08 Wenn Sie Ihre eingegebenen Befehlsfolgen noch einmal betrachten wollen, so können Sie dies mit " D Speicherstelle"( z. B. :
D C000) tun.
Es wird Ihnen dann bei unserem Beispiel folgende Zeile gezeigt:
. C000 A908 LDA #$08 Die Zahl C000 ist die Speicherstelle, in der der Befehl steht. A9 ist die Codezhl für den LDA-Befehl. Die darauffolgende Zahl zeigt den Wert an, der in den Akku geladen wird. Uns brauchen zunächst nur die Assembler-Befehle im Klartext zu interessieren.
Laden Sie nun das Beispielprogramm " ASSEMBLER-KURS 1" von der zweiten Seite. Es wird Ihnen weitere Hilfen zu den ersten Assembler-Befehlen und zum hexadezimalen Zahlensystem geben.
In der nächsten Ausgabe behanden wir die Indexregister und die dazugehörigen Assmblerbefehle.
Bis dahin wünschen ich Ihnen viel Spaß beim ausprobieren der gelernten Befehle!

                                    (rt)
      Maschinensprache-Kurs Teil 2      
     ------------------------------     

Diesmal werden gleich 15 neue, aber dafür doch recht einfache Befehle vorgestellt.
Vorher müssen wir uns jedoch etwas näher mit den Indexregistern ( X-Register und Y-Register) beschäftigen: Jedes dieser beiden Register kann ein Byte aufnehmen, d. h. Werte zwischen $00 und $ FF. Sie werden hauptsächlich im Zusammenhang mit den verschiedenen Adressierungsarten verwendet.

LDX                                     

Beispiel: LDX #$0 a oder LDX $1000 LDX entspricht in seiner Form dem schon bekannten LDA-Befehl.
LDY --- ist der entsprechende Befehl für das Y-Register.
STX/ STY ------- erinnern sehr stark an den STA-Befehl. Und so ist es auch. Bei STX bzw.
STY wird der Inhalt des Xbzw. Y-Regi- sters in die angegebene speicherstelle geschrieben.
Sie bemerken sicherlich die enge Beziehung zwischen dem Akku und den Indexregistern. Für einen schnelleren Datenaustausch zwischen diesen Registern sind die Befehle TAX, TXA, TAY und TYA verantwortlich.
TAX/ TAY ------- Der Inhalt des Akkus wird im X-Register abgelegt. Dadurch verändert sich jedoch der Inhalt des Akkumulators nicht. Für das Y-Register lautet der Befehl TAY.
TXA/ TYA ------- ist die Umkehrung des TAX bzw.
TAY. Nun wird der Inhalt des Xbzw.
Y-Registers in den Akku geschrieben, ohne daß sich das Xbzw. Y-Register verändert.
DEX, DEY und DEC ---------------- Während DEX und DEY das jeweilige Indexregister um 1 erniedrigen, wirkt der DEC-Befehl auf die nach ihm folgende Speicherstelle. Auch von deren Inhalt wird der Wert 1 abgezogen.
Der Akku ist dvon nicht betroffen.
INX, INY und INC ---------------- Diese Assemblerbefehle bewirken genau das Gegenteil der eben vorgestellten. Hier werden die jeweiligen Register um den Wert 1 erhöht.
Was passiert aber, wenn z. B. im X-Register der Wert $ FF steht und der Prozessor auf den Befehl INX trifft ?
Da ein Byte keinen größeren Wert als $ FF annehmen kann, wird das X-Register kurzerhand auf $00 gesetzt. Ähnlich sieht es bei $00 und einem DEX aus. Dann wird einfach wieder " oben" angefangen und $ FF gesetzt.
RTS --- steht gewöhnlich am Ende eines Assemblerprogrammes oder Unterprogrammes und soll künftig unser BRK ersetzen.
Wenn ein Programm auf den RTS-Befehl trifft, wird es nicht mehr unterbrochen, sondern es wird ins Basic gesprungen.
Laden Sie nun den zum Kurs gehörende Programm aus dem GAMESMENUE oder direkt von der ersten Seite dieser MAGIC DISK 64- Ausgabe. Es werden Ihnen die hier vorgestellten Befehle noch einmal verdeutlicht.
Auf der nächsten Seite folgt eine Zusammenstellung der neuen Befehle.
Ich wünsche Ihnen viel Erfolg beim Ausprobieren der neu erlernten Befehle.

In der nächsten Ausgabe der  MAGIC  DISK
64 behandeln wir  Vergleichsbefehle  und
die bedingte Verzweigung.               
LDX (LoaD X-register)                   
LDY (LoaD Y-register)                   
STX (STore X-register)                  
STY (STore Y-register)                  
DEC (DECrement memory)                  
DEX (DEcrement X-register)              
DEY (DEcrement Y-register)              
INC (INCrement memory)                  
INX (INcrement X-register)              
INY (INcrement Y-register)              
TAX (Transfer Accu into X)              
TAX (Transfer Accu into Y)              
TXA (Transfer X into Accu)              
TYA (Transfer Y into Accu)              
RTS (ReTurn from Subroutine)            
----------------------------------------
                                    (wk)
          Assembler-Kurs Teil 3         
         -----------------------        

Diesmal geht es in unserem Kurs um die vergleichsbefehle und die bedingte Verzweigung. Dafür muß jedoch zunächst das Statusregister des Prozessors näher erklärt werden.
Dieses 8- Bit-Register bildet die Grundlage für die bedingte Verzweigung.
Die einzelnen Bits des Statusregisters nennt man Flaggen ( oder Flags) . Jeder dieser Flaggen kann direkt angesprochen oder abgefragt werden und wird automatisch gesetzt oder gelöscht.
Es existiern folgende Flaggen:
Bit 0 Carry-Flag: Dieses Flag wird ----- gesetzt, wenn ein " Übertrag" stattfindet. Wenn z. B. bei der Addition zweier 8- Bit-Zahlen das Ergebnis größer als 255($ FF) ist und nicht mehr in ein Byte paßt, dann wird das Carry-Bit gesetzt.
Bei der Subtraktion erfüllt das Carry-Bit eine entgegengesetzte Aufgabe. Hierbei ist das Carry-Bit zunächst gesetzt. Beim Subtrahieren zweier Zahlen kann selbstverständlich kein Überlauf stattfinden. Sollte das Ergebnis jedoch kleiner als null werden (" Unterlauf"), so wird das Carry-Flag gelöscht.
Bit 1 Zero-Flag: Dieses Bit ist gewöhn------ lich gelöscht. Wenn das Ergebnis einer Befehlsfolge aber Null ergibt, wird sie gesetzt.
Bit 2 IRQ-Flag: Sie ermöglicht oder ver------ hindert Interrupts. Sie spielt in den späteren Kursteilen eine größere Rolle.
Bit 3 Dezimal-Flag: Von ihr hängt es ab,----- ob eine Rechenoperation im Dezimalmodus ausgeführt wird.
Normalerweise ist dieses Bit gelöscht und alle Rechnungen werden binär ausgeführt.
Bit 4 Break-Flag: Wenn das Programm auf ----- einen BRK-Befehl trifft, wird diese Flagge gesetzt.

Bit 5 ist nicht belegt.                 
-----                                   

Bit 6 V-Flagge (" Overflow") : Dieses Bit ----- wird nur im Zweierkomplement-Modus benötigt. In diesem Modus wird das 7 . Bit einer Zahl als Vorzeichen aufgefaßt. Es wird automatisch gesetzt, wenn ein Überlauf eingetreten ist, d. h. wenn der Zahlenbereich überschritten wurde und nun das höherwertigste Bit gesetzt wurde, obwohl es eigentlich das Vorzeichen enthalten sollte.
Bit 7 Negativ-Flag: Wenn das Ergebnis ----- einer Operation größer als 127($7 F) ist, wird diese Flagge gesetzt. Wie oben erwähnt, gibt es einen Modus, in dem Zahlen, die größer als $7 F sind (7 . Bit gesetzt), als Negativ angesehen werden.
Daß fast jeder Befehl auf die Flaggen einwirkt, soll uns der altbekannte LDA-Befehl zeigen.
Beispiel: LDA #00 In den Akku wird der Wert $00 geladen.
Da das Ergebnis dieser Aktion null ist, wird die Zero-Flagge gesetzt.
Bei LDA # FF hingegen bleibt die Zero-Flagge gelöscht. Da aber ein größerer Wert als $7 F geladen wurde, wird nun die Negativ-Flagge gesetzt.
Wir benötigen in diesem Kursteil lediglich die Flaggen C, Z, N und V.
Die anderen Flaggen sollten hier nur kurz erwähnt werden. Da Sie nun die Bedeutung der Flaggen kennen, wird Ihnen die Registeranzeige Ihres Maschinensprache- Monitors " N V - B D I Z C" sicherlich etwas mehr sagen.

Die Vergleichsbefehle:                  
---------------------                   

CMP:( CoMPare to accu) : Dieser Befehl --- ermöglicht einen Vergleich zwischen dem Inhalt des Akkus und einem beliebigen Wert ( oder dem Inhalt einer Speicherstelle) . Die Register werden dadurch verglichen, daß der adressierte Wert vom Inhalt des Akkus abgezogen wird. Dabei werden ( je nach Ergebnis) die Flaggen C, Z und N verändert. Der Akkumulatorinhalt bleibt unberührt.

Beispiel:   LDA #$05                    
            CMP #$01                    

Es wird also der adressierte Wert ($01) vom Inhalt des Akkus ($05) abgezogen.
Unsere Flaggen zeigen folgende Inhalte:
Carry-Flagge : gesetzt, da bei dieser " Subtraktion" kein Unterlauf aufgetreten ist.
Zero-Flagge : gelöscht, da die Differenz ($04) größer als Null ist.
Negativ-Flagge: gelöscht, da $04 kleiner als $7 F ist.
Der CMP-Befehl erlaubt folgende ( schon bekannte) Adressierungsarten:
Unmittelbare Adressierung, z. B: CMP #$10 Absolute Adressierung , z. B: CMP $2000 CPX/ CPY: Die Vergleichsbefehle CPX -------( ComPare to X) und CPY ( ComPare to Y) entsprechen dem CMP-Be- fehl, nur daß anstelle des Akkus mit den angegebenen Indexregistern verglichen wird.
Da die Vergleichsbefehle nur die Flaggen verändern, benötigen wir aber noch Befehle, die auf den Inhalt dieser Flaggen reagieren.

Die Verzweigbefehle:                    
-------------------                     

BEQ ( Branch on EQual)--- Verzweige zur angegebenen Adresse, falls die Zero-Flagge gesetzt ist.
BNE ( Branch on Not Equal)--- Verzweige, falls die Zero-Flagge gelöscht ist.
BCC ( Branch on Carry Clear)--- Verzweige, falls die Carry-Flagge gelöscht ist.

BCS (Branch on Carry Set)               
---  Verzweige, falls die Carry-Flagge  
     gesetzt ist.                       
BMI (Branch on MInus)                   
---  Verzweige, falls die Negativ-Flagge
     gesetzt ist.                       
BPL (Branch on PLus)                    
---  Verzweige, falls die Negativ-Flagge
     gelöscht ist.                      
BVC (Branch on oVerflow Clear)          
---  Verzweige, falls die V-Flagge      
     gelöscht ist.                      
BVS (Branch on oVerflow Set)            
---  Verzweige, falls die V-Flagge      
     gesetzt ist.                       

Wie Sie sehen, richten sich jeweils zwei dieser Befehle ( gegensätzlich) nach einer Flagge.
Wenn die Bedingungen für eine Verzweigung nicht gegeben ist, wird dieser Befehl einfach vom Programm übergangen.
Hier liegt eine deutliche Parallele zur IF. . . Then. . .- Anweisung in BASIC.
Bei den bedingten Verzweigungen gibt es noch eine weitere Besonderheit; Sehen wir uns zunächst den Befehl

. 2000  D0 03     BNE $2005             

etwas näher an. Wie Sie sehen, handelt es sich um einen 2- Byte-Befehl, obwohl alleine die Angabe der Sprungadresse schon 2 Bytes benötigen müßte. Diese speicherplatzsparende Besonderheit wird durch die relative Adressierung ermöglicht. Es wird also die Sprungadresse nicht direkt angegeben, sondern nur die Entfernung zum Sprungziel.
Wenn Sie jede Sprungadresse selbst ausrechnen müßten, würde dies zu einer wüsten Rechnerei führen. Da uns der Assembler jedoch diese lästige Arbeit abnimmt, können wir dieses Thema getrost übergehen. Sie müssen nur wissen, daß Sie nicht weiter als 129 Bytes hinter und 126 Bytes vor Ihren Verzweigungsbefehl springen dürfen.
Dieser eingeschränkte Aktionsradius der relativen Adressierung fällt Ihnen bei der Programmierung keum noch auf.
Jetzt, am Ende des dritten Teiles haben Sie schon die wichtigsten Grundlagen erlernt und sind in der Lage, kleinere Programme zu schreiben. Was Sie mit Ihrem Wissen anfangen können, zeigt Ihnen auch diesmal das Begleitprogramm zu diesem Kurs.

Laden Sie nun den "Assembler-Kurs 3" von
Diskette  und  genießen  Sie,  was   Sie
bisher gelernt haben.                   
                                    (wk)
         Assembler-Kurs Teil 4          
         

Heute behandeln wir zwei neue Assemblerbefehle und die Erstellung eines Programmablaufplans. Auf Diskette wird außerdem noch die Zeropage-Adressierung erklärt.

JMP (JuMP to adress)                    

Zuletzt wurde die bedingten Verzweigung erläutert. Der JMP-Befehl hängt nich von solchen Bedingungen ab. Wenn das Programm diesen Befehl erreicht, wird sofort zur angegebenen Adresse verzweigt.
Beispiel:
JMP $2000 springt zur Adresse $2000 Sicher haben Sie schon die Ähnlichkeit mit der GOTO-Anweisung in BASIC erkannt.
Die SPrungadresse des JMP-Befehls kann auch in indirekter Form vorliegen, z. B. :
JMP ($2000) .
Eine Hexadezimalzahl besteht immer aus einem höherwertigen Byte ( Bei der Zahl $2000) ist dies $20) und einem niederwertigen Byte ( hier:$00) . Das höherwertige Byte entspricht also den ersten beiden Stellen, das niederwertige den letzten beiden Stellen der Hexzahl.
Bei dem letzten Beispiel wird nun nicht zur Adresse $2000 verzweigt. Stattdessen werden die Speicherstellen $2000 und $2001 als " Sprungvektoren" behandelt.
Enthält z. B. die Adresse $2000 das Byte $05( niederwertiges Byte) und $2001 den Wert $10( höherwertiges Byte), so springt das Programm nach $1005 . Diese Adressierungsart des JMP-Befehls wird jedoch nur selten verwendet.

JSR (Jump to SubRoutine)                

Ein Unterprogramm ist eine selbstständige Gruppe von Befehlen, die vom Hauptprogramm getrennt sind.
Nach Aufruf des Hauptprogramms wird es abgearbeitet, bis ein JSR-Befehl erreicht wird ( z. B. : JSR $2000) . Nun wird in das Unterprogramm verzweigt. Es werden jetzt alle Befehle ausgeführt, bis das Programm auf ein RTS trifft.
Daraufhin wird in das Hauptprogramm zurückgesprungen.
Selbstverständlich kann auch aus dem Unterprogramm ein weiteres Unterprogramm aufgerufen werden ( Verschachtelung von Unterprogrammen) .
Die Kombination JSR. . . RTS enspricht unseren bekannten BASIC-Befehlen GOSUB. . . RETURN.
Der Vorteil eines Unterprogramms liegt darin, daß es von beliebig vielen Stellen aus dem Hauptprogramm aufgerufen werden kann. So müssen mehrmals benötigte Routinen nicht immer wieder neu geschrieben werden. Dadurch fällt weniger Programmierarbeit an und es wird Speicherplatz gespart.
Wie schon erwähnt, liegt einer der größten Nachteile der Assemblerprogrammierung in der Übersichtlichkeit längerer Programme. Ohne ein geeignetes Hilfsmittel wird das Programmieren zu einem reinen Glücksspiel ( Fehlersuche in Assembler ist weniger spaßig!) .
Doch zum Glück gibt es die Programmablaufpläne. Bei diesen wird durch einfache Symbole der ganze Aufbau eines Programmes verdeutlicht.
Die Ablaufrichtung eines PAP geht immer von oben nach unten vor, wenn sie nicht durch Pfeile gekennzeichnet wird.

Laden Sie nun den ASSEMBLER-KURS  4  aus
dem GAME-MENÜ. Dort erhalten Sie  anhand
von      Beispielprogrammen      weitere
Informationen.                          
                                 (rt/wk)

Assembler-Kurs Teil 5 Das Thema, das wir diesmal behandeln, wird Sie sicherlich nicht in Verzückung geraten lassen, aber es ist leider notwendig: Das Rechnen in ASSEMBLER.
Wie Sie sicher schon ahnen, wird die Sache etwas komplizierter als in BASIC.
Mit einer Zeile, wie C = A + B ist es in ASSEMBLER leider nicht getan. Um diesen Kursteil nicht unnötig zu komplizieren, werden wir uns heute nur mit der Addition und Subtraktion von ganzzahligen Werten ( genannt: Integers) beschäftigen. Dabei kommt man allerdings um die Kenntnis der Flaggen des Statusregisters ( bekannt aus Kursteil 3) nicht herum.
Bevor wir munter addieren und subtrahieren, sollten Sie vielleicht doch erstmal einen Blick auf die dualen Zahlendarstellungen des C64 werfen. Das Verlassen des erlaubten Zahlenbereiches kann einige unschöne Nebeneffekte haben.
Unser Computer kennt die ganzzahlige Werte von -128 bis +127 bei 8- Bit-Zahlen und von -32768 bis +32767 bei 16- Bit-Zahlen. Positive Dualzahlen sind für uns ja kein Problem, aber wie stellt man den negative Zahlen dar?
Das soll an dem Beispiel der Zahl -18 erklärt werden:
Die Zahl +18 wird bekanntlich als 00010010 kodiert. Um die negative Zahl zu erhalten, muß man das Komplement der Zahl bilden ( d. h. die Zahl wird invertiert: aus jeder 0 wird eine 1 und umgekehrt) und +1 dazuaddieren. Das Komplement von +18 ist 11101101 . Nun muß noch +1 dazuaddiert werden und man kommt zu 11101110, was der Zahl -18 entspricht.
Das äußerste rechte Bit ( Bit 7) nennt man das Vorzeichenbit, wobei 0 eine positive und 1 eine negative Zahl kennzeichnet. Diese Darstellungsform, bei der die negativen Zahlen umgewandelt werden, die positiven aber wie gewohnt aussehen, nennt man ZWEIERKOMPLEMENT.

ADC (ADd with Carry)                    

Wenn zwei Zahlen addiert werden, dann muß die eine im Akkumulator stehen, die zweite kann unmittelbar oder als Inhalt einer Speicherstelle gegeben sein. Das Ergebnis der Rechenoperation steht wieder im Akkumulator. Dies gilt auch für die Subtraktion. Das Ergebnis sollte aus dem Akkumulator schnell in eine Speicherstelle geschrieben werden, da es sonst beim weiteren Programmablauf verlorengehen könnte.
Unser neuer Befehl ADC addiert aber nicht nur zwei Zahlen, sondern auch noch den Wert des Carry-Bits ( was uns bei 8- Bit-Additionen gar nicht recht sein kann) . Daher sollte vor dem ADC zuerst der Befehl CLC stehen, mit dem das Carry-Bit gelöscht wird. Nun erhalten wir auf jeden Fall des richtige Ergebnis.
Dualzahlen werden fast wie Dezimalzahlen addiert:

0 + 0 = 0                               
0 + 1 = 1                               
1 + 0 = 1                               
1 + 1 = 0 und zusätzlich enthält hier   
          das Carry-Bit den Übertrag 1, 
          der bei der nächsten Stelle   
          nicht berücksichtigt wird.    

Ein kleines Beispiel:

 CLC                                    
 LDA #$5D        01011101   (Dez. 93)   
 ADC #$0E      + 00001110   (Dez. 14)   
 STA $1500     ----------               
                 01101011   (Dez. 107)  

Das Ergebnis #$6 B (=107) steht jetzt im Akkumulator und in der Speicherstelle $1500 .
Ein weiteres Rechenbeispiel:

  115         01110011                  
 + 14       + 00001110                  
-----        ---------                  
  129         10000001                  

Hier ist offensichtlich etwas schiefgegangen. Das Ergebnis hätte +129 lauten sollen; herausgekommen ist aber 10000001, was der Zahl -127 im Zweierkomplement entspricht. Das diese Rechnung nicht korrekt ist, liegt am Verlassen des erlaubten Zahlenbereiches. Der Computer gibt uns hier zwar keine Fehlermeldung aus, er warnt uns jedoch vor dem Ergebnis, indem er die V-Flagge setzt.
Diese Overfolw-Flagge wird immer dann gesetzt, wenn ein unbeabsichtigter Übertrag in das Vorzeichenbit stattfand.
Die gesetzte N-Flagge ( Vorzeichenflagge) sagt uns, daß das Ergebnis eine negative Zahl ist.
Die V-Flagge wird eigentlich automatisch gesetzt oder gelöscht. Dennoch gibt es den Befehl CLV, mitdem man die V-Flagge auch selbst wieder löschen kann, und somit das Warnsignal unterdrückt.
Bisher wurde nur die Addition von 1- Byte-(8- Bit) Zahlen geschildert.
Die Addition von 2- Byte-(16- Bit) Zahlen wird noch etwas komplizierter, da der Akkumulator nur 8 Bits aufnehmen kann.
Des Rätsels Lösung: Man addiert zunächst nur die niederwertigen Bytes der 16- Bit-Zahlen. Sollte dort ein Übertrag über das 8 . Bit hinaus vorliegen, so gelangt dieser ins Carry-Bit. Nun werden die beiden höherwertigen Bytes addiert, und der Inhalt des Carry-Bits hinzugezählt.
Das Abspeichern der beiden Bytes muß ebenfalls getrennt erfolgen. Erwähnt sei noch, daß bei 16- Bit-Zahlen das Bit 15 das Vorzeichen enthält.
Beispiel für eine 16- Bit-Addition:
$1000/$1001 enthalten die erste Zahl $1100/$1101 enthalten die zweite Zahl $1200/$1201 sollen das Ergebnis enthalten Das Programm dafür sieht folgendermaßen aus:

 CLC                                    
 LDA $1000                              
 ADC $1100  ; Addition des niederwerigen
 STA $1200    Bytes                     
 LDA $1001                              
 ADC $1101  ; Addition des höherwertigen
 STA $1201    Bytes ohne CLC            
SBC (SuBtract with Carry)               

Dieser Befehl, wer hätte es gedacht, subtrahiert zwei Zahlen voneinander.
Das Dumme ist nur: Unser Prozessor kann überhaupt nicht subtrahieren, er kann nur addieren! Um dennoch Zahlen voneinander abziehen zu können, wendet er einen Trick an. Eine der beiden Zahlen wird einfach zu einer negativen Zahl gemacht und dann werden beide Zahlen addiert. Diese Uwandlung nimmt uns der Befehl SBC jedoch schon ab. Es wird zu dem ersten Operanden nicht nur das Zweierkomplement der zweiten Zahl, sondern auch noch das Komplement des Carry-Bits addiert. D. h. wenn das Carry-Bit das Ergebnis bei der Subtraktion nicht verfälschen soll, muß es zuvor gesetzt sein. Dazu dient der Befehl SEC ( SEt Carry), der vor jedem SBC stehen sollte.
Ein Beispiel:

 SEC                                    
 LDA #$7D                               
 SBC #$0A                               
 STA $1500                              

Per Hand sieht die Rechnung so aus:

 Dezimal              Binär             
   125               01111101           
 -  10            +  11110110           
 -------          -----------           
   115           (1) 01110011           

Das Ergebnis $73(115) steht im Akku und in der Speicherstelle $1500 .
Wie Sie sehen, erhalten wir das richtige Ergebnis. Das Vorzeichen der Zahl ist positiv, da die N-Flagge nicht gesetzt ist. Das gesetzte "9 . Bit" steht im Carry und wird bei der 8- Bit-Subtraktion nicht benötigt. Auch bei der Subtraktion signalisiert die V-Flagge ein ubeabsichtigtes Verlassen des Zahlenbereichs.
Der SBC-Befehl hat also große Ähnlichkeit mit dem ADC, und so ist es nicht verwunderlich, daß eine 16- Bit-Subtrak- tion einer 16- Bit-Addition auch sehr verwandt ist.
Beispiel für eine 16- Bit-Subtraktion:
$1000/$1001 enthalten die erste Zahl $1100/$1101 enthalten die zweite Zahl $1200/$1201 sollen das Ergebnis enthalten Das Programm sieht folgendermaßen aus:

 SEC                                    
 LDA $1000                              
 SBC $1100  ; Subtraktion der           
 STA $1200    niederwertigen Bytes      
 LDA $1001                              
 SBC $1101  ; Subtraktion der höher-    
 STA $1201    wertigen Bytes ohne SEC   

Abschließend möchte ich noch zeigen, daß es dem Programmierer selbst überlassen bleibt, ob er die Zahlen wirklich als vorzeichenbehaftet ansieht, wie es eigentlich vorgesehen ist, oder ob er die Zahlen als vorzeichenlos betrachtet.
Dazu ein Beispiel:

 Dezimal                Binär           
   -6                 11111010          
  + 2               + 00000010          
 ----               ----------          
   -4                 11111100          

Das Ergebnis ist bei vorzeichenbehafteten Zahlen richtig, da -6($ FA) und -4($ FC) im Zweierkomplement gegeben sind.
Nun vergessen Sie mal das Zweierkomplement und betrachten Sie die Zahlen als vorzeichenlos. Dann hätten wir $ FA +$02=$ FC ( also dezimal 250+2=252) gerechnet. Auch dieses Ergebnis ist korrekt. Dieses Beispiel zeigt, daß es am Programmierer liegt, welches der beiden Ergebnisse er beabsichtigt.

BCD-Darstellung (Binary Coded Decimals) 

Da die Befehle ADC und SBC sowohl im Binär-, als auch im Dezimalmodus arbeiten, muß es wohl Befehle geben, die zwischen den beiden Modi umschalten.
Wenn es nicht vom Programm geändert wurde, ist automatisch der Binärmodus angeschaltet.
CLD: schaltet den Binärmodus an SED: schlaten auf Dezimalmodus um Bei deisen " binärkodierten" Dezimalzahlen gibt es nur die Ziffern 0 bis 9 . Es wird also jede Dezimalstelle für sich getrennt kodiert. Um diese 10 Ziffern kodieren zu können, benötigt man 4 Bits, mit denen bis zu 16 Kombinationen möglich wären. Sechs der Kombinationsmöglichkeiten bleiben daher ungenutzt. Die Dezimalzahl 0 wird als 0000, die 9 als 1001 kodiert. Die BCD-Dar- stellung der Zahl 128 lautet z. B. :

 0001   0010   1000       binär         
  1      2      8         dezimal       

Da jede Ziffer 4 Bits in Anspruch nimmt, fasst man je zwei BCD-Ziffern in einem Byte zusammen. Die Zahl 128 liegt folgendermaßen imnm SPeicher:0000000100101000 . Die BCD-Zahlen seien hier jedoch nur am Rande erwähnt, da Sie nur sehr selten verwendet werden. Rechnen werden wir nicht mit diesen Zahlen.
Das richtige Verständnis für die diesmal erlernten Befehle werden Sie erst durch eigenes Ausprobieren erhalten. Also nichts wie hingesetzt und losprogrammiert. Auch diesmal gibt es selbstverständlich wieder ein Begleitprogramm zu dem Kurs auf Diskette. Es nennt sich " ASSEMBLER-KURS 5" und enthält neben einer Joystickabfrage in ASSEMBLER auch noch einige nützliche Anwendungen der neuen Befehle.

Zusammenfassung der Befehle:            
---------------------------             
ADC (ADd with Carry)                    

Zwei Zahlen werden zusammen mit dem Inhalt des Carry-Bits addiert. Das Ergebnis steht wieder im Akkumulator.
Dieser Befehl gilt sowohl für die binäre, als auch für die dezimale ( BDC) Addition. Der ADC-Befehl kann die Flaggen N, V, Z und C beeinflussen.

SBC (SuBtract with Carry)               

Zwei Zahlen werden subtrahiert, indem das Zweierkomplement der zweiten Zahl zu der ersten dazuaddiert wird. Das Ergebis der Operation steht im Akkumulator. Auch SBC kann sowohl im Binär-, als auch im Dezimalmodus arbeiten.

CLC (CLear Carry)                       

Löscht die Übertragungsflagge. Dieser Befehl sollte immer vor einem ADC benutzt werden.

SEC (SEt Carry)                         

Setzt die Übertragsflagge. SEC sollte vor jedem SBC stehen.

CLD (CLear Decimal mode)                

Löscht die Dezimalflagge und schaltet somit den Binärmodus ein.

SED (SEt Decimal mode)                  

Setzt die Dezimalflagge auf 1 . Dadurch werden alle Rechenoperationen im Dezimalmodus ausgeführt.

CLV (CLear oVerflow flag)               

Löscht die Überlaufflagge.

                     (Ralf Trabhardt/wk)
         Assembler-Kurs Teil 6          
         

Diesmal geht es um die logischen Verknüpfungen AND, ORA und EOR, von denen Sie vielleicht einige schon von BASIC aus kennen.
Außerdem werden Sie die Befehle BIT und NOP kennenlernen.

AND (AND with accumulator)              

Die logischen Befehle verknüpfen den Akkumulatorinhalt bitweise mit den angegebenen Daten. Die AND-Verknüpfung läuft nach folgendem Muster an:
0 AND 0=0 Wie Sie sehen, erhält man 0 AND 1=0 als Ergebnis nur dann eine 1 AND 0=0"1", wenn beide Operanden 1 AND 1=1 an dieser Stelle das Bit auf 1 gesetzt haben.
Beispiel für eine AND-Verknüpfung:

 LDA #$9A                               
 AND #$D1                               
 STA $1000                              

Der Akku wird mit dem Wert $9 A (=144) geladen und mit der Zahl $ D1(=209) UND-Verknüpft. Das Ergebnis der Verknüpfung steht wieder im Akku und kann von dort in Speicherstelle $1000 abgespeichert werden. Dasselbe Ergebnis erhielte man selbstverständlich auch durch:

 LDA $1001                              
 AND $1002                              
 STA $1000                              

wenn vorausgesetzt wird, daß in Speicherstelle $1001 der Wert $9 A und in $1002 die Zahl $ D1 steht.
Führen wir nun die bitweise Berechnung durch, um zu sehen, welcher Wert in Speicherstelle $1000 abgelegt wird:

     $9A : 10011010                     
 AND $D1 : 11010001                     
           --------                     

10010000 Wenn Sie genau hinsehen, wird Ihnen auffallen, daß überall dort, wo in dem zweiten Operanden eine 0 auftaucht, auch das Ergebnis eine 0 enthält. Da haben wir auch schon das häufigste Einsatzgebiet des AND-Befehls gefunden, nämlich das gezielte Löschen von Bits.
Ein weiteres Beispiel soll dies verdeutlichen. Hier verknüpfen wir eine beliebige Zahl mit dem Wert $ F0(=240) .
Für jede Bitposition an der ein X steht, kann entweder eine 1 oder eine 0 eingesetzt werdene. Dies spielt für unser Beispiel keine Rolle:

           xxxxxxxx                     
 AND $F0 : 11110000                     
           --------                     

xxxx0000 An unserem Ergebnis sieht man, daß die untersten vier Bits gelöscht wurden, während die obersten Bits ihren alten Wert beibehalten haben.
Der AND-Befehl beeinflußt die Flaggen N ( wenn im Ergebnis Bit 7 gesetzt ist) und Z ( falls das Ergebnis gleich Null ist) .

ORA (inclusivE OR with Accumulator)     

Die Verknüpfung verläuft nach folgendem Schema:
0 ORA 0=0 Man erhält als Ergebnis 0 ORA 1=1 eine "1", wenn das Bit des 1 ORA 0=1 ersten Operanden oder das 1 ORA 1=1 Bit des zweiten Operanden an dieser Stelle auf 1 gesetzt ist.
Ansonsten ist ORA dem AND-Befehl sehr ähnlich. Er beeinflußt sogar dieselben Flaggen ( N-Flag und Z-Flag) .
Ein Beispiel für eine ORA-Verknüpfung:

 LDA #$9A                               
 ORA #$D1                               
 STA $1000                              

Auch hier wollen wir die Verknüpfung bitweise nachvollziehen:

     $9A : 10011010                     
 ORA $D1 : 11010001                     
           --------                     

11011011 Im Unterschied zu AND dient der ORA-Befehl dem gezielten Setzen von Bits. Als Beispiel möchte ich wieder ein beliebiges Byte mit dem Wert $ F0 ODER-Verknüpfen:

           xxxxxxxx                     
 ORA $F0 : 11110000                     
           --------                     

1111 xxxx Das Ergebnis zeigt, daß die obersten vier Bits auf 1 gesetzt wurden, während die anderen gleichgeblieben sind. Bei der ODER-Verknüpfung sind es jetzt die Einsen des Operanden $ F0, die eine Veränderung des Akkuinhaltes bewirken.
Vergleichen Sie das Ergebnis mit dem Beispiel der AND-Verknüpfung, so stellen Sie fest, daß dort die Nullen des zweiten Operanden für das Ergebnis entscheidend waren.

EOR (Exclusive OR with accumulator)     
 0 EOR 0 = 0  Man erhält an einer Bit-  
 0 EOR 1 = 1  stelle nur dann eine "1", 
 1 EOR 0 = 1  wenn das Bit des ersten   
 1 EOR 1 = 0  Operanden oder das Bit des
              zweiten Operanden an      
              dieser Position auf 1     
              gesetzt ist.              

Schaut man sich die Verknüpfungstabelle des EXKLUSIV-ODER an, so fällt ein Unterschied zum ORA direkt ins Auge: Nur wenn genau ein Operand das Bit auf 1 hat, wird auch im Ergebnis dieses Bit gesetzt, aber nicht, wenn beide Operanden eine gesetztes Bit haben.
Ein Beispiel für eine EOR-Verknüpfung:

 LDA #$9A                               
 EOR #$D1                               
 STA $1000                              

Bitweise ergibt sich folgendes Ergebnis:

     $9A : 10011010                     
 EOR $D1 : 11010001                     
           --------                     

01001011 Aus diesem Ergebnis läßt sich auf Anhieb kein besonderer Sinn für diesen Befehl erkennen. Aber was passiert, wenn der zweite Operand mit dem ersten operanden übereinstimmt ? Richtig, das Ergebnis der EOR-Verknüpfung wäre Null ( d. h. das Zero-Flag wäre gesetzt) . EOR kann also für Vergleiche benutzt werden, denn bei Ungleichheit der Operanden erhielte man ein Ergebnis ungleich Null.
Der EOR-Befehl hat aber noch ein weiters Einsatzgebiet. Um dies zu veranschaulichen, verknüpfen wir den Akkuinhalt $9 a mit $ FF:

     $9A : 10011010                     
 EOR $FF : 11111111                     
           --------                     

01100101 Und siehe da, wir erhalten als Ergebnis das Komplement des vorherigen Akkuinhates. Man muß also, um das Komplement einer Zahl zu bekommen, diese mit EOR $ FF verknüpfen.

Der BIT-Befehl                          

Eigentlich hat der Bit-Befehl nicht sehr viel mit den logischen Verknüpfungen zu tun. Man kann ihn eher zu den Vergleichsbefehlen zählen. Mit BIT führt man eine UND-Verknüpfung zwischen dem Akkuinhalt und der angegebenen Speicherstelle durch. Dabei wird aber der Akkumulatorinhalt nicht verändert.
Das Ergebnis dieser Verknüpfung wird in der Zero-Flagge festgehalten, da diese auf 1 gesetzt ist, falls das Ergebnis 0 war. Andernfalls ist das Zero-Flag gelöscht.
Die Adressierung des BIT-Befehles kann nur absolut erfolgen:

 LDA #$9A                               
 BIT $1500                              

Bei diesem Beispiel wollen wir annehmen, daß in Speicherstelle $1500 der Wert $ D1 abgelegt ist. Dann lautet die logische Verknüpfung wie beim AND-Befehl:

     $9A : 10011010                     
 AND $D1 : 11010001                     
           --------                     

10010000 Der Akkumulatorinhalt wird diesmal jedoch nicht verändert, und das Z-Flag ist gelöscht, da das Ergebnis ungleich 0 ist.

Der NOP-Befehl                          

Zum Abschluß des diesmaligen Kursteiles soll hier noch ein ganz einfacher befehl vorgestellt werden. Der NOP-Befehl tut nämlich nichts. Aber wozu kann man einen Befehl gebrauchen, der nur zwei Taktzyklen abwartet und ein Byte Speicherplatz beansprucht, aber sonst keine Wirkung hat ? Man nimmt ihn als Platzhalter innerhalb des ASSEMBLER-Programmes. Wenn man bei seinem Programm plötzlich feststellt, daß man noch einen Befehl vergessen hat, muß man normalerweise den gesamten Programmcode im Speicher verschieben. Hat man aber z. B drei NOP' s hintereinander geschrieben, so kann man diese einfach mit einem JMPoder JSR-Befehl überschreiben. Durch einen solchen " Notausstieg" hat man die Möglichkeit, weitere Befehle einzufügen.
Wie immer finden Sie auf der Diskette ein Begleitprogramm mit vielen nützlichen Tips. Starten Sie dazu das Programm " ASSEMBLER-KURS 6" aus dem Gamesmenü.
Ralf Trabhardt/( wk) Assembler-Kurs Teil 7 Bevor wir uns den ROM-Routinen des C64 zuwenden, werden noch die vier Bit-Verschiebebefehle ASL, LSR, ROL und ROR erklärt. Diese Befehle benötigen wir erst im Kursteil 9 bei der Multiplikation und Division von Ganzzahlen.
Trotzdem sollen diese Befehle schon jetzt behandelt werden, da Sie in der Folge 9 genug Probleme mit dem Verständnis der Fließkomma-Zahlen haben werden und nicht noch zusätzlich mit neuen Befehlen konfrontiert werden sollen.
Die Verschiebebefehle

ASL (Arithmetic Shift Left)             

Bei diesem Befehl wird der gesamt Akkuinhalt, oder der Inhalt der angegebenen Speicherstelle bitweise nach links verschoben. Das Bit 7 wird in das Carry-Bit ausgelagert, in Bit 0 des Bytes wird eine 0 eingefügt.

            7 6 5 4 3 2 1 0             
   Carry <= * * * * * * * * <= 0        

Ein einmaliges Linksverschieben entspricht einer Verdoppelung der ursprünglichen Zahl.

Beispiel: LDA #$19                      
          ASL                           
          STA $1000                     

$19 entspricht der binären Bitfolge:
00011001 . Ein ASL führt nun zu 00110010($32) und einer 0 im Carry-Bit. Das Ergebnis der Verschiebung wird in der Speicherstelle $1000 abgelegt.
Aber neben der Möglichkeit der Verdoppelung einer Zahl kann man mit ASL auch bequem die Bits einer Speicherstelle über das Carry-Bit testen. Es werden das Negativund das Zero-Flag beeinflußt.
Der Befehl ASL bietet folgende Adressierungsmöglichkeiten:

ASL          Akku-orientiert            
ASL $1000    absolut                    
ASL $1000,X  absolut X-indiziert        
ASL $FA      Zeropage-absolut           
ASL $FA,X    Zeropage-abs. X-indiziert  
LSR (Logical Shift Right)               

LSR bildet das Gegenstück zu ASL. Das betroffene Bit wird nun nach rechts geschoben. Dabei gelangt das bit 0 in das Carry-Bit und in Bit 7 wird eine 0 eingesetzt.

           7 6 5 4 3 2 1 0              
      0 => * * * * * * * * => Carry     
Beispiel: LDA #$19                      
          LSR                           
          STA $1000                     

Aus der Bitfolge 00011001 wird durch LSR ein 00001100 . Außerdem enthält das Carry-Bit jetzt die links aus dem Byte geschobene 1 . Das Ergebnis wird wieder in der Speicherstelle $1000 abgelegt.
LSR hat in diesem Fall die dezimale Zahl 25($19) ganzzahlig halbiert. Das gesetzte Carry-Bit signalisiert dabei, daß ein Rest aufgetreten ist (25 :2=12 Rest 1) .
LSR erlaubt die selben Adressierungsarten und beeinflußt die gleichen Falggen wie der ASL-Befehl.

ROL (ROtate Left)                       

Der ROL-Befehl hat große Ähnlichkeit mit dem ASL, nur daß das Byte jetzt nicht mehr nur verschoben wird, sondern um 1 Bit rotiert. Wie auch bei ASL landet das Bit 7 im Carry. Das Carry-Bit wird anschließend in Bit 0 übertragen. Somit ist das Bit 7 schließlich in Bit 0 gelangt.

            7 6 5 4 3 2 1 0             
   Carry <= * * * * * * * * <= Carry    
Beispiel: LDA #$B1                      
          ROL                           
          STA $1000                     

In den Akku wird die Bitkombination 10110001 geladen. Nach dem ROL befindet sich der Wert 01100011($63) im Akku, der in die Speicherstelle $1000 abgelegt wird. Das Carrybit enthält ebenfalls eine 1 .

ROR (ROtate Right)                      

Wie nicht anders zu erwarten war, ist ROR wieder das Gegenstück zu ROL. Jetzt wird das Byte nicht um ein Bit nach links, sondern nach rechts rotiert. Es befindet sich daher Bit 0 im Carry. Der Inhalt des Carry-Bits rotiert nun in Bit 7 hinein.

            7 6 5 4 3 2 1 0             
   Carry => * * * * * * * * => Carry    
Beispiel: LDA #$B1                      
          ROR                           
          STA $1000                     

Auch für ROR wird das Beispiel durchgeführt. Aus 10110001 wird nach ROR die bitfolge 11011000 . Das Carry-Bit ist auch in diesem Beispiel gesetzt.
Die zuletzt besprochenen Befehle lassen dieselben Adressierungsarten wie der ASL-Befehl zu, und auch sie verändern die Flaggen N und Z.

ROM-Routinen                            

Unsere Assembler-Programme stehen immer im RAM ( Random Access Memory), dem Lese-/ Schreib-Speicher. In das ROM ( Read Only Memory) können wir nicht hineinschreiben, es kann nur gelesen werden.
Folglich sind die oben erwähnten ROM-Routinen einige gegebene unterprogramme, die wir in unsern eigenen Assembler-Programmen verwenden können.
Der Sinn der Benutzung dieser ROM-Routinen soll Ihnen am Beispiel des " Bildschirm löschens" verdeutlicht werden:
Bisher benötigten wir eine Schleife, die den gesamten sichtbaren Bildschirmbereich mit dem SPACE-Zeichen "" vollgePOKEd hat. Es war also ein erheblicher Programmieraufwand erforderlich. Nun geht das Ganze aber viel kürzer. Wir springen einfach mit JSR ein ROM-Unter- programm an, das das Bildschirmlöschen für uns übernimmt. Ein " Clear Screen" wird jetzt einfach mit JSR $ E544 ausgeführt.
Übrigens werden alle ROM-Routinen mit einem JSR angesprungen, da diese allesamt mit RTS enden.
Einige ROM-Routinen benötigen vor ihrem Aufruf gewisse Informationen, ohne die sie nicht fehlerfrei arbeiten können.
Zum Beispiel braucht die Routine zum Setzen des Cursors die Zeilenposition im X-Register und die der Spalten im Y-Register.

LDX #$00                                
LDY #$08                                
JSR $E50C                               

Dieses Programm setzt den Cursor in der Zeile 0 auf die Spalte 8 .
Das folgende Programm gibt den ASCII-Text an der aktuellen Cursorposition aus

LDA #$00                                
LDY #$C1                                
JSR $AB1E                               

Der auszugebende Text für diese Routine muß im Speicher an der Stelle $ C100 beginnen, und mit einem Byte $00 enden.
Als Zeiger auf diese Textstelle fungieren hierbei der Akku ( niederwertiges Byte der Adresse) und das Y-Register ( höherwertiges Byte) . Wie Sie einen ASCII-Text direkt im Speicher ablegen können, verrät Ihnen das Handbuch Ihres Assemblers. Oft gibt es dafür einen Befehl der Art C100 . ASC " beispieltext" Der Text liegt dann folgendermaßen im Speicher:

C100  42 45 49 53 50 49 45 4C   beispiel
C108  54 45 58 54 00 00 00 00   text    

Eine weitere wichtige Routine ist die Tastaturabfrage JSR $ FFE4 . Hierbei steht der ASCII-Code der gedrückten Taste im Akkumulator.

Das Beispielprogramm                    
C000 JSR $FFE4                          
C003 BEQ $C000                          
überprüft, ob irgendeine Taste gedrückt 
wurde. In BASIC hätten wir              
10 GET A$                               
20 IF A$ = "" THEN 10                   
geschrieben.                            

Natürlich kann ich Ihnen in diesem Kurs nicht alle verfügbaren ROM-Routinen vorstellen. Wenn ich aber Ihr Interesse daran geweckt habe, dann sollten Sie sich ein Buch zulegen, das " dokumentierte ROM-Listings" enthält.
Aber wo genau liegt eigentlich der ROM-Bereich im Speicher?
Wir haben bis jetzt von $ AB1 E bis $ E50 C aufgerufen. Bei $ C000-$ CFFF liegt aber ein RAM-Bereich, den wir schon oft genutzt haben. Es muß daher mehr als nur einen ROM-Bereich geben.
In der Tat gibt es 3 unterschiedliche ROMbereiche. Von $ A000-$ BFFF liegt das ROM des BASIC-Interpreters. Dieser Interpreter sorgt dafür, daß Sie gleich nach dem Einschalten des Computers das BASIC verfügbar haben.
Zwischen $ D000-$ DFFF befinden sich die I/ O-Bausteine, die für die Einund Ausgaben verantwortlich sind.
Ab $ E000 bis zum Ende des Speichers $ FFFF liegt das Betriebssystem-ROM.
Speicherbelegungsplan des C64 :

$FFFF ------------------------------    
      |Betriebssystem|     RAM     |    
$E000 ----------------------------------
      |     I/O      | Zeichensatz |RAM|
$D000 ----------------------------------
      |     RAM      |                  
$C000 ------------------------------    
      |  BASIC-ROM   |     RAM     |    
$A000 ------------------------------    
      |              |                  
      |     RAM      |                  
      |              |                  
$0800 ----------------                  
      |    (RAM)     |                  
$0000 ----------------                  

Der mit " Zeichensatz" gekennzeichnete Bereich enthält den Zeichensatz-Generator ( character generator), den ich im Kursteil 8 mit der Erstellung selbstdefinierter Zeichen näher erläutern werde.
Wenn Sie den Speicherbelegungsplan ansehen, stellen Sie fest, daß einige bereiche mit derselben Adresse mehrfach belegt sind. So liegt z. B. von $ E000 bis $ FFFF nicht nur das Betriebssystem," darunter" befindet sich auch noch ein RAM-Bereich. Ähnlich verhält es sich mit dem Bereich von $ D000-$ DFFF, der sogar dreifach belegt ist.
Aber wie kann man zwischen den mehrfach belegten bereichen auswählen?
Dazu dient die Speicherstelle $01, der sogenannte Prozessorport. Je nachdem, welcher Wert in dieser Speicherstelle steht, verändern sich die Zugriffsmöglichkeiten auf den Speicher.
Als Assembler-Programmierer könnten wir zum Beispiel mit

LDA #$36                                
STA $01                                 

das gesamte BASIC abschalten un das darunterliegende RAM für unsere Zwecke nutzen. Wir hätten somit einen durchgehenden RAM-Speicher von $0800 bis $ CFFF geschaffen. Leider können wir aber nicht mehr auf die ROM-Routinen des BASIC-Interpreters zugreifen.

LDA #$37                                
STA $01                                 
stellt den  Ausgangszustand  wieder  her
und schaltet das BASIC-ROM an.          
 $01 $A000-$BFFF $D000-$DFFF $E000-$FFFF
 $37   BASIC       I/O         ROM      
 $36   RAM         I/O         ROM      
 $35   RAM         I/O         RAM      
 $34   RAM         RAM         RAM      
 $33   BASIC       Charset     ROM      
 $32   RAM         Charset     ROM      
 $31   RAM         Charset     RAM      
 $30   RAM         RAM         RAM      

Diese Tabelle des Prozessorports zeigt Ihnen die Möglichkeiten auf.
Auf den ersten Blick wirkt das mit der Mehrfachbelegung un dem Umschalten zwischen Bereichen alles etwas umständlich. Anders geht es aber nicht.
Mit unserem maximal 2 Byte langem Adressteil ( z. B. LDA $ FFFF) können wir nur den Bereich von $0000-$ FFFF, also 64 KByte andressieren. Würde man alle mehrfach belegten Bereiche hintereinander schreiben, käme man aber auf einen Speicherbereich von 64 KByte +24 KByte =88 KByte.
Als praktischen Anschauungsunterricht laden Sie nun am Besten das Beispielprogramm aus dem Menü: ASSEMBLER-KURS 7 .

                                 (rt/wk)

Assembler-Kurs Teil 8

Der Stapel                              

Der Stapel ist der Speicherbereich von $0100 bis $01 FF, der direkt von der CPU verwaltet wird. Der Zugriff auf diese " Page 1" ist daher sehr schnell.
Ein Stapel wächst von oben nach unten, d. h. in unserem Fall, daß zuerst ein Byte in $01 FF, das nächste in $01 FE usw.
abgelegt wird.
Bemerkenswert ist beim Stapel, daß er nach dem LIFO-Prinzip ( last infirst out) arbeitet. Das zuletzt auf den Stapel gebrachte Byte muß also als erstef wieder heruntergeholt werden, wenn die darunterliegenden Bytes gelesen werden sollen.
Wenn Sie Schwierigkeiten haben, sich das LIFO-Prinzip zu verdeutlichen, dann denken Sie sich den Stapel doch einfach als einen Stapel von Getränkekisten.
Dann wird Ihnen klar, daß Sie keinesfalls eine der unteren Kisten herausziehen können. Die zuletzt auf den Stapel gebrachte Kiste, das ist die Kiste ganz oben auf dem Stapel, muß als erste entfernt werden, um an die darunterliegende Kiste heranzukommen.
Aber woher weiß der Computer, welcher Wert auf dem Stapel der oberste ist?
Dazu benötigen wir einen Zeiger ( Pointer), der auf die jeweilige Spitze des Stapels zeigt, den sogenannten Stapelzeiger ( Stackpointer) . Unser Stapelzeiger ist 8 Bits breit, also eigentlich um ein Byte zu klein für die Adressierung des Stapelbereichs ($01 FF bis $0100) . Sicher fällt Ihnen auf, daß das höherwertige Byte ($01) über dem gesamten Stapelbereich gleich bleibt. Es ist daher unnötig, diesen Wert immer im Stapelzeiger mit abzuspeichern. Stattdessen begnügt man sich damit, daß der Stapelzeiger das niederwertige Byte der Spitze des Stapels enthält.
Die effektive Adresse, auf die der Zeiger deutet, kann man sich folglich mit $0100+( Inhalt des Stapelzeigers) selbst errechnen.
Gültige Werte sind für den Stapelzeiger zwischen $00 und $ FF.
So, jetzt wissen wir, wie ein Stapel funktioniert, aber wozu man einen Stapel benötigt, das wissen wir noch nicht.
Es gibt drei wichtige Anwendungsgebiete für einen Stapel:
1 . Er übernimmt die Verwaltung der Rücksprungadressen bei Unterprogrammaufrufen. Diese Eigenschaft wird später noch näher erläutert werden.
2 . Zur Zwischenspeicherung von Daten bei Interrupts.
3 . Kurzzeitige Sicherung von Daten.
Diesen letzte Punkt wollen wir uns nun genau ansehen.

Die Stapeloperationen                   

Im Grunde genommen benötigt man nur zwei verschiedene Stapelbefehle, nämlich einen, um etwas auf den Stapel zulegen ( PUSH) und einen, um einen Wert vom Stapel zu holen ( PULL, POP) .

Stapel        nach PUSH      nach PUSH  
vorher:       Wert1:         Wert2:     
$01FD |  |       |  |        |  |<-SP   
      |  |       |--|        |--|       
$01FE |  |       |  |<-SP    |W2|       
      |--|       |--|        |--|       
$01FF |  |<-SP   |W1|        |W1|       
      ----       ----        ----       

Wenn noch keine Stapeloperationen durchgeführt wurden, zeigt der Stapelzeiger SP auf die Speicherstelle $01 FF. Nun wird WERT1 auf den Stapel gelegt, und zwar an der Adresse, auf die der Stapelzeiger weist. Nach diesem Schreib- zugriff auf den Stapel wird der Stapelzeiger um 1 erniedrigt und zeigt so auf die nächste freie Speicherstelle des Stapels ($01 FE) . Eine weiters PUSH-Anweisung legt den WERT2 auf die Spitze des Stapels ( Top of Stack), was der Position des Stapelzeigers entspricht.
Anschließend erniedrigt sich der Stapelzeiger wieder um 1 .
nach PULL Wert2 : nach PULL Wert1 :

$01FD |  |            |  |              
      |  |            |--|              
$01FE |  |<-SP        |  |              
      |--|            |--|              
$01FF |W1|            |  |<-SP          
      ----            ----              

Während bei PUSH-Befehlen des Stapels zu beobachten war, daß sich der Stapelzeiger erst nach dem Schreiben in die Stapelspeicherstelle erniedrigt hat, stellt man nun fest, daß bei PULL- Anweisungen der Stapelzeiger vor dem Lesezugriff inkrementiert wird. Das ist auch nötig, da der Stapelzeiger immer auf die nächste FREIE Speicherstelle des Stapels zeigt. Wird der Stapelzeiger zuvor inkrementiert, dann weist er nun auf das zuletzt auf den Stapel geschobene Element. Dieser Wert kann nun vom Stapel geholt werden.
Das obige Beispiel verdeutlicht, daß der zuerst auf den Stapel gelegte Wert ( Wert1) erst als letzter vom Stapel geholt werden kann.
Die PUSH-Befehle

PHA (PusH Accumulator)                  

Mit diesem Befehl wird der Akkuinhalt auf den Stapel geschoben. Anschließend wird der Stapelzeiger um 1 erniedrigt.
Der Akkuinhalt bleibt dabei, ebenso wie die Flaggen, unverändert.

PHP (PusH Processor status)             

Anstelle des Akkuninhaltes wird jetzt das gesamte Statusregister des Prozessors mit allen Flaggen auf den Stapel gelegt.
Danach wird der Stapelzeiger um 1 erniedrigt.
Die PULL-Befehle

PLA (PuLl Accumulator)                  

Diese Anweisung ist das Gegenstück zu PHA. Der Stapelzeiger wird zuerst um 1 erhöht. Nun wird der Inhalt der Speicherstelle, auf die der Stapelzeiger deutet, in den Akku eingelesen. Neben dem Akkuinhalt können sich auch die Flaggen N und Z ändern.

PLP (PuLl Processor status)             

Der Stapelzeiger wird inkrementiert und der aktuelle Stapelwert wird in das Prozessor-Statusregister übertragen.
Dabei ändern sich selbstverständlich alle Flaggen.
Zusätzlich zu den Stapeloperationen gibt es auch noch zwei Transferbefehle, die sich direkt auf den Stapelzeiger beziehen.
Die Stapel-Transferbefehle

TSX (Transfer Stackpointer to X)        

Wie der Name der Anweisung schon aussagt, überträgt der Befehl den Stapelzeiger in das X-Register. Somit kann die Adresse, auf die der Stapelzeiger weist, ermittelt werden. Das X-Register kann dementsprechend Werte zwischen $00 und $ FF enthalten.

TXS (Transfer X to Stackpointer)        

Dieser gegenteilige Befehl zu TSX ermöglicht eine direkte Einflußnahme des Programmierers auf den Stapelzeiger, indem der Wert des X-Registers in den Stapelzeiger übertragen wird.
Alle oben genannten Befehle können nur impliziert adressiert werden, d. h. sie haben keinen Adressteil.

 Der Einsatz des Stapels bei Unterprg's 
 -------------------------------------- 

Bisher haben wir es als selbstverständlich erachtet, daß unser Assembler-Programm nach einem RTS bei der nächsten Anweisung nach dem JSR im aufrufenden Programm fortfährt.
Das Programm hat sich also die Stelle, von der aus der Unterprogrammsprung erfolgte, gemerkt. Womit wir wieder beim Stapel wären.
Der Prozessor muß also vor jeder Verzweigung in ein Unterprogramm die momentane Adresse auf den Stapel speichert, was der Adresse die Instruktion nach dem JSR-Befehl entspricht. Diese Adresse muß nach einer speziellen Regel " gestackt" werden, da unser Stapel bekanntlich immer nur ein Byte aufnehmen kann, eine Adresse jedoch aus 2 Bytes besteht. Zuerst wird das höherwertige Byte mit einem PUSH auf den Stapel gelegt und anschließend der Stapelzeiger um 1 vermindert. Erst jetzt wird das niederwertige Byte auf den Stapel gebracht. Erneut muß der Stapelzeiger dekrementiert werden.
Hauptprogramm Unterprogramm

       -----------                ----- 
      |     .     |     -> $C200 |  .  |
      |     .     |    |         |  .  |
$C100 | JSR $C200 | ---          |  .  |
$C103 |     .     | <----------- | RTS |
      |     .     |               ----- 

----------- Der Stapel nach JSR $ C200 :

      |   |<-SP                         
      |---|                             
$01FE |$03|                             
      |---|                             
$01FF |$C1|                             
      -----                             

Nach einem RTS-Befehl läuft der umgekehrte Prozess ab. Zuerst wird der Stapelzeiger um 1 erhöht und das niederwertige Byte vom Stapel geholt. Im Anschluß daran holt man das höherwertige Byte vom Stapel und der Stapelzeiger wird wieder erhöht. nun kann die aktuelle Adresse wieder zusammengesetzt werden und das Hauptprogramm fährt mit seiner Abarbeitung an der gewünschten Stelle fort.
Es fällt uns auf, daß diese abgelegten Rücksprung-Adressen denselben Stapelbereich belegen, den auch der Programmierer zur vorübergehenden Datenspeicherung benutzen kann. Da der Speicherbereich des Stapels begrenzt ist, müssen wir auch die Struktur dieser automatisch ablaufenden Prozesse der Unterprogramm-Verwaltung kennen.
( rt/ wk) Assembler-Kurs Teil 9

Multipilkation und Division             

Die Addition und Subtraktion von ganzen Zahlen mit ADC und SBC war noch recht einfach. Leider gibt es aber keine Assmblerbefehle wie MULT oder DIV, die unsere Berechnungen übernehmen könnten.
Wenn wir eine Multiplikation durchführen wollen, dann müßten wir eigentlich ein Programm dafür schreiben, was allerdings eine ziemliche Quälerei wäre, da wir das Problem über Additionen lösen müßten.
Glücklicherweise gibt es auch für dieses Problem eine geeignete ROM-Routine ($ B357) . Der Faktor muß dafür in den Speicherstellen $28 und $29, der zweite Faktor in $71/$72 bereitgestellt werden.
Das Ergebnis dieser 16- Bit-Multi- plikation erhalten wir im X-( niederwertiges Byte) und Y-Register ( höherwertiges Byte) .
Dummerweise existiert keine entsprechende Routine für die Division. Hier tritt außerdem noch das Problem auf, daß beim Dividieren zweier ganzer Zahlen in den seltensten Fällen das Ergebnis eine ganze Zahl sein dürfte. Jetzt kommen wir um die gebrochenen Zahlen nicht mehr herum.

Die Flißkommazahlen (Floating Point,FLP)

Gebrochene Zahlen werden im allgemeinenn als Fließkommazahlen bezeichnet. Fließkommazahlen bestehen immer aus drei Teilen: der Mantisse, der Basis und dem Exponenten.
Betrachten wir zunächst eine dezimale FLP-Zahl:4500 ist darstellbar als 4 .5*10↑3, was der Darstellung 4 .5 E3 entspricht (4 .5 ist die Mantisse,3 der Exponent und 10 die Basis) . Die Zahl 0 .045 ließe sich auch als 4 .5*10↑-2(4 .5 E-2) schreiben. beachten Sie bitte, daß beide Zahlen auf dieselbe Matissenform gebracht wurden und sich lediglich noch im Exponenten unterscheiden. Das haben wir durch ein Linksbzw. Rechtsverschieben des Dezimalpunktes erreicht.
Bei dezimalen FLP-Zahlen ist das alles noch recht einfach, aber wie kann man binäre FLP-Zahlen in ein einheitliches Format bringen, so daß alle Zahlen dieselbe Anzahl von Bytes haben ?
Betrachten wir das Problem anhand eines Beispiels:
Die Dezimalzahl 50 .125 soll in eine binäre FLP-Zahl umgewandelt werden. Die Umwandlung erfolgt in 5 Schritten:
1 . Vorkommateil umwandeln Der Vorkommateil wird als Integerzahl behandelt und wie gewohnt umgerechnet

   50 : 2 = 25  Rest: 0    niederw. Bit 
   25 : 2 = 12  Rest: 1                 
   12 : 2 =  6  Rest: 0                 
    6 : 2 =  3  Rest: 0                 
    3 : 2 =  1  Rest: 1                 
    1 : 2 =  0  Rest: 1                 

Das Ergebnis :110010 2 . Nachkommateil umwandeln Die Stellen nach dem Binärpunkt haben die Wertigkeiten 2↑-1,2↑-2 usw.
Bei der Berechnung muß daher die Dezimalzahl immer mit 2 multipliziert werden, bei auftretenden Vorkommastellen wird das jeweilige Bit gesetzt.

   0.125 * 2 = 0.25 Vorkommast.: 0      
   0.25  * 2 = 0.5  Vorkommast.: 0      
   0.5   * 2 = 1    Vorkommast.: 1 n.Bit

Das Ergebnis : .001 3 . Normalisierung der Mantisse Unter Normalisierung versteht man das Anpassen der Mantisse an ein bestimmtes Format. Der Binärpunkt muß soweit verschoben werden, bis er links genau neben der höchstwertigen binären 1 steht.

   Vorherige Mantisse: 110010.001       
   Normalisierte Man.: 0.110010001 * 2↑6

In unserem Bespiel mußte der Binärpunkt um 6 Stellen nach links verschoben werden, was durch eine Multiplikation mit 2↑6 ausgegelichen werden muß, damit das Ergebnis nicht verfälscht wird.
Die Mantisse ist nun in der richtigen Form und wir haben auch unseren binären Exponenten (69 gefunden. Die binäre Basis ist logischerweise 2 .
4 . Umwandlung des Exponenten Zu unserem Exponenten 6 wird nun noch die Zahl 128 hinzuaddiert, damit es möglich ist, auch negative Exponenten, die bei der Normalisierung der Mantisse entstehen können, darzustellen. Bei der Rückumrechnung ( Binär -> Dezimal) ist daher daruaf zu achten, daß vom binären Exponenten 128 abzuziehen ist.

         6                              
     + 128                              
    -------                             
       134  (binär : 10000110)          

Ergebnis : Mantisse:0 .110010001 Exponent:10000110 Nun muß nur noch festgelegt werden, in wievielen Bytes diese Zahl abgespeichert werden soll.
5 . Floatingpoint-Zahlen des C64 Dieser Computer kennt zwei verschiedene Fließkommaformate, die sich hauptsächlich durch die Art der Speicherung des Vorzeichens unterscheiden. Die eine wird nur innerhalb der beiden Fließkommakkumulatoren verwendet, die andere beim Abspeichern der Ergebnisse im Arbeitsspeicher.
a) In den beiden Fließkommaakkumulatoren ( abgekürzt: FAC und ARG) werden alle Berechnungen von Fließkommazahlen mit Hilfe von ROM-Routinen durchgeführt.
Der FAC belegt in der Zeropage den bereich von Speicherstelle $61 bis $66, ARG von $69 bis $6 E. Welche Aufgabe die einzelnen Bytes havben, können Sie der nachfolgenden Tabelle entnehmen.

               FAC  ARG                 
   --------------------                 
   Exponent    $61  $69                 
   Mantisse 1  $62  $6A                 
   Mantisse 2  $63  $6B                 
   Mantisse 3  $64  $6C                 
   Mantisse 4  $65  $6D                 
   Vorzeichen  $66  $6E                 

Schauen wir nun, wie die FLP-Zahl aus unserem Beispiel im FAC oder ARG abgelegt wird. Der Exponent erhält 8 Bits, während sich die Mantisse auf 32 Bits ausbreiten darf. Die normalisierte Mantisse wird aber ab dem Binärpunkt linksbündig in die zur Verfügung stehenden Bytes eingetragen. Ist die Mantisse länger als 4 Bytes, so wird der überschüssige Teil einfach weggelassen.
So entstehen u. a. die heißgeliebten Rechenfehler in einem Computer.
Zusätzlich gibt es noch ein Byte, das das Vorzeichen enthält. Von diesem Byte ist jedoch nur das Bit 7 von Bedeutung(0-> positiv ;1-> negativ), die anderen Bits sind uninteressant.
Format der Fließkommaakkumulatoren:

              /--------\                
              |10000110|                
              \--------/                
               Exponent                 
 /--------+--------+--------+--------\  
 |11001000|10000000|00000000|00000000|  
 \--------+--------+--------+--------/  
      1        2        3        4      
            M A N T I S S E             
              /--------\                
              |0xxxxxxx|                
              \--------/                
              Vorzeichen                

In hexadezimaler Form ergibt das:
86 C880000000 Sicher halten Sie die benutzung eines ganzen Bytes für nur ein Vorzeichenbit für Verschwendung. In den Flißkommaakkumulatoren stört das jedoch niemanden, da es ohnehin nur zwei davon gibt. Außerdem erleichtert das getrennte Vorzeichen die Berechung bei der Fließkommaarithmetik.
Hier nun eine kleine Auswahl der möglichen Rechenoperationen:

 /----------------------------+-------\ 
 |Rechenoperation             |Routine| 
 +--------------+-------------+-------+ 
 |Addition      |FAC :=ARG+FAC| $B86A | 
 |Subtraktion   |FAC :=ARG-FAC| $B853 | 
 |Multiplikation|FAC :=ARG*FAC| $BA2B | 
 |Division      |FAC :=ARG/FAC| $BB12 | 
 |Potenzierung  |FAC :=ARG↑FAC| $BF7B | 
 \--------------+-------------+-------/ 

Vor dem Aufruf der Routinen müssen FAC und ARG natürlich die gewünschten Zahlen enthalten.
Wie Sie sehen, enthält der FAC immer das Ergebnis der Berechnungn. Spontan fallen uns zwei Probleme auf: Wie bekomme ich die Zahlen in den FAC oder ARG und wie kann ich das Ergebnis abspeichern, damit es erhalten bleibt? Auch dafür stehen uns wieder einige ROM-Routinen zur Verfügung, die Sie gleich noch kennenlernen werden. Zunächst jedoch zurück zu dem zweiten Fließkomme-Format von demm die Rede war.
b) Es handelt sich um das Format, in dem die Fließkommazahlen im Speicher abgelegt werden, entweder als Ergebnis einer Berechung, oder als Operanden für die Rechenoperationen in FAC und ARG. Ein Format, das sich auf den Arbeitsspeicher bezieht, sollte möglichst kurz und speicherplatzsparend sein. Ein eigenes Byte für ein Vorzeichen ist jetzt völlig ausgeschlossen. Die ganze Fließkommazehl wird nun durch einen kleinen Trick auf 5 Bytes beschränkt.
Wir wissen, daß rechts neben dem Binärpunkt der normalisierten Mantisse eine 1 steht ( deshalb haben wir den Punkt ja dorthin gesetzt) . Wenn uns sowieso bekannt ist, daß es sich um eine 1 handelt, dann brauchen wir sie doch nicht mehr mit abzuspeichern. Diese 1 muß lediglich bei allen Berechnungen berücksichtigt werden. Diese Bits nennt man Hidden Bits ( versteckte Bits) . Da das äußerst links stehende Bit der Mantisse nun frei geworden ist, können wir dort unser Vorzeichen unterbringen.
Tatsächlich benötigen wir jetzt nur noch 5 Bytes für die Zahlendarstellung.
Format im Arbeitsspeicher:

              /--------\                
              |10000110|                
              \--------/                

Exponent

   /Vorzeichen-Bit                      
  /+-------+--------+--------+--------\ 
  |01001000|10000000|00000000|00000000| 
  \--------+--------+--------+--------/ 
       1        2        3        4     
             M A N T I S S E            

Hexadezimal erhält man:8648800000 ROM-Routinen zur Handhabung der Fließkommazahlen Innerhalb dises Kursteiles kann ich Ihnen leider nur eine kleine Auswahl dieser ROM-Routinen vorstellen.
$ B3 A2| Wandelt eine vorzeichenlose ganze | Zahl (0 . . .255), die im Y-Register | steht, in eine FLP-Zahl im FAC um.

     |                                  
$BBA2|Lädt FAC mit  einer  FLP-Zahl  aus
     |dem Arbeitsspeicher, auf  die  der
     |Zeiger Akku/Y-Register  weist  und
     |wandelt sie in  das  benötigte  6-
     |Byte-Format um.                   
     |                                  
$BA8C|Lädt ARG mit  einer  FLP-Zahl  aus
     |dem Arbeitsspeicher, auf  die  der
     |Zeiger Akku/Y-Register  weist  und
     |wandelt sie  in  das  benötigt  6-
     |Byte-Format um.                   
     |                                  
$BC0C|Kopiert den Inhalt des FAC in  den
     |ARG.                              
     |                                  
$BBFC|Kopiert den Inhalt des ARG in  den
     |FAC.                              
-----+----------------------------------
$B7F7|Wandelt den Inhalt des FAC in eine
     |vorzeichenlose  Integerzahl  (0...
     |65535) in den Speicherzellen  $14/
     |$15 und Y-Register/Akkumulator um.
     |                                  

$ BDDD| Wandelt den Inhalt von FAC in eine | ASCII-Zeichenkette und legt sie im | Puffer am $0100 ab.
|$ BBD4| Wandelt den FAC ( Rechenergebnis)| in eine 5- Byte-FLP- Zahl und legt | sie im Speicher under der Adresse | ab, die der Zeiger X-Register/ Y-| Register bildet.
Die USR-Funktion ( User callable machine language SubRoutine) Bisher waren wir es gewohnt, unsere ASSEMBLER-Programme von BASIC aus mit SYS-Befehlen aufzurufen. Die Parameterübergabe konnte nur durhc das POKEn der Werte an die jeweilige Adresse erfolgen.
Wenn der zu übergebende Wert jedoch eine FLP-Zahl ist, dann müßte die Zahl in BASIC zerlegt werden, um sie dann stückweise in den FAC zu POKEn. Das wäre doch sehr umständlich. Speziell für das Bearbeiten von Fließkommazahlen gibt es daher die USR-Funktion. Der Vorteil dieser Funktion ist, daß der Inhalt der angegebenen Variablen automatisch in den FAC übertragen wird. Aber woher weiß der Computer , wo unsere ASSEMBLER-Routine beginnt? Bei der Ausführung von USR wird der indirekte Sprung JMP ($0311) ausgeführt. Die Einsprungadresse des ASSEMBLER-Programms muß als Zeiger in den Adressen $0311( niederwertiges Byte) und $0312( höherwertiges Byte) vor dem Aufruf bereitgestellt werden.
Unter anderem enthält das Programm " ASSEMBLER-KURS 9" auch ein Beispiel für die Benutzung von USR.
Im nächsten ( und letzten) Kursteil geht es dann um den Umgang mit den Interrupts.

Also, bis zum nächsten Kursteil...      
                                 (rt/wk)

Assembler-Kurs Teil 10 Wie versprochen, handelt der letzte Teil dieses Kurses von en Interrupts.
Interrupt bedeutet soviel wie " Unterbrechung" des laufenden Programms. Der C64 reagiert auf verschiedene Arten von Interrupts, die man in Hardund Softwareinterrups unterteilen kann.

Hardware-Interrupts                     

Es gibt zwei unterschiedliche Arten von Hardware-Interrupts, den IRQ ( interrupt request = Interrupt Anforderung) und den NMI ( non maskable interrupt = Nicht maskierbarer Interrupt) . Beide sind eigentlich Pins am Prozessor, an denen durch ein Impuls ein entsprechender Interrupt ausgelöst werden kann. Die Reaktion des Prozessors auf beide Interrupts ist etwas unterschiedlich.
Grundsätzlich sagt man, daß der NMI die höhere Priorität beider Interrupt-Arten hat und somit für die für das System wichtigen Aufgaben eingesetzt wird.
a) Ablauf eines NMI:
1 . Der momentan zu bearbeitende Befehl des laufenden Programms wird noch ausgeführt.
2 . Der Programmzähler ( program counter), d. h. das Register, das immer auf den nachfolgenden Befehl eines Programms zeigt, wird auf den Stack geschoben. Dabei wird zuerst das höherwertige und dann das niederwertige Byte des Programmzählers abgelegt.
3 . Das Statusregister, das alle Flags enthält, wird auf den Stack gebracht.
4 . Der Inhalt der Adressen $ FFFA ( als niederwertiges Byte) und $ FFFB ( als höherwertiges Byte) wird zusammengesetzt und als neuer Programmzähler benutzt. Ab jetzt wird also das ( unterbrechende) Programm an der Stelle ausgeführt, auf die der neue Programmzähler zeigt.
b) Ablauf eines IRQ:
1 . Zunächst scheint der IRQ genauso zu verlaufen, wie der NMI. Der augenblicklich zu bearbeitende Befehl des laufenden Programms wird noch vollständig abgearbeitet.
2 . Anschließend erfolgt eine Überprüfung des Interrupt-Flags ( Bit 2 des Statusregisters) . Wenn das Bit gesetzt ist, dann wird die Interrupt-Anforderung einfach ignoriert, und der Prozessor fährt fort, als ob nichts geschehen wäre.
Ist das Interrupt-Bit jedoch gelöscht, so wird der IRQ ausgeführt.
3 . Der Programmzähler wird auf den Stack geschrieben.
4 . Das Statusregister wird gestackt.
5 . Nun wird das Interrupt-Flag gesetzt, so daß alle nachfolgenden Interrupt-Anforderungen ignoriert werden. Man bezeichnet diesen Vorgang als ' Sperren' des IRQ.
6 . Der Inhalt der Adressen $ FFFE und $ FFFF wird zusammengesetzt und als neuer Programmzähler benutzt.
Der Hauptunterschied zwischen NMI und IRQ liegt darin, daß der IRQ maskiert ( d. h. gesperrt) werden kann.
Wie oben bereits beschrieben, wird beim IRQ zuerst das I-Flag untersucht und erst daraufhin entschieden, ob ein Interrupt auszuführen ist, oder nicht.
Dieses Interrupt-Flag des maskierten Interrupts IRQ kann vom Programmierer selbst direkt beeinflußt werden.
Mit dem Befehl SEI ( SEt Interrupt mask) wird das I-Flag gesetzt und somit werden alle folgenden IRQs gesperrt. Durch CLI( CLear Interrupt mask) kann dieser Zustand wieder aufgehoben werden, indem das I-Flag gelöscht wird. Ein IRQ ist nun für den nachfolgenden Programmteil wieder zugelassen. Häufige Anwendung finden diese beiden Befehle auch beim Umschalten der Speicherkonfiguration durch den Prozessorport. Ohne das vorherige Abschalten der Interrupts mittels SEI kann es beim Ausschalten von ROM-Berichen zum Systemabsturz kommen.
Wodurch können die Interrupt IRQ und NMI ausgelöst werden ?
Selbstverständlich nur durch die Hardware des Computers:
IRQ: a) durch den Videocontroller VIC b) durch den CIA-Baustein ($ DC00) NMI: a) durch den CIA-Baustein ($ DD00) b) durch die RESTORE-Taste

Der Software-Interrupt                  

Software-Interrupts werden durch den Befehl BRK ( BReaK) ausgelöst. Das war einer der ersten Befehle, die wir in diesem Kurs kennengelernt haben. Nun wollen wir untersuchen, was genau passiert, wenn der Prozessor auf einen BRK-Befehl im Programm stößt:
1 . Das Break-Flag ( Bit 4 des Statusregisters) wird gesetzt.
2 . Der Programmzähler wird auf den Stack gebracht.
3 . Das Statusregister wird gestackt.
4 . Nun wird das Interrupt-Flag gesetzt, so daß alle IRQs gesperrt werden.
5 . Der Inhalt der Adressen $ FFFE und $ FFFF ( dieselben Adressen wie beim IRQ) wird zusammengesetzt und als neuer Programmzähler benutzt.
Meistens wird der BRK-Interrupt von den Assembler-Monitoren dazu genutzt, um das laufende Programm abzubrechen und das Statusregister mit den aktuellen Flags anzuzeigen. Es wäre aber auch eine andere Nutzung dieses Interrupts möglich.
Der Software-Interrupt BRK wird also im Gegensatz zu den Hardware-Interrupts durch einen Assembler-Befehl ausgelöst.
Egal, ob IRQ, NMI oder der Softwareinterrupt BRK. Eines haben alle gemeinsam. Sie lesen alle einen Zeiger aus ($ FFFE/ FFFF bei IRQ, BRQ;$ FFFA/$ FFFB bei NMI) der auf eine ROM-Routine weist.
Diese ROM-Routinen werden nun als Interruptprogramm abgearbeitet und führen am Ende einen unbedingten Sprung aus. Dieser unbedingt e Sprung beruft sich auf einen Zeiger, der im RAM steht.

Diese Zeiger sind: für IRQ   $0314/$0315
                   für BRK   $0316/$0317
                   für NMI   $0318/$0319

Diese Zeiger sind schon mit einem bestimmten Wert initialisiert, so daß der Sprung ohne unser Eingreifen bestens funktioniert. Wie Sie schon ahnen werden, müssen wir diesen Zeiger lediglich auf unsere selbstgeschriebenen Routinen umbiegen, und schon haben wir eigene Interruptroutinen.

Das Ende der Interrupts: RTI            

Wenn das Programm durch einen Interrupt unterbrochen wurde und der Prozessor nun eine Interrupt-Routine bearbeitet, dann muß ihm auch irgendwie kenntlich gemacht werden, wann diese Unterbrechung beendet werden soll. Auch die Interruptroutinen benötigen daher ein Programmende. Für ' normale' Programme haben wir als Kennzeichnung für das Programmende immer den RTS-Befehl ( ohne vorherigen JSR-Befehl) im Hauptprogramm benutzt, wodurch wir zurück ins BASIC kamen.
Der Befehl für das Ende unser selbstgeschriebenen Interrupt-Routine lautet RTI ( ReTurn from Interrupt) . Bei der Bearbeitung dieses Befehls läuft der umgekehrte Prozeß des Interrupt-Aufrufs ab.
1 . Das Statusregister muß vom Stack geholt werden.
2 . Der Programmzähler wird vom Stack geholt und wieder als aktueller Zähler benutzt.
3 . An der Stelle, auf die der Programmzähler zeigt, wird das unterbrochene Programm fortgeführt.
Der Hauptunterschied zwischen RTS und RTI liegt darin, daß beim RTI zusätzlich zum Programmzähler auch noch das Statusregister vom Stack geholt werden muß.
Ein Interrupt hat doch einige Ähnlichkeiten mit einem Unterprogrammaufruf.
Ein Unterprogramm wird jedoch von einer klar definierten Stelle im Hauptprogramm ( durch den JSR-Befehl) aufgerufen. Die Interrupts NMI und IRQ werden jedoch hardwaremäßig ausgelöst, so daß die Unterbrechung des Hauptprogramms an jeder beliebigen Stelle stattfinden kann. Diese Eigenschaft erweckt den Eindruck, daß die Interruptroutine und das Hauptprogramm parallel arbeiten, was aber natürlich nicht der Fall ist.
Im Begleitprogramm " ASSEMBLER-KURS10" werden wir eigene Interrupt-Routinen schreiben. Dabei geht es um die Benutzung des Systeminterrupts und der Interrupts durch den Videocontroller ( Rasterzeilen-Interrupt) .

Dies war der letzte Teil des  Assembler-
Kurses.  Ich  hoffe,  er  konnte   Ihnen
einige   Kenntnisse    und    Anregungen
verschaffen.                            
                                   rt/wk