Ein jeder TT hat sie, ein jeder Falcon wartet auf sie, aber kaum einer kennt sie, die FPU MC68882. Dabei kann diese »Floating Point Unit« alle Fließkomma-Rechnungen geradezu gigantisch beschleunigen. Speziell, wenn man sich diesem Teil auf Assembler-Ebene nähert, sind ungeahnte Leistungen möglich.
In vielen Bereichen des täglichen Computerlebens finden Fließkommazahlen zunehmend Verwendung. Leider enthalten MC68000 bis MC68030 in ihrem Befehlssatz nur Integerarithmetik, die Fließkommaarithmetik simuliert in der Regel ein Unterprogramm. Natürlich kostet das Zeit und es wäre wünschenswert, könnte der Prozessor Fließkommazahlen direkt verarbeiten.
Im Jahre des Herrn 1985 (zu Zeiten des MC68020) hatte Motorola ein Einsehen und brachte den MC68881 auf den Markt. Dieser Coprozessor stellt 64 Befehle speziell zur Bearbeitung von Fließkommazahlen zur Verfügung. Er enthält acht Fließkommaregister mit 80 Bit Breite und beherrscht verschiedene Zahlenformate nach dem IEEE Floatingpoint Standard (siehe Tabelle 1). Dazu kommen noch die vom MC68000 bekannten Byte-, Word-, und Long-Integer sowie das »Packed Dezimal Real«-Format mit 96 Bit Länge. Allerdings treten diese unterschiedlichen Zahlenformate nur bei I/O-Operationen auf. Intern konvertiert die FPU diese nach Double Extended (80 Bit), da der MC68881/2 grundsätzlich mit diesem Format rechnet.
Natürlich will diese »Rechnerei« auch überwacht bzw. gesteuert sein. Zu diesem Zweck existieren ähnlich wie auf dem MC680xx noch Status-Register, hierbei handelt es sich um drei 32 Bit lange Vertreter. Den Aufbau entnehmen Sie Tabelle 2.
Für den Anwender sieht die Verbindung einer FPU mit einem MC68020 oder MC68030 wie ein einheitlicher Prozessor aus, der aber jetzt auf wundersame Weise auch über schnelle Fließkomma-Befehle verfügt. Natürlich sind die Zeiten auch am MC68881 nicht spurlos vorübergegangen, es wurde ein Nachfolger nötig. Das aktuelle Kind in Sachen FPU-Coprozessor von Motorola nennt sich MC68882 und ist noch fixer.
Bezüglich Pinbelegung und Befehlssatz ist der MC68882 voll kompatibel zum MC68881. Von Sonderfällen einmal abgesehen, kann der MC68881 also problemlos gegen den MC68882 ausgetauscht werden, daher besprechen wir auch nur den MC68882. Abgesehen von der internen Beschleunigung einiger Befehle ist die wichtigste Neuheit im MC68882 die »Conversion Unit« (CU). Sie erlaubt in beschränktem Umfang die parallele Bearbeitung von Befehlen. Während die APU (Arithmetik Processing Unit) sich noch mit dem vorherigen Befehl »herumschlägt«, liest die CU bereits neue Befehle und Operanden aus dem Speicher. Voraussetzung für das optimale Funktionieren der CU ist, daß der nächste Befehl nicht auf das Ergebnis des Vorgängers warten muß.
Zur Kennzeichnung der neuen Zahlenformate bei FPU-Assembler-Befehlen, finden - neben den schon bekannten Anhängseln ».B .W .L« - noch folgende Verwendung:
.S Single (32 Bit)
.D Double (64 Bit)
.X Double Extended(80 Bit)
.P Packed Dezimal (92 Bit)
Um eventuell aufkommende Trockenheit zu vermeiden, finden Sie auf der TOS-Diskette das Beispielprogramm »APFEL.PRG«. Es verdeutlicht, was durch optimale Assembler-Programmierung aus dem MC68882 herauszuholen ist. Was könnte besser geeignet sein, die schnelle Fließkommaberechnung zu demonstrieren, als das gute alte Apfelmännchen. Um dieses Programm starten zu können, muß Ihr Rechner über eine 68020/68030 CPU und 68881/68882 FPU verfügen, sonst gibt es außer einer Fehlermeldung nichts zu sehen. Abgesehen von dieser Voraussetzung gibt es aber keine Einschränkungen.
In der Datei »APFEL_FP.S« verbirgt sich der Assembler-Quelltext-Code zur Berechnung einer Zeile der Mandelbrotmenge nach der allseits bekannten und beliebten Formel:
z = z2 + c
In einer höheren Programmiersprache formuliert (etwa C), würde die Berechnung für jeden einzelnen Punkt eines Bildes ungefähr so aussehen:
for (z = 0; z<t; z++)
{
r_quad = r_zahl * r_zahl;
i_quad = i_zahl * i_zahl;
if ((r_quad + i_quad) > 4)
return (FALSE)
i_zahl = z * r_zahl * i_zahl + i_cons;
r_zahl = r_quad - i_quad + r_cons;
}
Ob der vielen Multiplikationen in diesem kurzen Code, greift das Grausen nach dem Programmiererherz. Um schnelle Apfelmännchen zu berechnen, waren daher geradezu grausame Klimmzüge von Nöten. Mit einem MC68882 wäre das alles nicht nötig gewesen. Doch halt, wer nun glaubt, diese Formel einfach mit eingeschalteter 68881-Option durch seinen C-Compiler jagen zu müssen, der erhält zwar ein recht schnelles Ergebnis, aber noch lange nicht das Optimum! Noch kann kein Compiler die Assembler-Optimierung von Hand ersetzen. Besonders dann, wenn die Conversion Unit des MC68882 voll ausgenutzt werden soll. Unsere Routine darf sich bei 80-Bit-Genauigkeit als optimal bezeichnen:
mandel-line:
; C-Prototyp:
; int mandel_line (double r_cons, double i_cons, double r_delta, long t, long x, long g, int *erg)
; ACHTUNG! Die Parameter werden nach PURE-C-Art in den Registern übergeben!
;
; r_cons = fp0
; i_cons = fp1
; r_delta = fp2
; t = d0 (Rechentiefe)
; x =d1 (Anzahl der Punkte einer Zeile)
; g = d2 (Grenze für das Apfelmännchen)
; erg = a0 (Zeilenpuffer)
fmovem fp3-fp7, -(sp) ; FPU-Register sichern
fmove.x fp2, fp6 ; r_delta nach fp6
fmove.l d2, fp7 ; g nach fp7
move.w d0, d2 ; t nach d2
loopx:
fmove.x fp1, fp3 ; i_zahl = i_cons
fmove.x fp0, fp2 ; r_zahl = r_cons
move.w d2, d0 ; Schleifenzähler nach d0
loopz:
fmove.x fp3, fp4 ; i_quad = i_zahl*i_zahl
fmul.x fp3, fp4
fmul.x fp2, fp3 ; i_zahl *= r_zahl
fmul.x fp2, fp2 ; r_zahl *= r_quad
fadd.x fp3, fp3 ; i_zahl *= 2
fmove.x fp2, fp5 ; if ((r_quad + i_quad)g) break
fadd.x fp4, fp5
fcmp.x fp7, fp5
fbgt.w no_apple
fadd.x fp0, fp2 ; r_zahl += r_cons
fadd.x fp1, fp3 ; i_zahl += i_cons
fsub.x fp4, fp2 ; r_zahl -= i_quad
dbra.w d0, loopz ; noch tiefer?
no_apple:
fadd.x fp6, fp0 ; r_cons += r_delta
move.w d0, (a0)+ ; das Ergebnis in erg ablegen
dbra.w d1,loopx ; das nächste Pixel?
fmovem (sp)+, fp3-fp7 ; FPU-Register holen
rts ; und Tschüß
Auf einem Falcon 030 mit MC68882, bei 16 Farben, 640x480 Pixeln, einer Iterationstiefe von 50 und einer Grenze von 4, berechnet APFEL.PRG die Grundmenge in knapp unter 27 Sekunden - bei 80 Bit Genauigkeit, wie gesagt.
Ein TT sollte das natürlich noch ein wenig schneller können! Wer sich den Assembler-Quelltext näher ansieht, wird eine gewisse Verwunderung angesichts der etwas chaotisch anmutenden Befehlsanordnung nicht unterdrücken können. Aber zur Beruhigung darf ich nochmals an die Conversion Unit, die der MC68882 im Gegensatz zum MC68881 besitzt, erinnern. Das Geheimnis der MC68882-Programmierung ist tatsächlich die »richtige« Reihenfolge der Befehle. So verlangsamt zum Beispiel
fadd.x fp0, fp2 ; r_zahl += r_cons
fsub.x fp4, fp2 ; r_zahl -= i_quad
fadd.x fpl, fp3 ; i_zahl += i_cons
anstelle von
fadd.x fp0, fp2 ; r_zahl += r_cons
fadd.x fpl, fp3 ; i_zahl += i_cons
fsub.x fp4, fp2 ; r_zahl -= i_quad
die ganze Berechnung um eine ganze Sekunde. Warum? Weil die CU nicht optimal arbeiten kann. Im ersten Beispiel muß der Befehl »fsub.x fp4,fp2« auf das Ergebnis von »fadd.x fp0,fp2« warten. Der erste Befehl speichert sein Ergebnis in »fp2« und dieses Ergebnis wird wiederum vom nächsten Befehl gebraucht. Die CU kann also die Bearbeitung des zweiten Befehls nicht parallel in die Wege leiten. Im zweiten Beispiel dagegen kann sich die CU richtig ins Zeug legen. Solange die APU »fadd.x fp0,fp2« noch Arbeit hat, holt sich die CU bereits »fadd.x fpl, fp3« und während die APU bereits an dieser Zeile rechnet, holt die CU auch schon »fsub.x fp4,fp2« aus dem Speicher.
Übrigens: Wer unter Pure C Programme mit der 68881-Compiler-Option übersetzt, erlebt auf Systemen ohne FPU eine herbe Überraschung. Es ist nicht möglich, das Programm ordnungsgemäß mit einer Fehlermeldung zu beenden, da im Startup-Code (noch vor »main()«) ohne Rücksicht auf Verluste zu »_fpuinit« gesprungen wird. Zum Abstellen dieser Unart, sollte die betreffende Zeile aus PCSTART.S entfernt werden. Für den nachträglichen Sprung in die Funktion »_fpuinit« sind wir dann selbst zuständig. Aber nicht ohne vorherige Prüfung, ob denn eine FPU vorhanden ist.(ah)
Bezeichnung | Vorzeichen | Exponent | Mantisse | Bits gesamt |
---|---|---|---|---|
Single | 1 | 8 | 23 | 32 |
Double | 1 | 11 | 52 | 64 |
Double Extended | 1 | 15 | 64 | 80 |
Tabelle 1. Mit bis zu 80 Bit Genauigkeit berechnet die FPU Fließkommazahlen
Datentransport | |
FMOVE | MOVE von und nach FPx, CR, SR |
FMOVEM | übertrage mehrere Register (FPx, SR, CR und FPU-IP) |
FMOVECR | MOVE von ROM-Konstanten nach FPn |
Programmsteuerung | |
FBcc | bedingter Sprung |
FDBcc | prüfe Bedingung, dekrementiere und springe |
Scc | setze ein Byte in Abhängigkeit von einer Bedingung |
FTRAPcc | erzeugt Trap bei Bedingung |
FTST | testet einen Operand auf 0 |
FNOP | keine Operation |
Arithmetik | |
FADD | Addition |
FSUB | Subtraktion |
FCMP | Vergleich von zwei Operanden (Subtraktion ohne Speicherung) |
FMUL | Multiplikation |
FDIV | Division |
FMOD | Modulo-Rest |
FSGLDIV | Division mit einfacher Genauigkeit (32 Bit) |
FSGLMUL | Multiplikation mit einfacher Genauigkeit (32 Bit) |
FABS | Absolutwert des Operanden |
FINT | Integerwert des Operanden |
FINTRZ | Integerwert des Operanden, auf 0 gerundet |
FNEG | Negation |
FSCALE | Skaliere Exponent |
FREM | IEEE-Rest |
FGETEXP | Exponent des Operanden |
FGFTMAN | Mantisse des Operanden |
Trigonometrie | |
FSINCOS | Sinus und Cosinus gleichzeitig! |
FCOS | Cosinus |
FACOS | Arcus Cosinus |
FCOSH | Cosinus Hyperbolicus |
FSIN | Sinus |
FASIN | Arcus Sinus |
FSINH | Sinus Hyperbolicus |
FTAN | Tangens |
FATAN | Arcus Tangens |
FTANH | Tangens Hyperbolicus |
FATANH | Arcus Tangens Hyperbolicus |
Potenzen und Logarithmen | |
FLOGN | natürlicher Logarithmus |
FLOGNP1 | natürlicher Logarithmus von x+l |
FLOG2 | Logarithmus zur Basis 2 |
FLOG10 | dekadischer Logarithmus |
FETOX | Exponentialfunktion e^x |
FETOXM1 | Exponentialfunktion êx-1 |
FTENTOX | Zehnerpotenz |
FTWOTOX | Zweierpotenz |
FSORT | Quadratwurzel |
Systemsteuerung | |
FSAVE | Retten des Coprozessorzustandes |
FRESTORE | Wiederherstellung des Coprozessorzustandes |
Tabelle 3. 64 Befehle sorgen für beschleunigte Berechnungen
Floating Point Status Register [FPSR]
Bit 0-7 | Accured Exception |
0-2 | nicht benutzt |
3 | INEX |
4 | DZ |
5 | UNFL |
6 | OVFL |
7 | IOP |
Bit 8-15 | |
8 | INEX1 |
9 | INEX2 |
10 | DZ |
11 | UNFL |
12 | OVFL |
13 | OPERR |
14 | SNAN |
15 | BSUN |
Bit 16-23 | |
Bit 24-31 | |
24 | NAN |
25 | I |
26 | Z |
27 | N |
28-31 |
Bit 0-7 | Modus 0-3 nicht benutzt | |
4-5 | Rundungs-Art | |
6-7 | Präzision | |
Bit 8-15 | Freigabe-Byte | |
8 | INEX1 | Unexakte dezimale Eingabe |
9 | INEX2 | Unexakter Prozeß |
10 | DZ | Division durch 0 |
11 | UNFL | Underflow |
12 | OVFL | Overflow |
13 | OPERR | Operand ist fehlerhaft |
14 | SNAN | Signaling Not a Number |
15 | BSUN | Branch/Set on Unordered |
Bit 16-31 | nicht benutzt |
Bit 0-31 Adresse der letzten FPU-Instruction
Tabelle 2. Alles unter Kontrolle: Die Statusregister der FPU