Der Verwandlungskünstler: DSP-Programmierung auf dem Falcon, Teil 1

Teil 1: Register und Befehlssatz des DSP 56001

Da die Verfügbarkeit des Falcon030 inzwischen gesichert scheint (oder ist!), wird es höchste Zeit, die Leistungen des starken Zweitprozessors im Falcon, dem digitalen Signalprozessor, nicht mehr nur in der Theorie zu quantifizieren, sondern die realen Qualitäten anhand einer genauen Befehlsbeschreibung und eines Programmbeispiels einem jeden Interessierten zugänglich zu machen.

An Euphorie und allgemeinen Bekundungen, die den Falcon030 aufgrund des eingebauten DSPs (digitaler Signalprozessor) zum Superrechner erheben, mangelt es wahrlich nicht. Soviel ist schon über den DSP geschrieben worden, daß jeder aufmerksame Leser sich in etwa vorstellen kann, wozu er geeignet ist und welche Eigenschaften ihn von einem „normalen“ Prozessor abheben. Schlag Wörter wie Spracherkennung, Bildverarbeitung, Stereo-Equalizer in CD-Qualität oder Harddiskrecording fallen fast immer dann, wenn vom DSP die Rede ist. Inzwischen gelangen auch die ersten Anwendungen auf den Markt, die sich die speziellen Eigenschaften des DSP zunutze machen und somit das untermauern, was bisher in der Theorie erörtert wurde. Doch gerade wegen der (noch) wenigen Anwendungen (und der mageren Fachliteratur) und auf der anderen Seite den inzwischen ausreichend vorhandenen Falcons, wird es höchste Zeit, das komplette Innenleben des DSP umzustülpen, so daß jeder Programmierinteressierte in die Lage versetzt wird, eigene DSP-Anwendungen schreiben zu können.

Da es schon einige deutsche Fachliteratur zum Thema „DSP-Schnittstellen“ mit insbesondere der Beschreibung der neuen XBIOS-Funktionen gibt, wollen wir im ersten Teil dieser Artikelreihe mit den Registern und dem Befehlssatz des DSPs beginnen.

Der Chip

Mit dem DSP56001 steht dem Falcon ein zweiter, völlig autarker Prozessor mit eigenem RAM und eigenen Ein-/Ausgabe-Schnittstellen zur Seite, der natürlich auch parallel zum Hauptprozessor arbeitet. Über eine weitere Schnittstelle (ca. 1 MByte/ Sekunde) können beide Prozessoren miteinander kommunizieren, sprich ihre Aufgaben untereinander absprechen. Natürlich sind die bevorzugten Aufgaben des DSPs aufgrund seiner internen Architektur andere als die des Hauptprozessors. Welche Aufgaben das im einzelnen sind, verdeutlicht am besten ein näherer Blick in den Aufbau des DSP.

Ich glaub', mich streift 'n Bus

Auffälligster Unterschied zum Hausprozessor: Der DSP verfügt über drei Datenbusse (mit den dazugehörigen Adreßleitungen) und ist somit in der Lage, intern auf drei verschiedene Speicher in einem Befehlszyklus zuzugreifen. Zusammen mit dem schnellen, aber nicht unbedingt üppigen externen RAM ist der DSP in Sachen Speicheroperationen durch keine noch so ausgetüftelte Cache-Logik anderer Prozessoren zu schlagen. Der maximal drei mal 16 Bit breite Adreßraum stellt für die DSP-spezifischen Aufgaben kein Hindernis dar. Selbst die lediglich 32 Kilo-Worte (ein Wort sind beim DSP 24 Bit, also 3 Bytes) im Falcon030 sind schon großzügig bemessen. Der DSP in den NeXT-Rechnern mußte mit acht Kilo-Worte auskommen. Da die meisten Aufgaben des DSP in Echtzeit zu berechnen sind, die Ein- und Ausgabe werte über die schnellen Schnittstellen eingelesen und gleich wieder ausgegeben werden, benötigt man das RAM nur für den Programmcode und die globalen Variablen - dafür sind 32 Kilo-Worte allemal genug.

