DSP 56001-Kurs - Das unentdeckte Land

Eines der herausragendsten Merkmale des Falcon 030 ist sein DSP. Unser Kurs zeigt, wie Sie ihn in eigenen Programmen verwenden.

Nun gibt es ihn endlich zu kaufen: Ataris Falcon 030. Entwickler haben ihn schon seit Monaten, trotzdem gibt es nur wenige Programme, die den DSP benutzen. Das liegt zum einen daran, daß viele Programmierer in »C« entwickeln und sich nur ungern mit Assemblerprogrammierung befassen wollen. Zum anderen sind Ataris Informationen spärlich und man ist so gezwungen, sich vieles selbst zusammenzureimen. Ein Grund weshalb es viele Entwickler als »Know-How-Vorsprung« ansehen, über die DSP-Programmierung Bescheid zu wissen.

Wir sind jedoch der Meinung, daß der Erfolg eines neuen Computers maßgeblich von der verfügbaren Software abhängt.

Natürlich können wir hier keinen kompletten DSP-Assemblerkurs vorstellen, das würde den Rahmen jeder Zeitschrift sprengen: Motorolas DSP-User-Manual umfaßt ca. 500 Seiten! Wir sprechen Programmierer an, die schon einmal 680x0-Assembler »gesehen« haben. Im ersten Teil werden wir auf Besonderheiten und Unterschiede zu 680x0-Assembler eingehen. Im zweiten Teil zeigen wir, wie der DSP in den Falcon integriert ist und beschreiben die wichtigsten Betriebssystemroutinen zur DSP-Programmierung. In der dritten Folge werden wir das neu erworbene Wissen in einen DSP-Soundeffekt einbringen.

Zum Schreiben von DSP Assemblerprogrammen braucht man, natürlich, einen entsprechenden Assembler. Am besten verwendet man das Paket von Motorola (Assembler, Linker, LOD-Konverter) aus der Motorola Mailbox.

Die DSP-Register

Für den Programmierer ist es interessant, welche Register zur Verfügung stehen. Hier hat der DSP einiges zu bieten:

2 Akkumulatoren:        A,B 56 Bit
2 Input Register:       X,Y 48 Bit
8 Adressregister:       R0-R7   16 Bit
8 Offset Register:      N0-N7   16 Bit
8 Modus Register:       M0-M7   16 Bit

Auf den ersten Blick sind die Zahlen 56 und 48 ungewöhnlich für die Breite eines Registers. Die Daten im DSP56001 sind 24 Bit orientiert, d.h. mit einem Speicherzugriff lädt man 24 (bzw. 48 Bit) auf einmal. Dazu kommt, daß man die Register A,B,X,Y nicht nur als ganzes, sondern auch aufgesplittet benutzen kann:

A: A2   8 Bit   -   A1 24 Bit   -   A0 24 Bit
B: B2   8 Bit   -   B1 24 Bit   -   B0 24 Bit
X: X1   24 Bit  -   X0 24 Bit
Y: Y1   24 Bit  -   Y0 24 Bit

Diese Register sind besonders wichtig, denn alle Berechnungen laufen über sie. Die Akkumulatoren sind nur als Zielregister zugelassen. Die oberen 8 Bit der Akkus (A2/B2) dienen als Überlaufbytes und erweitern so den Zahlenbereich. Ungewöhnlich ist auch die interne Darstellung der Daten, denn der DSP verwendet ein 24 Bit Festkommaformat im Zweierkomplement. Die beiden Zahlenformate im Vergleich:

16 Bit Zweierkomplement (wie im 680x0):
Zahlenbereich: -32768 bis 32767
Wert  -32768  16384  ... 64 32 16 8 4 2 1
Bit   15      14     ...  6  5  4 3 2 1 0
24 Bit Festkomma (wie im DSP 56001):
Zahlenbereich: -1 bis 0.999999
Wert  -1  0.5  0.25  0.125  0.0625 ...
Bit   23  22   21    20     19     ...

Dieses Format gilt natürlich nur für die ALU Register A,B,X,Y. Die restlichen (16 Bit breiten) Register verhalten sich wie gewohnt, mit ihnen kann man aber keine Rechenoperationen durchführen. Was bedeutet das? Man muß genau überlegen, was man bezweckt und wie man die Zahl »anschauen« will. Dazu ein paar Beispiele:

Festkommaarithmetik:

Sie benutzen einfach Kommazahlen im Assembler, auch wenn es ungewohnt ist. Den erlaubten Zahlenbereich dürfen Sie natürlich nicht überschreiten.

