Assembler ohne Grenzen: Arbeiten mit dem Coprozessor

Programme werden meistens dann in Assembler geschrieben, wenn sie möglichst schnell und kurz sein sollen. Müssen jedoch mathematische Berechnungen mit Fließkommazahlen durchgeführt werden, ist der Programmieraufwand gewaltig. In diesen Fällen sind höhere Programmiersprachen oft der einzige Ausweg. Durch den mathematischen Coprozessor des TT gehört dieses Problem nun der Vergangenheit an.

Das Prozessorgespann 68020/30 + 68881/2, wie z.B. im Atari TT, macht es möglich, auf Assembler-Ebene genauso selbstverständlich mit Fließkommazahlen umzugehen wie in Compilersprachen. Der Umfang an mathematischen Funktionen des Coprozessors entspricht dem von Hochsprachen und geht sogar teilweise darüber hinaus, wobei die Anwendung dieser Funktionen nicht wesentlich schwieriger ist. Wenn man also mathematische Anwendungen in Assembler programmieren möchte, braucht man nicht jedesmal das Rad neu zu erfinden, sondern kann, wie in anderen Programmiersprachen, auf einen leistungsfähigen Befehlssatz zurückgreifen.

Ohne Coprozessor ist die Programmierung einer Sinusfunktion eine recht komplizierte Angelegenheit, da der Sinus durch eine Potenzreihe angenähert werden muß. Wenn eine hohe Genauigkeit gewünscht wird, muß außerdem noch mit unhandlichen, 80 Bit großen Zahlen gearbeitet werden, wodurch das Ganze in eine wilde Bit-Schieberei ausartet. Eine entsprechende Routine würde mehrere Seiten Quelltext beanspruchen. Für Nichtmathematiker: Die Potenzreihe für eine Sinusberechnung mit ca. 3 genauen Nachkommastellen sieht folgendermaßen aus:

sin(x)=x-x^3/3!+x^5/5!-x^7/7!+x^9/9!

Mit Coprozessor sieht das gleiche so aus:

FSIN.X #x,FP0

Diese „Routine“ ist nicht nur ungleich kürzer, sondern auch um den Faktor 50 schneller als die andere, außerdem wird hier mit bis zu 19 Nachkommastellen gerechnet. Man erspart sich also viel Arbeit und hat gleichzeitig den Vorteil einer wesentlich höheren Geschwindigkeit. Es ist durchaus möglich, durch effiziente Programmierung des Coprozessors, mathematische Routinen um das 30-50fache zu beschleunigen. Selbst wenn man eine Hochsprache hat, die den Coprozessor unterstützt, lohnt es sich, diejenigen Programmteile, die Fließkommaarithmetik benutzen, in Assembler zu schreiben. Denn selbst gegenüber den schnellsten Compiler-Sprachen kann man noch leicht eine 50-200-prozentige Geschwindigkeitssteigerung erzielen. Außerdem sind Programmiersprachen, die den Coprozessor unterstützen, zur Zeit sehr teuer, und einige sind zudem recht fehlerhaft.

Wenn man über ein wenig Programmiererfahrung mit dem 68000 verfügt, ist es mit wenig Aufwand und Einarbeitungszeit möglich, auch komplizierte mathematische Probleme mit dem 68881/2 in Assembler zu lösen. Man braucht dafür nicht wesentlich länger als mit einer Hochsprache, aber durch die ungleich schnellere Ausführung der Programme wird man für diese Arbeit auf jeden Fall belohnt.

Ich möchte noch bemerken, daß ich hier nicht mehr auf die Programmierung der Prozessoren 60000-68030 eingehe. Es ist nicht unbedingt nötig, die zusätzlichen Befehle und Adressierungsarten des 68020/ 30 zu kennen, aber es erleichtert viele Dinge ungemein.

Extras!

Durch den 68881/2 wird der Befehlssatz des 68020/30 in drei Bereichen erweitert:

  1. 49 Befehle (siehe Bild 1)
  2. 11 Register : 8 Datenregister FP0-FP7 (80 Bit), 3 Kontrollregister FPCR, FPSR, FPIAR (32 Bit)
  3. 4 Datentypen: Single Precision Real (32 Bit), Double Precision Real (64 Bit), Extended Precision Real (96 Bit), Packed Decimal String Real (96 Bit)