Die drei Datenschleusen verlangen allerdings auch nach drei getrennten Speichern. Einer der Datenbusse ist verantwortlich für den Transfer der nächsten Befehle vom Programmspeicher zur Recheneinheit. Die zwei übrigen Busse führen zu zwei Datenspeichern, deren Inhalt, Aufgabenverteilung und Nutzung dem Programmierer frei stehen. Bild 1 zeigt die derzeitige Speicheraufteilung im Falcon030. Auf die Speicherspiegelung in höhere Adreßbereiche sollte man sich nicht verlassen, denn spätestens mit der nächsten Speichererweiterung fehlt der Platz für derartige Erscheinungen (Fata Morganen).

Oftmals wird bei der Beschreibung des Speicheraufbaus verschwiegen, daß die drei Datenbusse nur prozessorintern vorhanden sind, während externe Speicher nach wie vor mit nur einem Bus fahren. Jetzt erklärt sich auch, wie der externe Programmspeicher die beiden Datenspeicher überdecken kann. Außerdem wird deutlich, daß man von der Buskolonne nur profitiert, wenn man sich auf internes RAM bezieht.

Es empfiehlt sich mit der Assembler-Direktive org den Beginn des Y-Speichers um die Größe des benutzten Programmspeichers zu erhöhen (z.B.: ORG Y:$600). Weiter darf der Instruktionenbereich nicht in die Interrupt-Vektoren geschrieben werden, also: ORG P:$40.

Speicheraufbau des DSP 56001 im Falcon

Bild 1: Nicht ganz trivial: Die Speicheraufteilung des DSP im Falcon

Abkürzungen der verwendeten Registergruppen

   

n | 6-Bit-Wert (Immediate)

x | 8-Bit-Wert (evtl. auch 24-Bit-Wert z.B. bei MOVE)

X | 12-Bit-Wert

A | 56-Bit-A- und -B-Akkumulator X | 48-Bit-X- und -Y-Registei X0 | 24-Bit-X0, -X1-, -Y0- und -Y1-Register ea | Registerbezogener Speichermhalt
(Rn)-Nn, (Rn)+Nn, (Rn)-,(Rn)+,(Rn);(Rn+Nn) -(Rn) pp | 6 Bit absolute kleine Adresse aa | 6 Bit absolute große Adresse (I/O-Adressen) ar | alle Register (evtl. sind bei einigen Befehlen verschiedene Register ausgeschlossen; ausprobieren!)
X0;X1;Y0;Y1;A0;A1;A2;B0;B1;B2;A;B;Rn;Nn;Mn;SR;
OMR;SP;SSH;SSL;LA;LC + | entweder addieren (standard) oder subtrahieren
(-) für MAC, MACR, MPY, MPYR

Abkürzungen der Bedingungen »cc«

     
CC (HS) carry clear (higher or same) C = 0
CS(L0) carry set (lower) C = 1
EC extension clear E = 0
EQ equal Z = 1
ES extension set E = 1
GE greater than or equal N^V = 0
GT greater than Z+(N^V) = 0
LC limit clear L = 0
LE less than or equal Z+(N^V) = 1
LS limit set L = 1
LT less than N^V = 1
MI minus N = 1
NE not equal Z = 0
NR normalized Z+(-U & -E) = 1
PL plus N = 0
NN not normalized Z+(-U & -E) = 0

mit

- für logisches Komplement
+ für logisches ODER
& für logisches UND
^ für logisches ausschließendes ODER

Die DSP 56001-Register

Bild 2: Der aufgeflochtene DSP-Registerdschungel bringt die Qualitäten des DSP ans Licht.

Der DSP zieht alle Register

Auch in Sachen Registeranzahl läßt sich der DSP nicht lumpen. Für das adreßerzeugende System stehen alleine drei mal acht Register (16 Bit) bereit. In Kombination mit den Adreßregistern (R0 bis R7), den Offset-Registern (N0 bis N7) und den Modifier-Registern (M0 bis M7) ergibt sich eine Vielzahl an indirekten Adressierungsarten (Bild 3). Dabei ist darauf zu achten, daß das Offset-Register und das Modifier-Register sich jeweils auf das Adreßregister mit dem gleichen Index (0-7) beziehen.