MOVE #0.875,X0  Registerinhalt X0=$700000

Integerarithmetik:

Um sicherzustellen, daß alle Rechenoperationen nach Ihren Vorstellungen ausgeführt werden, müssen die Daten »linksbündig« in das Register geschrieben werden.

<

pre> MOVE #<$3F,X0 Registerinhalt X0=$3F0000 !!! aber

MOVE #>$3F,X0  Registerinhalt X0=$00003F

Dies bedeutet auch, daß ein 16 Bit Wert, der von der CPU zum DSP geschickt wird, linksbündig in ein 24 Bit DSP Register geschrieben werden soll. Ist das geschehen, brauchen Sie sich keine Gedanken über die weiteren Berechnungen zu machen. In einem Beispiel wollen wir $3F (8 Bit) durch zwei dividieren:

MOVE #<$3F,X0       $3F0000->X0 X0=$3F0000
MOVE #0.5,X1        $400000->X1 X1=$400000
MPY X0,X1,A     X0*X1->A    A1=$1F8000

Wenn Sie wieder nur die »linken« 8 Bit nehmen ($1F), haben Sie genau das Ergebnis erwartet. Das Ergebnis erscheint in A1. Allgemein ist es so, daß ein 24 Bit Wert, der in einen Akku geschrieben wird, im Register A1 (bzw. B1) landet. Dabei wird der Wert, falls nötig, vorzeichenrichtig erweitert:

MOVE #<$3F,X0       $3F0000->X0 X0=$3F0000
MOVE X0,A       X0->A       A=$00 3F0000 000000

und

MOVE #<$80,X0       $800000->X0 X0=$800000
MOVE X0,A       X0->A       A=$FF 800000 000000

Das »DSP User Manual« widmet dieser Thematik fast 100 Seiten. In einem letzten Beispiel zeigen wir, wie schnell man einen kleinen Denkfehler mit großer Auswirkung macht:

MOVE #<$3F,X0   $3F0000->X0 X0=$3F0000
MOVE #<2,X1 $020000->X1 X1=$020000
MPY X0,X1,A X0*X1->A    A=$00 00FC00 000000

Man multipliziert nicht 63 mit 2, sondern 63/128 mit 1/64 und schon stimmt das Ergebnis! Die Sichtweise 16128 (=$3F00) durch 64 zu dividieren ist korrekt und das Ergebnis 252 (=$00FC).

Die restlichen Register: Da es sich um Adressregister handelt, kommen wir zuerst zur Speicherorganisation. Der DSP unterscheidet zwischen X,Y, und P-Speicher. X und Y-RAM ist für Daten gedacht, wobei diese Zweiteilung durchaus hilfreich ist, wenn man z.B. Real- und Imaginärteil einer komplexen Zahl oder einfach nur den linken und rechten Kanal eines Samples ablegen will. Das P-RAM ist der Programmspeicher. Hier liegen neben dem Programm auch diverse Interruptvektoren. Der DSP 56001 hat je 256 Worte (=24 Bit!) X- und Y-RAM und 512 Worte P-RAM. Zudem läßt sich ein je 256 Worte großer ROM Bereich einblenden, der dann eine Sinustabelle bzw. eine A-/µ-Law-Tabelle zur Datenkompression enthält. Im Falcon wurde dem DSP externer Speicher spendiert, mehr dazu im nächsten Teil. Das Ansprechen des Speicher sieht in Assembler so aus:

MOVE #$10,X0    $100000->X0 X0=$100000
MOVE X0,Y:$100  X0->(Y-Mem $100)
MOVE Y:$100,A   (Y-Mem $100)->A A=$00 100000 000000

Wie speichert man ein mehr als 24 Bit breites Register im Speicher ab? Dazu kann man X und Y Speicher als L-Speicher zusammenfassen:

MOVE A,L:$100   A1->(X-Mem $100)
                A0->(Y-Mem $100)

Die oberen 8 Bit des Akkus (A2) lassen sich also nicht so ohne weiteres im Speicher ablegen!

Die Adressregister R0-R7: Diese Register sind 16 Bit breit und können ähnlich wie die Adressregister im MC680x0 verwendet werden.

MOVE #$100,R0   $100->R0    R0=$0100
MOVE #$10,X0    $100000->X0 X0=$100000
MOVE X0,Y:(R0)  X0->(Y-Mem $100)