Wichtig ist dabei, daß die physikalische Trennung der beiden Prozessoren für den Programmierer nicht transparent ist, d.h. die Entscheidung, welcher Prozessor einen Befehl ausführen muß, wird von der Hardware getroffen. Dies gilt nicht für den 68000-Prozessor, da er keine Coprozessorschnittstelle besitzt. Bei einem Mega ST/E wird der Coprozessor, falls vorhanden, über Hardware-Adressen angesprochen. Dadurch wird die Programmierung sehr kompliziert und damit unattraktiv. Bei einem Rechner mit 68020/30 und 68881/2 hat man also sozusagen, „einen“ Prozessor mit erweitertem Befehlssatz und zusätzlichen Registern. Der Coprozessor kann insgesamt mit 7 Datentypen arbeiten, welche allerdings nur für die Kommunikation mit der Außenwelt bestimmt sind, da intern immer mit höchster Genauigkeit und einem eigenen Format gearbeitet wird. Das heißt, es müssen alle Operanden in das interne Format gewandelt werden, bevor mit ihnen gerechnet werden kann.

Bild 1: Der Befehlssatz des 68881/2

Darüber braucht man sich jedoch keine Gedanken zu machen, da der Coprozessor sämtliche Formatumwandlungen vollkommen automatisch erledigt. Dadurch ist es möglich, Berechnungen mit beliebigen Zahlenformaten durchzuführen, ohne daß man sich um deren Anpassung kümmern muß. Notwendig ist nur die Angabe des Datentypes des Quelloperanden. Der Zieloperand ist bei arithmetischen Operationen immer ein FPU-Datenregister, wie aus Bild 1 zu ersehen ist.