Mit den Modifier-Registern (nach dem Reset auf $FFFF gesetzt) lassen sich bequem Ring-Buffer realisieren. Nach jedem Adreß-Update beziehungsweise nach einem LUA-Befehl wird das Adreßregister (Rn) mit dem Modifier-Register modulo-verknüpft. Steht in M0 zum Beispiel $17 und in R0 $337, dann führt ein Adreß-Update, wie nach (R0)+. zu der neuen Adresse $320. Dieses Beispiel realisiert einen 24 Einträge großen Ring-Buffer an der Adresse $320. Da der Adreßraum auf 16 Bit begrenzt ist, entspricht ein Modifier-Wert von $FFFF einem linearen Adreß-Update. Ein Sonderfall gilt für den Modifier-Wert 1. Dieser „Bit Reverse Modus“ ist für Fast Fourier Transformationen interessant.

Breit...

Neben der indirekten Adressierung steht natürlich noch die direkte zur Wahl. Hier läßt sich durch Angabe einer konkreten Adresse, oder im Assemblertext indirekt über ein Label, eine bestimmte Speicherzelle bestimmen. Intern spaltet sich diese Adressierungsart nochmals in drei Fälle: die 16-Bit-Adresse, der 6-Bit-Adresse und die obere 6-Bit-Adresse. Letztere wird quasi von der maximalen Adresse ($FFFF) heruntergezählt. Auf diese Weise lassen sich die Ein-/Ausgaberegister im oberen Bereich des X-Speichers über eine 6-Bit-Adresse ansprechen.

... breiter

Die Befehlsberechnungen laufen in aller Regel über die beiden 56 Bit breiten Akkumulatoren A und B. Als Quellregister stehen zusätzlich die zwei Eingangsdatenregister X und Y beziehungsweise deren Substrate (X0, X1, Y0 und Y1) zur Wahl.