Ebenfalls ähnlich wie im gewohnten Assembler lassen sich die Adressregister direkt bei der Befehlsausführung ändern. Es gibt noch folgende Varianten:

(R0)+   R0+1 -> R0 nach Ausführung
(R0)-   R0-1 -> R0 nach Ausführung
(R0)    R0-1 -> R0 vor Ausführung

Nun kommen die Indexregister ins Spiel, die es erlauben, ein Adressregister um mehr als eins zu erhöhen/erniedrigen:

(R0)+N0   R0+N0 -> R0 nach Ausführung
(R0)-N0   R0-N0 -> R0 nach Ausführung

Die letzte Variante erlaubt, einen Offset auf ein Adressregister zu benutzen, ohne dieses selbst zu ändern:

(R0+N0)   Speicherzugriff auf Adresse R0+N0
R0 bleibt unverändert!

Wichtig ist, immer nur zusammengehörige Adress- und Indexregister miteinander zu kombinieren. Von den Registern bleiben nur noch die merkwürdigen Modusregister übrig. Nach dem Reset des DSP sind alle 8 Modusregister auf $FFFF initalisiert. Ändern Sie daran nichts, merken Sie auch nie deren Existenz. Setzen Sie aber eines einen Wert zwischen 1 und 32767, ändert sich die Art und Weise, wie mit Adressregistern gerechnet wird. Das zu dem Modusregister gehörige Adressregister befindet sich in der sog. Modulobetriebsart. Was das bedeutet, verdeutlichen wir an einem Beispiel:

MOVE #$100,R0
MOVE #$FF,M0
loop:
MOVE X:(R0)+,A
JMP loop

Dieses kleine Programm würde, das M-Register einmal außer acht gelassen, in einer Endlosschleife die Inhalte der Speicherzellen $100,$101 ... in den Akku A laden. Nach Überlauf der 16 Bit, also bei $FFFF, würde R0 wieder bei Null beginnen. Der Wert $FF in M0 bedeutet, daß R0 nach den Wert $1FF wieder auf den Startwert $100 zurückspringt. Man hat somit einen Ringspeicher der Länge 256 programmiert! Man sagt, die Adressarithmetik rechnet »Modulo 256«. Nun ist die untere Grenze des Ringspeichers je nach Modulowert nicht frei wählbar. Nehmen wir an, man muß sich an folgende Formel halten: Sie wollen z. B. einen 21 Worte großen Ringspeicher (M=21):

Die untere Grenze muß ein Vielfaches von 2^k sein, wobei keine ganze Zahl und k>=(ln M)/(In 2) sein muß.

In unserem Beispiel ergibt sich k>=4.39, die nächst größere ganze Zahl ist also k=5! Die gültigen unteren Grenzen sind demnach Vielfache von 2^5: 0,32,64,96.... Nach Wahl der richtigen Grenze brauchen Sie nur noch das M-Register auf den Wert M-1 zu setzen. Ein gültiger (funktionierender) Programmteil ist also:

MOVE #$40,R0    Untergrenze 64
MOVE #20,M0 Modulo 21
=> Ringspeicher von 64 bis 84 einschließlich

Man schaltet auf normale, lineare Arithmetik mittels dem Wert $FFFF (=Modulo 32768, also der gesamte Adressraum!) zurück. Der Wert Null in einem M-Register bewirkt die »Reverse-Carry« - Arithmetik. Darauf gehen wir in diesem Kurs aber nicht ein, denn diese Betriebsart hat erst bei der Fast Fourier Transformation (FFT) größeren Nutzen.

Wichtige Befehle des DSP

In diesem Abschnitt zeigen wir auf einige, für Assembler ungewöhnliche, Befehle des DSP. Die meisten dieser

Befehle benötigen wir später in unserem Beispielprogramm. Übrigens ist der DSP kein RISC Chip, denn er bietet immerhin 60 Befehle in vielen verschiedenen Addressierungsarten.

Schleifenbefehle

DO - Start Hardware Loop

Im Gegensatz zum 680x0 kann der DSP hardwaremäßig eine Schleife realisieren.

DO ,End        ; Schleifenzähler
... ; Befehle in der Schleife
End; erster Befehl außerhalb der Schleife

Praktisch alle Register und Adressierungsarten sind für erlaubt. DO-Schleifen können geschachtelt werden.

ENDDO - End current DO Loop

Verläßt die innerste DO Schleife (wie ein break in »C«).

REP - Repeat next Instruction

Wiederholt den nächsten Befehl :

REP 
ASL A

