Flaggen und Abzweige - die Null-Flagge bzw. das Zero Flag ("Programmieren" lernen)

  • Eine wichtige Errungenschaft von CPUs sind Flaggen !

    Diese Übersetzung findet sich in der deutschen Ausgabe von Rodney Zaks Klassiker. Gemeint sind natürlich keine Fahnen oder Wimpel sondern "Flags". Eine schönere Übersetzung wäre vielleicht Warnlampen oder Informationsleuchtpult.

    Real sind Flags natürlich keine Lampen oder Flaggen, sondern bestimmte Bits an einer bestimmten immer gleichen Stelle, die ein oder ausgeschaltet werden.


    So eine CPU macht nicht nur verschiedenes, sie signalisiert auch, wenn bestimmte Umstände eintreten. Ein Beispiel war bereits öfters erwähnt worden, immer dann wenn ein Überlauf eintritt, wird das von der CPU bemerkt und "gemeldet", d.h. an einer bestimmten Stelle signalisiert (nämlich im sogenannten Überlauf Flag).


    Welche Dinge eine bestimmte CPU insgesamt überwacht, hängt nun wieder vom Modell ab. Manches wird evtl. intern auch bemerkt, aber nicht nach außen mitgeteilt, d.h. mittels Flag mitgeteilt.

    Außerdem ist es auch noch so, daß gar nicht alle Befehle wirklich alle Flags beeinflussen können. Damit man da den Überblick nicht verliert gibt es dann wieder Tabellen. Es bedeutet für den Programmierer, daß er aufpassen muß, ob das Flag, was er abfragen möchte überhaupt von dem Befehl beeinflußt wird, den er schreiben möchte.


    Meist ist es so, daß einfach der letzte Befehl, der gerade ausgeführt worden ist, die Flags verändert hat und im Anschluß schaut man dann nach, ob ein bestimmtes Flag nun gesetzt ist oder nicht. In Abhängigkeit davon, wie der Flag-Zustand des interessierenden Flags dann gerade ist, macht man evtl. an einer anderen Stelle im Programm weiter.



    Welche Umstände in der CPU sind nun so besonders, daß es lohnt sie zu melden ?


    Eigentlich alle bei denen eine Zahl die gerade bearbeitet wurde einen besonderen Wert annimmt.


    Oft sind das

    • Überläufe - wenn also das "Zahlrädchen" (Register) vom maximalen Wert auf Null (oder noch weiter) weitergezählt wurde, das Register also überläuft
    • Vorzeichenwechsel - wenn eine Zahl, die gerade so betrachtet wird, daß sie auch negative Zahlen darstellen kann, von + nach - wechselt
    • Rechenoperationen, bei denen im Ergebnis eine Zahl herauskommt, die einen Übertrag in die nächste Stelle erfordert

    und ganz wichtig

    • wenn in einem Register oder bei der letzten CPU Arbeit eine 0 (Null) entstanden ist


    Dieses Flag, was bemerkt, ob da eine 0 da war, nennt man das Zero-Flag !



    Wenn also etwas ein Register mit dem Wert 0 geladen wird ( LADE Register(Name) $00 ), wird i.a. automatisch auch die Null-Flagge, das Zero-Flag, gesetzt.

    Wenn man beim Weiterzählen eines Registers wieder bei Null ankommt, weil die Zahlenmenge quasi komplett durchgezählt ist, wird auch das Zero-Flag gesetzt.

    Wenn man zwei Zahlen miteinander vergleicht und beide den gleichen Wert haben, wird das Zero-Flag gesetzt.



    Es gibt auch CPUs bei denen man bei jedem Befehl entscheiden kann, ob die Flags beeinflußt werden oder nicht. Bei vielen anderen macht die CPU das automatisch ohne Nachfrage. Was dafür dann aber bei manchen Flags geht, ist, daß man sie gezielt selber ein- oder ausschalten kann. (etwa das Carry Flag oder das Interrupt Flag)


    Wozu ist das nun gut ?


    Es erlaubt einem Fallunterscheidungen zu machen und auch auf die besonderen Zustände der Zahlen/Register zu reagieren.

    Wie das geschieht hängt wieder von der CPU ab. Die ARM CPUs haben hier z.B. eine ihrer ganz großen Stärken, indem sie nämlich i.P. die Ausführung eines jeden Befehls davon abhängig machen können, ob ein bestimmtes frei wählbares Flag gesetzt ist oder eben nicht.

    Bei anderen CPUs gibt es Sprungbefehle, die nur dann einen Sprung ausführen, wenn das zugehörige Flag gesetzt ist. Damit kann man dann den Programmablauf woanders fortsetzen, wenn das Flag "an" war. Meist gibt es daneben auch noch einen weiteren Sprungbefehl, der nur dann ausgeführt wird, wenn das zu ihm gehörende Flag "aus" war.


    Das sieht also so aus


    Flag ---> GESETZT ? ANGESCHALTET ? ---> Springe nach Adresse $Flag-ist-ON

    Flag ---> AUSGESCHALTET ? ---> Springe nach $Flag-ist-OFF


    ( Es gibt dabei auch Sprungbefehle, die mehrere solche Flags in einer Anweisung zusammen abfragen. Solches Verhalten läßt sich aber auch aus einfachen Einzelflagabfragen zusammenbauen, wenn man mehrere solche hintereinander ausführt.)



    Die Flags selber werden in einem sogenannten Statusregister gespeichert. Natürlich nur solange, bis sich wieder ein Flag ändert, was bereits beim nächsten Befehl passieren kann.


    Da die Flags ja unmittelbar Auskunft über die letzte Operation geben, kann es manchmal sehr wichtig sein, dieses Statusregister (also alle Flags auf einmal) woanders abzuspeichern. Vor allem dann, wenn man erstmal was anderes rechnen will und später aber an exakt der aktuellen Stelle weitermachen möchte (etwa bei einem Interrupt oder einer Unterfunktion).

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502=


    Das Statusregister wird in der Zeile wo die Register stehen auch mit angezeigt. Allerdings steht das als hexadezimaler Wert dort. Damit man da "per Blick" sehen kann, welche Flags gesetzt sind, braucht es viel Übung. ( bei SR )


    Das Zero-Flag ist beim 6502 ein "dankbares Flag", weil so ziemlich jeder Befehl es verändert (Ausnahmen gibts natürlich).


    Die zum Zero-Flag gehörenden beiden Befehle für die Abfrage sind beim 6502


    BNE - was einen Sprung ausführt, wenn das Zero-Flag nicht gesetzt war, und

    BEQ - was genau dann springt, wenn das Zero-Flag an war


    Das B steht dabei für Branch ( Verzweige ) und die beiden Buchstaben dahinter haben die Bedeutung

    NE = Not Equal (Ungleich) bzw. EQ = Equal (Gleich)

    Das ist natürlich besonders sinnig, wenn man vorher mit einem Vergleichsbefehl zwei Zahlen verglichen hat.


    Leider springen die nicht überallhin, sondern nur in die unmittelbare Nähe und zwar relativ, also vor oder zurück vom aktuellen Punkt im Programm.



    Ohne Vergleichsbefehl sind sie bereits super zum Abzählen, wenn man sich auf Abzählen bis max. 255 beschränken kann.


    Dazu lädt man einfach ein Register mit dem Zählwert + 1 und zählt es runter. Wenn es dabei Null erreicht, wird das Zero-Flag gesetzt. Sonst war es ungesetzt. Man springt daher mit BNE einfach zurück und zählt weiter runter.

    Ist das Zero-Flag dann endlich an, wird der Sprung nicht mehr ausgeführt und es geht hinter dem BNE weiter.


    LDX #$zählwert+1

    (...)

    DEX
    BNE zurück nach (...)


    Da man so nicht mehr immer das gesamte Register durchrotieren muß, können wir uns jetzt auf eine Bildschirmzeile beschränken. Diese hat (dezimal) 40 Zeichen, also 0 bis 39, das sind (hexadezimal) $00 bis $27.

    Da man mit $0c00,X "inidziert adressieren" kann, ist das letzte Zeichen der Zeile zuerst ansprechbar, wenn man die $27 ins X-Register lädt.

    Wenn man dagegen das erste Zeichen ansprechen will, müßte der XR Wert "0" sein um bei $0c00 zu landen. Da wir aber hier das Zero-Flag testen wollen, erreichen wir den ersten Wert gar nicht mehr - und darum fangen wir bei Adresse $0bff an, was genau die VOR $0c00 ist.

    Außerdem wird ganz am Anfang der Wert des letzten Zeichens der Zeile im Y-Register gesichert, weil man es am Ende wieder braucht.


    Wichtig ist, daß man einen Text in die erste Zeile tippt, sonst sieht man gar nichts.




    Ganz schön schnell ...


    Frage: In welche Richtung läuft die Schrift ?

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()

    • Offizieller Beitrag

    Hübsch. :)

    Die Schrift läuft von links nach rechts, die Daten werden an einer um 1 höheren Adresse geschreiben, als sie gelesen werden.

    Das Zeichen vom Zeilenende ($0C27) wird an den Zeilenanfang geschrieben ($0C00).

    +++ ATH

  • =6502=


    Genau ! , man sieht also, alles keine ganz große Kunst.


    Allerdings ist es ein bißchen zu schnell, um es lesen zu können.

    Was also hilfreich wäre, wäre eine Art Warteschleife, damit es auf eine schöne Geschwindigkeit gedrosselt wird.


    ( Daß Warteschleifen generell eher nicht gut sind, sollte bekannt sein. Zumindest in "echten" Programmen hat sowas nichts zu suchen, weil man damit ja allen anderen Tasks Rechenzeit wegnimmt, außerdem kann man ja schließlich Timer bemühen, die einem sagen wann es weitergeht. Trotzdem sind sie eine prima Sache, wenn man sie beim "Rumbasteln" irgendwo einsetzen kann und dann alles verzögert anschaut. Noch mehr Komfort habe dann echte Programmiertools mit TRACE und BREAKPOINTs. )


    Ein Warteschleife, die keine Zeit "kennt", muß währenddessen etwas machen. Je aufwendiger das ist, was sie tut, desto langsamer wird sie. Und sie muß das, was sie tut, öfters machen, sonst merkt man nichts von ihr.


    Öfters Dinge tun, geht gut in einer Zählschleife

    und genau das geht ja jetzt prima, mit der Abbruchbedingung, daß die Null (Zero) erreicht wurde.


    Strenggenommen ist das Konstrukt im Beispiel oben einfach eine Zählschleife mit einem Schrittwert von 1 und der Besonderheit, daß bisher nur rückwärts gezählt werden kann. Beim Zählen der Positionen (Bsp oben) passiert ja:

    FOR X = 39 TO 1 STEP -1

    oder vielleicht besser

    X=39 : WHILE X>0 DO X=X-1


    Und das sollte doch allen bißchen bekannt vorkommen.


    Wenn man nun will, daß das Zählen länger dauert, zählt man einfach in der Schleife nochmal - man baut verschachtelte Schleifen ; hier nehmen wir einfach wieder das XR und das YR her und zählen ... sonst passiert gar nichts weiter.


    LDX#$25

    LDY#$FF

    DEY

    BNE (springt auf DEY zurück)

    DEX

    BNE (springt auf den Beginn der innneren Schleife)


    Man kann das ja mal ausprobieren, und damit man doch was sieht, schreiben wir nun in der inneren Schleife noch die aktuellen Werte in den Bildschirm (oben links)



    Das geht recht flott, und man sollte es evtl. öfters mal starten. Eine gute Idee ist auch, den Startwert für das XR zu erhöhen, also statt der #$25 eine #$FF oder #$77 einzutragen.


    Das Konstrukt entspricht somit einem


    X=25 : WHILE X>0 DO

    ....Y=255 : WHILE Y>0 DO

    ........PRINT TAB(0,0) X,Y

    ........Y=Y-1

    ....WHILEEND

    ....X=X-1

    WHILEEND



    Und genau sowas bauen wir in den Scroller mit ein, nur dort ohne die Werte von XR und YR auszugeben.


    Es gibt aber noch ein anderes Problem: Die beiden Register XR und YR werden ja schon für den Scroller gebraucht !

    Man könnte also einfach die Warteschleife außerhalb des Scrollens dazufügen, da ist sie schön positioniert aber auch recht ineffektiv. Besser ist "MITTENDRIN". Also so, daß jeder Umkopiervorgang eines Zeichens verzögert wird.
    Und damit das klappt, müssen wir die Registerwerte für XR und YR vor der Warteschleife zwischenspeichern und danach die eigentlichen Werte wieder laden.




    Man sieht die neue Warteschleife bei $5011 bis $5019.

    XR und YR werden in $4000 und $4001 zwischengespeichert.

    Der Rest ist einfach der Scroller von oben.


    Wieder gilt, es muß ein Text in der obersten Bildschirmzeile stehen.

    Diesmal kann man aber durch Ändern der Werte der Warteschleife die Geschwindigkeit regulieren !


    Man kann den Wert auch ändern, ohne das Listing anzufassen, indem man eingibt

    >5012 80

    um ein LDX#$80 zu setzen, oder

    >5014 25

    um ein LDY#$25 zu setzen.


    Bitte selber verschiedene Werte probieren !

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    4 Mal editiert, zuletzt von ThoralfAsmussen ()