Programmierung des MC68881/82

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)

Die Suche nach dem Optimum

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)

Die Zahlenformate des MC 68882

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

Die Assembler-Befehle der FPU

   
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 | Unexakte dezimale Eingabe
4 DZ | Division durch Null
5 UNFL | Underflow
6 OVFL | Overflow
7 IOP | Unzulässiger Prozeß
Bit 8-15 | Exception
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-23 | 7 Bit des Quotient + 1 Bit Vorzeichen
Bit 24-31 | Condition Code
24 NAN | das Ergebnis ist keine gültige Zahl
25 I | das Ergebnis ist Unendlich (+/-)
26 Z | Zero-Bit, das Ergebnis ist 0
27 N | Negativ-Bit, das Ergebnis ist negativ
28-31 | nicht benutzt

Floating Point Control Register [FPCR]

     
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

Floating Point Instruction Address [FPIAR]

Bit 0-31 Adresse der letzten FPU-Instruction

Tabelle 2. Alles unter Kontrolle: Die Statusregister der FPU
Richard Kurz


Links

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