Schiebt den Akku nach links. Wieder sind alle Register und Adressierungsarten erlaubt. Allerdings können nicht alle Befehle wiederholt werden, dazu gehören alle Schleifen- und Sprungbefehle.

Bitorientierte Befehle

BCLR - Bit test and clear

Testet Bit #n des angegebenen Ziels und löscht dieses.

BCLR #3,A

BSET - Bit test and set

Testet Bit #n des angegebenen Ziels und setzt dieses.

JCLR - Jump if Bit clear

Springt zur angegebenen Adresse, wenn das Bit der angegebenen Quelle nicht gesetzt ist.

JCLR #2,X:$100,Ziel

JSET - Jump if Bit set

Springt zur angegebenen Adresse, wenn das Bit der angegebenen Quelle gesetzt ist.

Arithmetische Befehle

ADD - Add

Addiert Quell- zum Zieloperand.

ADD ,     z=z+q

Als Quelle sind alle ALU-Register erlaubt, als Ziel nur die Akkumulatoren.

SUB - Sub

Subtrahiert Quell- vom Zieloperand.

SUB ,     z=z-q

Als Quelle sind alle ALU-Register erlaubt, als Ziel nur die Akkumulatoren.

MPY - Signed Multiply

Multipliziert die Quelloperanden und schreibt das Ergebnis in den Zieloperanden.

MPY ,, z=q1*q2
bzw.
MPY -,,  z=-q1*q2

Als Quelle sind X0,X1,Y0,Y1-Register erlaubt, als Ziel nur die Akkumulatoren. Die Kombination X1X1 und Y1Y1 ist nicht erlaubt. Der Befehl benötigt nur 2 Taktzyklen, da ein Hardwaremultiplizierer benutzt wird.

MAC - Signed Multiply accumulate

Multipliziert die Quelloperanden und addiert/subtrahiert das Ergebnis vom Zieloperanden.

MAC ,, z=z+q1*q2
bzw.
MAC -,,  z=z-q1*q2

ABS -Absolute Value

Berechnet den Absolutwert des angegebenen Akkus.

ABS A

Weitere Besonderheiten

Parallel-Move

Neben einer ALU-Operation kann der DSP gleichzeitig 2 weitere Speicher/ Registerzugriffe ausführen:

ADD X0,A X:(R4)-,X1 A,Y0

Das ist gleichbedeutend mit 3 einzelnen Befehlen (braucht aber wesentlich weniger Zeit). Der DSP besteht aus den 3 Einheiten ALU (Data Arithmetic Logie Unit), AGU (Adress Generation Unit) und PC (Program Controller). Diese 3 Einheiten können hier gleichzeitig ausgenutzt werden. Nutzt man diese Möglichkeit nicht, liegt ein Teil brach. Man kann manche Programme mit dieser Fähigkeit gut optimieren.

In unseren Beispielprogrammen benutzen wir diese Möglichkeiten nicht, da es unübersichtlich und der DSP auch so schnell genug dafür ist.

Pipelining

Da das Dekodieren und Ausführen verschiedener Befehle unterschiedlich lange dauern kann, aber jeweils teilweise parallel abgearbeitet wird, kann es in speziellen Fällen dazu kommen, daß ein Befehl auf Daten zugreifen will, die der vorhergehende Befehl noch gar nicht endgültig bearbeitet hat.

MOVE #10,R0
MOVE X:(R0),A

In diesem Fall ist der neue Wert von R0 im zweiten Befehl noch nicht verfügbar. Damit es funktioniert, muß ein NOP eingefügt werden. Assembler erkennt solche Fehler.

Direkte Adressierung

Praktisch alle ALU-Befehle sind register-orientiert, d.h. eine direkte Adressierung ist dabei nicht möglich.

ADD #10,A   geht nicht!
aber
MOVE #10,X0
ADD X0,A    richtig!

Wollen Sie größere Projekte mit dem DSP verwirklichen, müssen Sie auf das ausführliche DSP Reference Manual zurückgreifen, um alle Befehle und Feinheiten des DSP zu erfahren.

Nach dieser »Tour de Force« durch den DSP Assembler wenden wir uns im nächsten Teil dem Gebiet der TOS-Routinen zu. Der dritte Teil zeigt Ihnen an einem Beispiel, wie Sie einen Soundeffekt mit dem DSP Programmieren. (uw)

Literaturverweis:

DSP 56000/56001, Digital Signal Processor User's Manual, Motorola Inc. 1990


Andreas Binner und H


Links

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