Befehl Quelle S oder S1 Ziel D oder Quelle S2 Beschreibung
ABS D A * Berechne absoluten Wert (Zweierkomplement)
ADCS.D X A * Addiere 48-Bit-Quelle mit Carry fui 96-Bit-Addition (doppelte Genauigkeit)
ADD S,D A,X,X0 A * Addiere (24-, 48- oder 56-3it-0uelle)
ADDLSD A A * Schiebe links und addiere Akku
ADDR S,D A A * Schiebe rechts und addiere Akku
AND S.D XO A * Logisches UND (24-Bit-Operation. ändert nur A1/B1)
ANDI S,D #x MR, CCR, OMR Logisches UND mit direktem Datum fur Control-Register
ASLD A * Schiebe Akku eins nach links
ASRD A * Schiebe Akku eins nach rechts (behalte Vorzeichen)
BCHG S,D #n ea,pp,aa,ar Teste Bit und ändere es
BCLR S,D #n ea,pp,aa,ar Teste Bit und lösche es
BSET S,D #n ea,pp,aa,ar Teste Bit und setze es
BTST SD #n ea,pp,aa,ar teste Bit
CLRD A * Lösche Akku
CMP S1,S2 A,X0 A * Vergleiche (setze Condition Code Register)
CMPM S1,S2 A,X0 A * Vergleiche absoluten Wert (56-Bit Befehl, evtl vorzeichenerweitert)
DIV S,D X0 A * Dividiere stufenweise (behalte Rest) eine komplette Division benötigt 24 Wiederholungen
DO S,D #X< ea,aa,ar Label Wiederhole S-mal die Befehle bis zum Label Sprunge auf den letzten Befehl innerhalb einer Schleife sind nicht erlaubt Für den letzten Befehl innerhalb der Schleife gibt es Einschränkungen (evtl. NOPs einfugen)
ENDDO Beende Hardware LOOP (DO)
EOR S,D X0 A * Logische X-ODER-Verknüpfung (24-Bit Operation)
ILLEGAL Illegaler Befehl (löst einen Interrupt aus)
Jcc D Label Bedingter Sprung
JCLR #n,S,D ea,aa,pp,ar Label Springe bei gelöschtem Bit
JMP D Label Springe
JScc D Label Bedingter Sprung in Unterroutine
JSGLR #n,S,D ea,aa,pp,ar Label Springe in Unterroutine bei gelöschtem Bit
JSET #n,S,D ea,aa,pp,ar Label Springe bei gesetztem Bit
JSRD Label Springe in Unterroutine
JSSET #n,S,D ea,aa,pp,ar Label Springe bei gesetztem Bit in Unterroutne
LSL D A * Schiebe Akku (A1/B1) um eins nach links (tulle mit Null, Bit 47 ms Carry-Bit)
LSR D A 4 Schiebe Akku (A1/B1) um eins nach rechts (fülle mit Null, Bit 24 ms Carry-Bit)
LUA ea D Rn,Nn Berechne Adresse und passe Adiesse an
MAC +S1,S2,D S1=S2=X0 A * Multipliziere mit Vorzeichen und addiere/subtrahiere zu Akku
MACR +S1,S2,D S1=S2=X0 A * wie MAC aber gerundet
MOVE S,D ea,aa,ar,#x,ea,aa,ar Kopiere (entspricht NOP mit Parallel-MOVE) (Rn und Nn als Ziel stehen erst im übernächsten Befehl bereit)
MOVEC S D ea,aa,ar,#x,ea,aa,ar Kopiere von/zu Kontrollregister
MOVEM S,D ea,aa,ar,#x,ea,aa,ar Kopiere von/zu Programmspeicher (z.B. P:ea)
MOVEPSD ea,pp,ar,#x,ea,aa.ar Kopiere von/zu Peripherie
MPY +S1,S2,D S1=S2=X0 A * Multipliziere mit Vorzeichen und schreibe Ergebnis in Akku
MPYR +S1,S2,D S1=S2=X0 A * wie MPY. aber gerundet
NEGD A * Negiere Akku
NOP Keine Operation
NORM S,D Rn A Normiere Akku (Rn enthält dann die Anzahl der Shifts, positiv == rechts)
NOT D A * Negiere Accumulator
OR S.D XO A Logische ODER-Verknüpfung
ORI S,D #x MRCCROMR Logische ODER-Verknüpfung mit einer konstanten
REPD ea.aa at.AX Wiederhole nur folgenden Befehl (gleiche Einschränkungen wie bei DOJ.QQP)
RESET Software Reset der kompletten Qn-Chip-Peripherie
RNDD A * Runde Akku (abhängig von Scaling-Mode)
ROLD A * Rotiere Akku eins nach links (24-Bit A1/B1)
RORD A * Rotiere Akku eins nach rechts (24-Bit A1/B1)
RTI Beende Interrupt-Routine
RTS Beende Unterroutine
SBC SD X A * Subtrahiere lang (56-Bit) mit Carry-Bit
STOP Halte Prozessor an (Low-power standby state)
SUBSD A,X,X0 A * Subtrahiere (24- 48- oder 56- Bit Quelle)
SUBL S,D A A * Schiebe nach links und Subtrahiere (für schnelle Division in FFTs)
SUBR S,D A A 4 Schiebe nach rechts und subtrahiere
SWi Software interrupt (IPL3)
Tcc S,D,S1,D1 S=A,X0 S1=Rn D=A D1=Rn Kopiere bei erfüllter Bedingung (S1/D1 optional)
TFR S,D A,X0 A * Kopiere 56-Bit-Register (erlaubt parallel moves!)
TSTS A * Teste Akku (setzt SR-Register)
WAIT Wartet auf einen interrupt

Bild 4: Man fühlt sich fast zu Hause (auf dem MC68030) angesichts der recht ähnlichen DSP-Befehle.