Der Aufbau der 4 neuen Datentypen ist in Bild 2 veranschaulicht. Bei SINGLE, DOUBLE und EXTENDED ist der innere Aufbau jedoch für die Programmierung nicht von Bedeutung, hier aber der Vollständigkeit halber angegeben. Wichtig ist nur, welche Genauigkeit die Datentypen haben und wieviel Platz sie im Speicher benötigen. Zum Rechnen können alle Datentypen von Byte bis Extended benutzt werden, nur das Packed-Decimal-String Real Format sollte nicht verwendet werden. Es ist, wie der Name schon sagt, einem String sehr ähnlich und daher leicht in einen solchen zu verwandeln, wie das kleine Programm zeigt. Die umgekehrte Richtung geht so ähnlich, allerdings muß man dann eventuell vorhandene Fehler abfangen. Der Befehl FMOVE.P FPm, <ea>{#kj bzw. {Dnj bietet die Möglichkeit zu bestimmen, mit wievielen Stellen eine Zahl ausgegeben werden soll. Man hat dabei die Wahl zwischen der Anzahl der Stellen rechts vom Komma, wobei der Exponent berücksichtigt wird, und der Anzahl der Stellen in der Mantisse. Mit Hilfe dieser Informationen steht der Programmierung beliebiger mathematischer Funktionen nichts mehr im Wege (siehe Bilder 1-3).

Beispiele:

D0.B=SQRT(D0.B)
FSQRT.B     D0,FP0 
FMOVE.B     FP0,D0 
D1.W=SIN(Pi/D0.B)
FMOVECR.X   #0,FP0 
FDIV.B      D0,FP0 
FSIN.X      FP0 
FMOVE.W     FP0,D1

Es soll folgender Graph gezeichnet werden:

20*e^(SIN(x/k1)+COS(x/k2))+k3

Die Konstanten k1, k2, k3 (16 Bit) stehen ab der Stelle, auf die A0 zeigt. Die Variable x läuft von 0 bis 640.

MOVEQ #0,D0
    FMOVE.B #20,FP0 
LOOP:
    FMOVE.W     D0,FP1
    FMOVE.W     D0,FP2 
    FDIV.W      (A0),FP1 
    FDIV.W      2(A0),FP2 
    FSIN.X      FP1 
    FCOS.X      FP2
    FADD X      FP2,FP1 
    FETOX.X     FP1 
    FMUL.X      FP0,FP1 
    FADD.W      4(A0),FP1
    FMOVE.W     FP1,D1 
BSR     PUNKT
ADDQ.W  #1,D0
CMPI.W  #640,D0 
BNE     LOOP

(siehe auch das Rosetten- und das Wellen-Programm auf der Leserservicediskette)

Bedingte Verzweigungen

Bild 2: Außau der neuen Datentypen

Genau wie bei der CPU, gibt es auch bei der FPU Befehle, deren Ausführung von Bedingungen abhängt. Dies sind die Befehle FBcc, FDBcc, FScc und FTRAPcc.

Der Unterschied zu den entsprechenden Befehlen der CPU besteht darin, daß hier das Condition-Code-Byte des FPSR ausgewertet wird. Da die Bedeutung der Bits N, Z und I klar sein dürfte, werde ich nur das NAN-Bit erläutern: Das Ergebnis einer arithmetischen Operation (z.B. FADD) kann in drei Fälle aufgespalten werden. Der 1. Fall (Normalfall) tritt ein, wenn alle Operanden im Definitionsbereich der jeweiligen Funktion liegen. Im 2. Fall kann die Funktion zwar nicht errechnet werden, das Ergebnis ist jedoch mathematisch festgelegt, z.B. O+Unendlich = Unendlich, -1*Unendlich = -Unendlich, LN(0) = Unendlich. Der letzte Fall tritt dann auf, wenn das Ergebnis mathematisch nicht zu bestimmen ist, z.B. -Unendlich+Unendlich, 0/0, LN(-1). Wenn dies passiert, wird das NAN-Bit gesetzt, und gleichzeitig wird dem Zieloperanden ein sogenanntes NAN-Format zugewiesen. Sollte bei einer Operation einer der Operanden gleich NAN sein, so ist das Ergebnis ebenfalls NAN, und das NAN-Bit wird in diesem Fall auch gesetzt. Das I-Bit wird übrigens von den Bedingungscodes nicht berücksichtigt. Wenn es für eigene Zwecke benötigt wird, kann man es durch ein „_FMOVE.L FPSR,Dn_“ in ein CPU-Datenregister schieben und dann selbst auswerten. Es gibt zwei Gruppen von Bedingungscodes (Bild 4), die sich voneinander darin unterscheiden, daß die einen das BSUN-Exception-Bit setzen können und die anderen nicht (BSUN s.u.). Die sogenannten IEEE Non-Aware-Bedingungscodes sind jedoch nur für denjenigen von Interesse, der tiefer in diese Materie einsteigen will. Nur noch eine Bemerkung hierzu : Durch die Existenz von NANs, also Zahlen, die keine sind, werden die Bedingungscodes etwas komplizierter als nötig. Das Gegenteil von „Less Than“ ist dann nicht mehr „Greater Than or Equal“, sondern „Not Less Than“.

Nun noch ein abschließendes Beispiel hierzu: Die folgenden Zeilen realisieren eine Schleife, die von 0 bis 2*Pi in Schritten der Größe 0.1 zählt.

    FMOVECR.X   #0,FP0 
    FMUL.B      #2,FP0 
    FMOVE.B     #1,FP1
    FDIV.B      #10,FP1
    FMOVE.B     #0,FP2
LOOP:

(beliebiges Programm)

    FADD.X      FP1,FP2 
    FCMP.X      FP0,FP2 
    FBOLE       LOOP

Fehler aufgetreten?

Um festzustellen, ob während einer Rechnung ein Fehler aufgetreten ist, benötigt man das Status- und das Control-Register der FPU (Bild 5), genauer gesagt das Exception-Enable- und das Exception-Status-Byte. Beide Bytes haben den gleichen Aufbau, jedoch eine unterschiedliche Bedeutung. Wenn irgendein Fehler auftritt, wird im Exception Status Byte das entsprechende Bit gesetzt. Das Exception Enable-Byte hingegen stellt eine Maske dar, die angibt, welche Fehler eine Exception-Behandlung auslösen können. Ist z.B. im Exception-Enable-Byte das DZ-Bit gesetzt, wird im Falle einer Division durch Null eine Ausnahmebehandlung (Exception) über Vektor 50 ausgelöst. Dadurch hat man die Möglichkeit, für jeden Fehler eine Routine im Betriebssystem zu installieren, die eine entsprechende Fehlerbehandlung durchführt. Man könnte zum Beispiel eine Dialogbox ausgeben, die es einem ermöglicht, das Programm abzubrechen oder es mit korrigierten Parametern fortzusetzen. Da das TOS diese Vektoren nicht belegt, sollten normalerweise alle Bits im Exception-Enable-Byte auf Null gesetzt sein.

Bild 5: Das Status- und Control-Register der FPU
Bild 3+4: Es gibt zwei Gruppen von Bedingungscodes

Das Exception-Status-Byte stellt jedoch nur eine Momentaufnahme dar, d.h. beim nächsten Befehl werden unter Umständen alle Bits verändert. Dadurch wäre man gezwungen, nach jedem Befehl abzufragen, ob irgendein Fehler aufgetreten ist. Dies ist aber in den meisten Fällen nicht sinnvoll und auch zu umständlich. Das Accrued-Exception-Byte schafft hier Abhilfe, da es sich das Auftreten eines Fehlers beliebig lange merkt. Jedes einmal gesetzte Bit kann nur vom Benutzer durch einen FMOVE-Befehl gelöscht werden. Wenn man also dieses Byte vor einer längeren Berechnung löscht, kann hinterher festgestellt werden, ob ein bestimmter Fehler aufgetreten ist, um dann zu entscheiden, ob das Ergebnis gültig ist oder nicht. Es ist dann allerdings nicht mehr möglich zu rekonstruieren, welcher Befehl den Fehler ausgelöst hat.

Die Bedeutung der verschiedenen Bits im einzelnen:

BSUN: Dieses Bit wird gesetzt, wenn das NAN-Bit gesetzt ist und einer der Befehle FBcc. FDBcc, FScc und FTRAPcc im Zusammenhang mit einer IEEE Non-Aware Bedingung benutzt wird.

SNAN: Durch das sogenannte „Signaling Nan“-Format ist es möglich, eigene Datenformate zu kreieren.

OPerr: Dieser Fehler tritt auf, wenn bei bestimmten arithmetischen Operationen der Quelloperand außerhalb des mathematischen Definitionsbereiches liegt. OPerr wird ebenfalls gesetzt, wenn das NAN-Bit gesetzt wird. Beispiele: ACOS(x) ist nicht definiert für x=+-Unendlich, x>1 und x<-1; SQRT(x) ist nicht definiert für x<0 und x=-Unendlich; OPerr wird auch gesetzt, wenn bei FMOVE.B/W/L der Quelloperand nicht in das Integerformat paßt oder wenn er NAN ist.

OVFL: Ein Overflow tritt dann auf, wenn beim Arbeiten mit single-, double- oder extended-precision-Zahlen, unter Beachtung der Rundungsgenauigkeit, die zuzuweisende Zahl zu groß ist, um im Zieloperanden gespeichert werden zu können.

UNFL: Ähnlich wie OVFL, aber hier ist die Zahl zu klein, um noch dargestellt werden zu können. Bei B/W/L wird die Null zugewiesen ohne UNFL zu setzen.

DZ: Division durch Null. Tritt aber auch in folgenden Fällen ein: FATANH(+-1), FLOGxx(0), FLOGNPl(-1).

INEX1: tritt dann auf, wenn eine dezimale Zahl (,P Format) eingelesen wird, die binär nicht genau dargestellt werden kann, z.B:0.1.

INEX2: Sobald eine Zahl gerundet wird, also Nachkommastellen abgeschnitten werden, wird INEX2 gesetzt. Dies hängt natürlich von der Rundungsgenauigkeit ab.

Geht es noch schneller?

Diese Frage kann positiv beantwortet werden, denn trotz der enormen Geschwindigkeit der FPU ist es möglich, Programme mit einfachen Mitteln wirkungsvoll zu beschleunigen. Ein bekanntes Mittel zur Beschleunigung von Programmen ist die registeroptimierte Programmierung, welche natürlich auch bei der FPU nicht wirkungslos ist, denn es fällt nicht nur ein Lesezyklus weg, sondern auch die Umwandlung der Operanden in das interne 80-Bit-Format.

Die zweite Möglichkeit zur Optimierung liegt in der Tatsache begründet, daß die FPU ein eigenständiger Prozessor ist, denn während die FPU eine arithmetische Berechnung durchführt, kann die CPU ganz normal Weiterarbeiten. Dies ist jedoch nur dann machbar, wenn kein Prozessor auf das Ergebnis einer Berechnung des anderen wartet und kein Register von beiden benutzt wird. Die parallele Ausführung von Befehlen scheitert auch dann, wenn ein zweiter FPU-Befehl gestartet wird, während der erste noch bearbeitet wird. In diesem Fall warten beide Prozessoren auf die Fertigstellung des ersten Befehls. Zur Parallelverarbeitung eignen sich am besten diejenigen FPU-Befehle, welche die meiste Ausführungszeit benötigen. So kann die CPU beispielsweise 10 Multiplikationen durchführen, während die FPU einen Sinus berechnet. Dadurch wird die Sinusberechnung praktisch in Nullzeit erledigt. Diese effektive Art der Optimierung ist aber leider nur selten einsetzbar, da die genannten Einschränkungen bei der Programmierung oft auftreten.

Bild 6: Der FMOVE-Befehl beim 68882

Darüber hinaus ist der 68882, im Gegensatz zum 68881, in der Lage, auch eigene Befehle parallel zu verarbeiten oder deren Ausführung teilweise zu überlappen. Dies sind vor allem arithmetische Operationen im Zusammenhang mit FMOVE-Befehlen, welche keine Formatumwandlungen vornehmen müssen, wie z.B.: FMOVE FPm, FPn oder FMOVE.X <ea>,FPm. Man sollte immer versuchen, schnelle FMOVE-Befehle mit schnellen arithmetischen Befehlen zu kombinieren und umgekehrt. Die parallele Verarbeitung ist nicht möglich, wenn Registerkonflikte auftreten und wenn mit den Datentypen B, W, L, P gearbeitet wird. Folgende Befehle können nicht oder nur ganz minimal parallel ausgeführt werden: FBcc, FDBcc, FScc, FTRAPcc, FMOVEM. Beispiel für eine Optimierung auf dem 68882:

Programmschleifen sollten, wenn möglich, teilweise ‘aufgerollt’ werden.

FMOVE.X (A0)+,FP0 
FMUL.X  FP2,FP0 
FMOVE.X FP0,(A1)+

Die folgende Routine ist ca. 12 % schneller:

FMOVE.X (A0)+,FP0 
FMOVE.X (A0)+,FP1 
FMUL.X  FP2,FP0 
FMUL.X  FP2,FP1 
FMOVE.X FP0,(A1)+
FMOVE.X FP1,(A1)+

Diese Optimierungen bringen natürlich nichts, wenn die programmierten Funktionen nicht bereits auf mathematischem Wege vereinfacht wurden. Man sollte möglichst versuchen, die Anzahl der trigonometrischen Operationen so klein wie möglich zu halten. Zur Vereinfachung von Polynomen eignet sich das sogenannte Homer-Schema ganz besonders.

Beispiel:

2*x^3-4*x^2+x-1 (5 Multiplikationen)
-> -1+(1+(-4+2*x)*x)*x (3 Multiplikationen).

Die Demoprogramme

Das hier abgedruckte Rosette- und das Wellen-Programm auf der Leserservicediskette (siehe auch Titelbild) sollen verdeutlichen, wie einfach die Programmierung des Coprozessors ist. Da ich hier auf die Funktionsweise der Programme nicht eingehen möchte, habe ich die beiden GFA-BASIC-Listings beigefügt, welche die grundsätzliche Arbeitsweise verdeutlichen. Wie man sieht, sind die eigentlichen Hauptprogramme sehr kurz im Gegensatz zu den Eingaberoutinen, welche man allerdings universell einsetzen kann. Wer nicht viel tippen möchte, kann den Eingabeteil inklusive der entsprechenden Unterprogramme weglassen, muß dann allerdings die Parameter direkt eintragen und jedesmal neu assemblieren. Es kann jeder Assembler verwendet werden, der die Befehle des 68030 und des 68882 unterstützt, wie z.B. der MAS 68K. Allerdings sollte man den dazugehörigen Linker TLINK.TTP nicht verwenden, da er ab bestimmten Quellcodegrößen Fehler macht.

Die beiden Demos zeigen, wie groß die Beschleunigung von mathematischen Programmen durch den Coprozessor ist, und daß sich die Beschäftigung mit dessen Programmierung lohnt. Das Wellen-Programm benötigt beispielsweise für eine Animation mit 80 Bildern ca. 10 Minuten. Ein entsprechendes Programm in TURBO C benötigt ohne Coprozessor 3 Stunden, auf einem normalen ST sogar 11 Stunden. Auf einem Atari TT ist es also möglich, selbst aufwendige Animationen in einem Bruchteil der bisher üblichen Zeit zu berechnen.

Zum Schluß!

Diese kleine Einführung in die Programmierung des 68881/2 kann und will kein Ersatz für ein ausführliches Handbuch sein, aber es sollte trotzdem möglich sein, mit Hilfe dieser Informationen eigene Programme zu erstellen. Die Programmierung des Coprozessors ist wie gesagt sehr einfach, da er einem alle Aufgaben, die kompliziert sind oder viel Arbeit machen, einfach abnimmt; es ist jedoch ein umfangreiches Detailwissen vonnöten, um alle Möglichkeiten des 68881/2 nutzen zu können. Ich habe hier versucht, die wichtigsten Infonnationen zusammenzustellen, bin mir aber bewußt, daß diese Auswahl nicht jeden zufriedenstellen wird. Daher empfehle ich jedem, der sich intensiver mit dieser Materie beschäftigen möchte, entsprechende Fachliteratur zu erwerben. Für Fragen und Anregungen hier noch meine Adresse:

Jochen Fischer
Gierlichstr. 2
W-5120 Herzogenrath

Literatur:
Steve Williams
“68030 Assembly Language Reference“
Addison-Wesley Publishing Company Inc.


* Umwandlung des Packed Decimal String Real * Formates in einen String lea zahl,a0 ;Puffer für Zahl ; fmove.f <ea>,fpn ; fmove.p fpn,(a0){#k} lea string,a1 ;Puffer fur String btst.b #7,(a0) ;Zahl negativ ? beq positiv ;nein move.b #'-',(a1)+ ;ja positiv: move.b 3(a0),d0 ;erste Ziffer unpk d0,d1,#$3030 ;in ASCII wandeln move.b d1,(a1)+ ;in String schreiben move.b #'.',(a1)+ ;Komma setzen moveq #4,d2 ;Offset moveq #7,d3 ;Zähler m_loop: move.b (a0,d2.w),d0 ;2 Ziffern holen unpk d0,d1,#$3030 ;in ASCII wandeln move.w d1,(a1)+ ;in String schreiben addq.w #1,d2 ;Offset erhöhen dbf d3,m_loop ;bis zur letzten Ziffer move.b #'e',(a1)+ btst.b #6,(a0) ;Exponent negativ? beq positiv2 ;nein move.b #’-',(a1)+ ;ja positiv2: bfextu 2(a0){0,4},d0 ;1. Stelle d Exponents lsl.b #4,d0 bfextu (a0)(4,4},d1 ;2 Stelle d Exponents or.b d1,d0 ;1. u. 2. Stelle in D0 unpk d0,d1,#$3030 ;in ASCII wandeln move.w d1,(a1)+ move.b 1(a0).d0 unpk d0,d1,#$3030 ;3. und 4. Stelle move.w d1,(a1)+ * ---------------------------------------------- zahl: dc.l 0,0,0 * bss string: ds.b 20

****************************** * * * ROSETTE.TOS * * * * by Jochen Fischer * * * * (c) 1991 MAXON Computer * ****************************** macro Cconws str movem.l d1-d2/a0-a2,-(sp) ;Ausgabe des pea str ;Strings str move.w #9,-(sp) ;unter Beachtung trap #1 ;der ESC-Sequenzen addq.l #6,sp movem.l (sp)+,d1-d2/a0-a2 endm macro Cconrs buf movem.l d1-d2/a0-a2,-(sp) ;Lesendes pea buf ;Strings str move.w #10,-(sp) ;unter Beachtung trap #1 ;der ESC-Sequenzen addq.l #6,sp movem.l (sp)+,d1-d2/a0-a2 endm macro Cconin movem.l d1-d2/a0-a2,-(sp) ;Warten auf move.w #1,-(sp) ;einen trap #1 ;Tastendruck addq.l #2,sp ;d0=Zeichen movem.l (sp)+,d1-d2/a0-a2 endm macro rd_int buf,frage,len,pos ;Read Integer pea buf(pc) ;Puffer für Eingabe pea frage(pc) ;Zeiger auf Frage move.w len,-(sp) ;Max. Eingabelänge move.w pos,-(sp) ;(X,Y)-Position bsr lies_int adda.l #12,sp endm macro rd_card buf,frage,len,pos ;Read Cardinal pea buf(pc) ;s.o. pea frage(pc) move.w len,-(sp) move.w pos,-(sp) bsr lies_card adda.l #12,sp endm * ---------------------------------------------- init equ $0 maus_off equ $a maus_on equ $9 a_line equ $3 * ---------------------------------------------- * Initialisierung * ---------------------------------------------- text movem.l d0-d7/a0-a6,-(sp) ;CPU. Reg und fmovem.l fpcr/fpsr/fpiar,-(sp) ;alle FPU Reg fmovem.x fp0-fp7,-(sp) ;sichern aline init ;line_a init lea a_zeiger,a1 ;Line_a Zeiger move.l a0,(a1) ;sichern move.w -$c(a0),d0 ;x_max lsr.w #1,d0 ;d0=x_halbe move.w -$4(a0),d1 ;y_max lsr.w #1,d1 ;d1=y_halbe lea x_hlb(pc),a0 ;x und y_halbe move.w d0,(a0) ;sichern move.w d1,2(a0) Cconws clr_scr(pc) * ---------------------------------------------- * Eingabeteil * ---------------------------------------------- rd_card Antwort,Frage1,#4,#$0202 ;Rad.1 lesen cmp.w d1,d0 ;Radius l<=y_halbe ? bge e1 move.w d0,4(a0) ;ja -> nehme Eingabe bra w1 e1: move.w 2(a0),4(a0) ;nein->nehme y_halbe/2 lsr.w 4(a0) w1: move.w 2(a0),d2 ;y_halbe sub.w 4(a0),d2 ;y_halbe-Radius 1 rd_card Antwort,Frage2,#4,#$0402 ;Rad.2 lesen cmp.w d2,d0 ;Rad 2<=y_halbe-Rad.1 ? bge e2 move.w d0,6(a0) ;ja -> nehme Eingabe bra w2 e2: move.w d2,6(a0) ;nein->nehme y_halbe-Rad 1 w2: rd_int Antwort,Frage3,#5,#$0602 ;lese Freq.l move.w d0,8(a0) rd_int Antwort,Frage4,#5,#$0802 ;lese Freq.2 move.w d0,10(a0) rd_card Antwort,Frage5,#3,#$0a02 ;lese Konst1 move.w d0,12(a0) rd_card Antwort,Frage6,#3,#$0c02 ;lese Konst2 move.w d0,14(a0) rd_card Antwort,Frage7,#5,#$0e02 ;Genauigkeit move.w d0,16(a0) Cconws cur_off(pc) ;Cursor aus Cconws clr_scr(pc) * ---------------------------------------------- * Hauptprogramm (hier wird gerechnet) * ---------------------------------------------- lea x_pos(pc),a1 ;1.Position berechnen move.w (x_hlb,pc),d0 ;x Position add w (rad1,pc),d0 ;rad.1 addieren add.w (rad2,pc),d0 ;rad.2 addieren move.w d0,(a1) ;sichern move.w y_hlb(pc),4(a1) ;y position fmove.x #0,fp0 ;Bei 0 anfangen fmove.x #1,fp1 fdiv.w step(pc),fp1 ;fp1=1/step move.w x_hlb(pc),d0 ;d0=x_hlb move.w y_hlb(pc),d1 ;d1=y_hlb move.w rad1(pc),d2 ;d2=rad1 move.w rad2(pc),d3 ;d3=rad2 fmove.w freq1(pc),fp6 ;fp6=freq1 fmove.w freq2(pc),fp7 ;fp7=freq2 rloop: fmove.x fp0,fp2 ;x=w fmul.x fp6,fp2 ;x=x*f1 fcos.x fp2 ;x=cos(x) fmul.w d2,fp2 ;x=x*rad1 fadd.w d0,fp2 ;x=x+x_hlb fmove.x fp0,fp3 ;y=w fmul.x fp6,fp3 ;y=y*freq1 fmul.w ry1(pc),fp3 ;y=y*ry1 fsin.x fp3 ;y=sin(y) fmul.w d2,fp3 ;y=y*rad1 fadd.w d1,fp3 ;y=y+y_hlb fmove.x fp0,fp4 ;x1=w fmul.x fp7,fp4 ;x1=x1*freq2 fcos.x fp4 ;x1=cos(x1) fmul.w d3,fp4 ;x1=x1*rad2 fmove.x fp0,fp5 ;y1=w fmul.x fp7,fp5 ;y1=y1*freq2 fmul.w ry2(pc),fp5 ;y1=y1*ry2 fsin.x fp5 ;y1=sin(y1) fmul.w d3,fp5 ;y1=y1*rad2 fadd.x fp4,fp2 ;x=x+x1 fadd.x fp5,fp3 ;y=y+y1 bsr line ;linie zeichnen fadd.x fp1,fp0 ;w=w+step fcmp.p max(pc),fp0 ;Schluß ? fble rloop Cconin ;auf Taste warten fmovem.x (sp)+,fp0-fp7 ;alle Register zurück fmovem 1 (sp)+,fpcr/fpsr/fpiar movem.l (sp)+d0-d7/a0-a6 clr.w -(sp) trap #1 ;zurück zu GEM * ------------------------------------------------- * Unterprogramme * ------------------------------------------------- * Unterprogramm für Linien line: movem.l d0-d2/a2-a3,-(sp) lea a_zeiger,a3 movea.l (a3),a3 lea x_pos,a2 move.w #1,$18(a3) ;farbe =1 move.w #-1,$20(a3) move.w #$ffff,$22(a3) ;linienstil clr.w $1a(a3) ;die restlichen clr.w $1c(a3) ;Bitplanes sind clr.w $1e(a3) ;gleich null fmove.w fp2,$26(a3) ;x neu fmove.w fp3,$28(a3) ;y neu move.w (a2),$2a(a3) ;x alt move.w 4(a2),$2c(a3) ;y alt move.w #0,$24(a3) ;replace fmove.w fp2,(a2) ;neue x_pos sichern fmove.w fp3,4(a2) ;neue y_pos sichern aline a_line ;linie zeichnen movem.l (sp)+,d0-d2/a2-a3 rts * ------------—---------------—-----— * Unterprogramm fur cardinal Eingabe * ------------—---------------—-----— lies_card: movem.l d1-d3/a0-a2,-(sp) movea.l 36(sp),a0 ;Bufferadr in A0 move.w #$1b59,(a0) ;ESC Y in Buffer move.w 28(sp),2(a0) ;Koord. in Buffer addi.w #$2020,2(a0) ;jeweils 32 addieren move.b #0,4(a0) ;Stringende Cconws (a0) ;String ausgeben Cconws clr_line(pc) ;Zeile löschen movea.l 32(sp),a1 ;Textadr in A1 Cconws (a1) ;Text ausgeben move.b 31(sp),(a0) ;Länge in Buffer Cconrs (a0) ;String einlesen move.b 1(a0),d1 ;Anzahl Zeichen in D1 beq lcl_exit ;Anzahl=0 -> exit ext.w d1 subq.w #1,d1 clr.w d2 clr.w d0 lc_loop: mulu #10,d0 ;Zahl mal 10 move.b 2(a0,d2.w),d3 ;Zeichen in D3 subi.b #$30,d3 ;ASCII - 48 bmi lcl_exit ;keine Ziffer -> exit cmpi.b #9,d3 ;Wert>9 ? bgt lcl_exit ;keine Ziffer -> exit ext.w d3 add.w d3,d0 ;d0=d0+Ziffer addq.b #1,d2 ;nächste Ziffer dbf d1,lc_loop ;bis zur letzten Z. bra lc_ende lcl_exit: moveq.l #0,d0 ;Fehler aufgetreten lc_ende: movem.l (sp)+,d1-d3/a0-a2 rts * Unterprogramm fur integer Eingabe lies_int: movem.l d1-d3/a0-a2,-(sp) movea.l 36(sp),a0 ;Bufferadr. in A0 move.w #$1b59,(a0) ;ESC Y in Buffer move.w 28(sp),2(a0) ;Koord. in Buffer addi.w #$2020,2(a0) ;jeweils 32 addieren move.b #0,4(a0) ;Stringende Cconws (a0) ;String ausgeben Cconws clr_line(pc) ;Zeile löschen movea.l 32(sp),a1 ;Textadr in A1 Cconws (a1) ;Text ausgeben move.b 31(sp),(a0) ;Länge in Buffer Cconrs (a0) ;String eingeben move.b 1(a0),d1 ;Zeichen gelesen ? beq lil_exit ;nein -> exit ext.w d1 move.b 2(a0),d0 ;erstes Zeichen in D0 clr.l d3 ;Zahl positiv cmp.b #'+',d0 ;erstes Zeichen ? beq li_plus cmp.b #'-',d0 ;'-' erstes Zeichen ? beq li_minus bra li_ziffer ;1.Zeichen ist Ziffer li_minus: or.l #$10000,d3 ;Zahl negativ li_plus: moveq #1,d2 ;1.Zeichen fällt weg subq.b #2,d1 ;ein Zeichen weniger bra li_pl_mi li_ziffer: subq.w #1,d1 clr.w d2 li_pl_mi: clr.w d0 li_loop: mulu #10,d0 ;Zahl mal 10 move.b 2(a0,d2.w),d3 ;Zeichen in D3 subi.b #$30,d3 ;ASCII - 48 bmi lil_exit ;keine Ziffer -> exit cmpi.b #9,d3 ;Wert>9 ? bgt lil_exit ;keine Ziffer -> exit ext.w d3 add.w d3,d0 ;d0=d0+Ziffer addq.b #1,d2 ;nächste Ziffer dbf d1,li_loop ;bis zur letzten Z. swap d3 btst #0,d3 ;Zahl negativ ? beq li_ende ;nein -> ende neg.w d0 ;sonst negieren bra li_ende ;-> ende lil_exit: moveq.l #0,d0 ;Fehler aufgetreten li_ende: movem.l (sp)+,d1-d3/a0-a2 rts * ------------------------------------------------- * Daten * ------------------------------------------------- x_hlb: dc.w 0 y_hlb: dc.w 0 * Hier werden die Eingaben eingetragen rad1: dc.w 0 ;Radius 1 rad2: dc.w 0 ;Radius 2 freq1: dc.w 0 ;Frequenz 1 freq2: dc.w 0 ;Frequenz 2 ry1: dc.w 0 ;Konstante 1 ry2: dc.w 0 ;Konstante 2 step: dc.w 0 ;Genauigkeit * max: dc.p 6.283185 a_zeiger: dc.l 0 x_pos: dc.w 0 y_pos: dc.w 0 * Frage1: asciiz 'Radius 1 :' Frage2: asciiz 'Radius 2 :' Frage3: asciiz 'Frequenz 1 :' Frage4: asciiz 'Frequenz 2 :' Frage5: asciiz 'Konstante 1 (1-3) :' Frage6: asciiz 'Konstante 2 (1-3) :' Frage7: asciiz 'Genauigkeit (>200):' Antwort: dc.b 0,0,0,0,0,0,0,0, 0, 0 clr_scr: dc.b 27,'E',0 cur_off: dc.b 27,'f',0 cur_on: dc.b 27,'e',0 clr_line: dc.b 27,'K',0


Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]