Während die logischen Verknüpfungen und die Bit-Schiebebefehle immer nur in 24 Bits operieren (nur A1 bzw. B1 werden modifiziert), stehen die Rechenbefehle (wie ADD, MAC, SUB, ...) als 56-Bit-Operationen bereit, wobei 24-Bit-Register (X0, X1, Y0 und Y1) und 48-Bit-Register (X und Y) als Quelle automatisch vorzeichenerweitert werden. Doch aufgepaßt: Die Akkumulatoren betrachtet der DSP bei Berechnungen als Festkommazahlen mit A2 und B2 vor dem Komma und A1, A0, B1 und B0 hinter dem Komma. Damit ist der Wert eines 48-Bit-Registers nicht größer, sondern lediglich genauer als der eines 24-Bit-Registers. Anders ausgedrückt: Ein 24-Bit-Wert wird zuerst um 24 B it nach links geschoben und dann auf 56 Bit vorzeichenerweitert, ein 48-Bit-Wert hingegen wird nur noch vorzeichenerweitert. Dennoch lassen sich mit selbigen Rechenbefehlen bequem 24-Bit-Integer-Berechnungen durchführen. Man muß sich nur stets vor Augen halten, daß sich diese Integer in den mittleren Teilregistern der Akkumulatoren (A1 und B1) befinden.

Den oberen Teilregistern der Akkumulatoren (A2 und B2) kommt noch eine weitere Aufgabe zu. Sie dienen als Overflow- bzw. Underflow-Zähler. Damit lassen sich hintereinander 255 Additionen oder Subtraktionen in einem Akkumulator ausführen, bevor man auf einen eventuellen Überlauf prüfen muß. Daß sich somit jede Menge Rechenzeit einsparen läßt, versteht sich von selbst.

Statussymbole

Das CCR-Register des Statusregisters entspricht weitgehend dem des MC68000 und wird, soweit sinnvoll, von jedem Befehl entsprechend gesetzt. Eine Ausnahme bildet das Limit-Bit. Dieses wird bei jedem Setzen des Overflow-Bits mitgesetzt, läßt sich dann aber nur durch einen expliziten Löschbefehl (wie z.B. ANDI #$FFCF,SR) auf Null zurücksetzen. Entsprechend den Bit-Kombinationen des CCR-Registers entwickeln sich daraus die bedingten Sprünge (siehe Bild 4).

Das Registergespann LC und LA sorgt für weitere Rechenpower. In Kombination mit den Befehlen REP oder DO benötigt eine Hardware-Schleife nur einen initialen Befehlszyklus. Das Herunterzählen des Schleifenzählers LC und der Sprung vom Ende einer Schleife zum Beginn erledigt der digitale Knecht parallel zu den ausführenden Befehlen. Ein kleiner Schönheitsfehler ließ sich dennoch nicht vermeiden: Bedingt durch die Pipelining-Architektur (die Befehlsdekodierung des nächsten Befehls läuft parallel zur Befehlsausführung des aktuellen Befehls) verschluckt der DSP den ersten Befehl nach einer Schleife, wenn man diese mit END-DO abbricht. Dieses Problem umgeht man am einfachsten durch das Einfügen eines NOP-Befehls nach der Schleife.

Mnemonic Zugriffsadresse Wert von Rn nach Befehlsausführung
(Rn) Rn Rn
(Rn+Nn) Rn+Nn Rn
(Rn)+ Rn Rn+1
(Rn)- Rn Rn-1
-(Rn) Rn-1 Rn-1
(Rn)+Nn Rn Rn+Nn
(Rn)-Nn Rn Rn-Nn

Bild 3: Die vielfältigen indirekten Adressierungsarten des DSP56001

Viel zu wichtig, um es an dieser Stelle nicht zu erwähnen: Die Hardware-Schleife arbeitet auch verschachtelt. Dazu kopiert der Prozessor vor dem Schleifenbeginn die alten Werte des LC und LA in den schnellen prozessorinternen System-Stack und holt sich die alten Werte bei einem Schleifenabbruch wieder.

Soweit sind neben den Registern auch schon die Befehle ausreichend erläutert, und es stellt sich nur noch die alles entscheidende Frage nach der Assembler-Syntax. Schließlich wollen noch die Parallel-Moves untergebracht sein. Alle mit dem Asterix gekennzeichneten Befehle aus Bild 4 erlauben neben dem eigentlichen Befehl noch maximal zwei Parallel-Moves. Wie der Name schon verrät, benötigen die Parallel-Moves im Prinzip keine weitere Rechenzeit. Eine Ausnahme bilden Zugriffe auf externes RAM. Parallel-Moves werden durch Leerzeichen getrennt, an den Befehl in derselben Zeile angehängt, ohne explizit das Kommando MOVE zu verwenden.

Beispiel:

ADD X0,A X:(R0)+,Y1 Y:(R4)+N4,B0

Obiges Kommando addiert XO zum Akkumulator A, kopiert den X-Speichereintrag, auf den RO zeigt, in das Register Y1, erhöht R0 um eins (unter Berücksichtigung des Modifier-Registers M0), dann wird B0 mit dem Wert aus dem Y-Speicher, auf den R4 zeigt, geladen, und zu guter Letzt wächst R4 um den Betrag von N4 (wieder mit Hilfe von M4). Zeigen R0 und R4 in das prozessorinterne RAM, begnügt sich diese Befehlsanreicherung mit nur einem Zyklus. Selbstverständlich sind auch Register-zu-Register-Transfers als Parallel-Moves gestattet.

Ohne es bisher erwähnt zu haben, ist jetzt auch klar, wie die verschiedenen Spei -eher anzusprechen sind, nämlich durch Voranstellen des Kürzels, gefolgt von einem Doppelpunkt. Auf gleiche Weise greift man auf den Programmspeicher zu (MOVE P:$36,X0). Langwörter (zwei 24-Bit Werte, eines aus dem X-Speicher und eines aus dem Y-Speicher) kopiert man bequemer mit dem L-Kürzel, vorausgesetzt die Adresse im X- und Y-Speicher ist die gleiche (z.B. MOVE L:(R3),Y).

Auch jetzt sind wieder ein paar Ausnahmen zu bedauern. Will man gleichzeitig auf einen X- und einen Y-Speicher via Adreßregister zugreifen, dürfen die zwei Adreßregister nicht aus der gleichen Hälfte (R0-R3 und R4 bis R7) stammen. Hier ein Falsch-Richtig-Beispiel:

Falsch:

SUB X0,B X:(R0),X0 Y:(R3)+,X1

Richtig:

SUB X0,B X:(R0),X0 Y:(R4)+,X1

Ein weiteres Handicap läßt sich allerdings auch zum Positiven ummünzen. Da Datentransfers in die Register Rn, Nn und Mn erst zum übernächsten Befehl Wirkung zeigen, kann im nächsten Befehl noch mit den „alten“ Registerwerten gearbeitet werden. Außerdem steht für die Parallel-Moves noch nicht das Ergebnis des aktuellen Befehls bereit.

Der Befehl

AND X0,A A,B

kopiert also A nach B noch vor der UND-Verknüpfung.

Soviel zu den Befehlen und Registern des DSP. Noch kann der DSP nur so vor sich hin werkeln, ohne seine ermittelten Ergebnisse preisgeben zu können. Dies ändert sich mit dem zweiten Teil dieser Serie, wo wir die Schnittstellen sowohl auf DSP- wie auf MC68030-Seite vorstellen.

Literatur:

[1] DSP56000/DSP56001 Digital Signal Processor User's Manual, Motorola, 1990

[2] Das Buch zum ATARI Falcon030, Dietmar Hendricks, Alexander Herzlinger, Martin Pittelkow, Data Becker, 1992

Übersicht DSP-Programmierkurs

Teil 1: Register und Befehlssatz des DSP 56001

Teil 2: I/O-Schnittstellen und I/O-Programmierung des DSP 56001 und des MC68030

Teil 3: Programmiertools des DSP 56001 (Assembler, Linker, Debugger)

Teil 4: Tips und Tricks mit Programmbeispiel


Jürgen Lietzow
Links

Copyright-Bestimmungen: siehe Über diese Seite