Digitale Power - Grundlagen: XBIOS-Funktionen für den DSP 56k

Eine leistungsstarke Komponente im Sound-Subsystem des Falcon ist der digitale Signalprozessor DSPS6k. Atari greift dem Programmierer mit zahlreichen Betriebssystemfunktionen bei der Einbindung des »Coprozessors« unter die Arme.

Als eigen ständiger Prozessor ist der DSP56k in der Lage, unabhängig vom MC68030 Programme zu bearbeiten. Sein Speicheraufbau teilt sich in drei Bereiche: X-Speicher, Y-Speicher und Programmspeicher (P-Speicher). Intern verfügt der DSP56k bereits für 2x256x24 Bit sowie 512 x 24 Bit RAM, der durch 96 KByte externes statisches RAM ergänzt wird. Der maximale Adreßraum beschränkt sich auf je 192 KByte, da der Adreßbus lediglich 16 Bit breit ist. Moment: Mit 16 Bit lassen sich doch nur 65535 Adressen ansprechen, woher kommen die 192 KByte? Ganz einfach: Der DSP arbeitet mit Adressen zu je 24 Bit, also drei Byte. Ein DSP-Wort ist nicht 16 Bit, sondern 24 Bit breit.

Der X-Speicher ist ein Datenspeicher. In einem Assemblerquelltext sprechen Sie diese Adressen mit »X:$xxxx« an. Die ersten 256 Worte sind interner XSpeicher. Über ein einblendbares ROM erhalten Sie im Bereich von X:$100 bis X:$1ff Zugriff auf Mµ- und A-Law-Tabellen, die z.B. der Datenreduktion dienen. Die Adressen X:$ffc0 bis X:$ffff repräsentieren die Peripherie-Register des DSP.

Ganz ähnlich ist der Aufbau des Y-Speichers. Die ersten 256 Worte sind auch hier interner Speicher, gefolgt von einer einblendbaren Sinus-Tabelle im ROM (Y:$100 bis Y:$1ff). Die externen X- und Y-Speicherbereiche sind doppelt vorhanden, jeweils von X:$0 und Y:$0 bis X:$3fff und Y:$3fff. Dieser Block erscheint gespiegelt ab Adresse X:$4000 und Y:$4000. Die ersten 256 Worte - bei eingeblendeten ROM die ersten 512 Worte - überdecken jedoch den externen Speicher ab Adresse $0, nicht aber die Spiegelung.

Bild 1. Der Speicheraufbau des DSP 56k

Der Programmspeicher erstreckt sich von Adresse P:$0 bis P:$7fff. Die untersten 512 Worte sind dabei interner Speicher, dessen erste 64 Adressen eine Vektortabelle enthalten. Aus diesem Grund beginnt ein DSP-Programm in der Regel ab Adresse P:$40. Der P-Speicher überdeckt somit X- und Y-Speicher, wobei der Y-Speicher über die ersten 16 Kiloworte, der X Speicher die zweiten 16 Kiloworte überdeckt. Die relativen Adressen beginnen aber stets bei $0. Demnach zeigen die Paare X:$0/P:$4000 sowie Y:$0/P:$0 auf die jeweils gleiche physikalische Adresse. Da die ersten 64 Adressen des P-Speicher bereits belegt sind, dürfen Sie diese auch im Y-Speicher nicht verwenden. Diese Überschneidungen müssen Sie bei der Programmierung beachten (Bild 1).

Kommunikation mit dem MC68030

Den Datenaustausch zwischen DSP und MC68030 übernimmt das Host-Interface (Port B), wobei XBIOS-Funktionen den Transfer zum DSP steuern. Der DSP stellt derartigen Komfort natürlich nicht zur Verfügung. Hier müssen Sie den Port B über das PBC (»Port B Bus Control Register«) von Hand konfigurieren und den Datenfluß über das HSR (»Host Status Register«), HRX (»Host Receive Data Register«) und HRT (»Host Send Data Register«) selbst steuern. Die letzten beiden Register liegen an der gleichen Adresse, erfahren aber abhängig vom gewählten Modus (lesen, schreiben) unterschiedliche Behandlung. Für die Soundhardware ist besonders die zweite Schnittstelle interessant: Das SSI (»Synchronous Serial Interface«). Über das SSI gelangen die Daten des Multiplexer zum DSP und umgekehrt.

Bevor wir die XBIOS-Funktionen zum Umgang mit dem DSP erläutern, vorab einige allgemeine Hinweise zur Installation eines DSP-Programms. Um Konflikte mit anderen Programme zu vermeiden, ist es notwendig, den DSP für den von Ihnen benötigten Zeitraum für andere Anwendungen zu sperren. Erst jetzt darf das Hauptprogramm, also die 68030-Anwendung, weitere DSP-spezifische Funktionen aufrufen. Auf diese Weise bleibt der Status des DSP während des Benutzungszeitraums erhalten. Ein kleiner Teil des DSPSpeichers ist stets von residenten System-Programmen belegt. Ein DSP-Programm darf die unteren 64 Adressen nicht benutzen und beginnt somit frühestens ab Adresse P:$40. Adresse P:$0 enthält immer einen Sprungbefehl zum Programmstart:

org p: $0
	jmp start
org p: $40
	start: ...

Jedes geladene DSP-Programm (auch Unterprogramm) muß sich beim Betriebssystem über eine Identifikationsnummer anmelden. In der Regel stammt diese ID vom Betriebssystem. In Absprache mit Atari darf ein Programm diese Nummer auch selbst vorgeben. Somit läßt sich prüfen, ob ein bestimmtes Programm bereits im DSP enthalten ist, ein Neuladen entfällt dann. Der Grundgedanke von Atari geht aber noch einen Schritt weiter: DSP-Anwendungen sollen unabhängig vom Programm, das sie geladen hat, zur Verfügung stehen. Einmal installiert, kann jede Anwendung von ihnen profitieren. Der DSP verkraftet stets ein Programm, aber mehrere Unterprogramme.

Für die Programmierung des DSPs findet in der Regel ein Assembler Einsatz. Der Assembler und Linker übersetzt den Quelltext (.ASM) in eine Binärdatei. Ein spezieller Konverter erzeugt schließlich das ».LOD«-Format. Einige Funktionen des Betriebssystems greifen auf dieses Format zurück.

Die XBIOS-Funktionen

Wie bereits erwähnt, unterscheidet sich die Wortgröße des DSPs von bekannten Prozessoren. Diese Wortgröße könnte sich natürlich mit kommenden Computern von Atari und dem Einsatz neuer DSPs ändern. Die Funktion

size = Dsp_GetWordSize (); /* opcode: 103 */

liefert die Größe eines DSP-Wortes in Byte. Der Falcon030 liefert hier demnach den Wert 3.

status = Dsp_Lock (); /* opcode: 104 */

»Dsp_Lock ()« sollte vor jeglicher Aktion mit dem DSP erfolgen. Mit dieser Funktion sperren Sie den DSP für andere Anwendungen. Ein Rückgabewert von -1 signalisiert, daß eine andere Anwendung den DSP bereits benutzt. Liefert die Funktion den Wert Null, ist der DSP bis zum nächsten

Dsp_Unlock (); /* opcode: 105* /

gesperrt. Diese Funktion gibt den DSP wieder für andere Programme frei. Bevor Sie ein Programm in den DSP laden, müssen Sie vorher prüfen, ob ausreichend Speicherplatz vorhanden ist. Die Funktion

long *xavail, *yavail;
Dsp_Available (xavail, yavail); /* opcode: 106 */

gibt darüber Auskunft - getrennt nach X- und Y-Speicher. Beide Speicherbereiche beginnen an der physikalischen Adresse $0. Da sich der Programmspeicher aus diesen Bereichen zusammensetzt, ist der freie Speicher die Summe des X- und Y-Speichers. Der freie Speicher wird stets in DSP-Worten (24 Bit) angegeben. Ist ausreichend Platz vorhanden, reservieren Sie diesen mit:

long xreserve, yreserve;
Dsp_Reserve (xreserve, yreserve); /* opcode: 107 */

Nur so ist gewährleistet, daß Ihr Programm nicht von einem Unterprogramm überschrieben wird. Der reservierte Speicher beginnt wiederum bei X:$0 bzw. Y:$0 und bleibt bis zum nächsten Dsp_Reserve() erhalten. Hat alles geklappt, liefert die Funktion den Wert Null, andernfalls -1.

int ability;
ability = Dsp_RequestUniqueAbility (); /* opcode: 113 */

Diese Funktion liefert in »ability« eine bislang unbenutzte ID. Sie ist beim Start eines (Unter-)Programms mit anzugeben.

int ability;
ability = Dsp_GetProgAbility (); /* opcode: 114 */

»Dsp_GetProgAbility()« liefert die ID des aktiven DSP-Programms. Damit läßt sich prüfen, ob ein bestimmtes Programm bereits im DSP vorliegt.

char *file, *buffer;
int ability, status;
status = Dsp_LoadProg (file, ability, buffer); /* opcode: 108 */

»Dsp_LoadProg ()« lädt ein Programm von Diskette oder Festplatte und erzeugt im Speicherbereich, auf den »buffer« zeigt, das DSP-Programm, überträgt es in den DSP und startet es. Der Parameter »ability« bestimmt die zuvor festgelegte ID. Liefert die Funktion den Wert -1, ist ein Fehler aufgetreten. Die Puffergröße errechnet sich wie folgt: 3 * (Anzahl Worte für X-, Y- und P-Speicher + 3 * Anzahl der Speicherblöcke)) Der Faktor 3 gibt die Größe eines DSP-Worts in Byte an und gilt daher nur für den DSP56k char *file, codeptr; long size; size = Dsp_LodToBinary (file, codeptr); / opcode: 111 */

Die Funktion »Dsp_LoadToBinary()« lädt die »*.LOD«-Datei »file« und erzeugt ein lauffähiges DSP-Binary im Speicherblock »codeptr«. Seine Größe errechnet sich nach der obigen Formel. Als Rückgabewert liefert die Funktion die Länge des Binary oder - im Falle eines Fehlers - einen negativen Wert.

char *codeptr;
long codesize;
int ability;
Dsp_ExecProg (codeptr, codesize, ability); /* opcode: 109 */

Diese Funktion überträgt ein DSP-Binary in den DSP. Lade- und Konvertierungszeit entfallen dabei. »codeptr« zeigt auf einen Speicherblock der Länge »codesize«. »ability« enthält die ID des Programms.

Unterprogramme

Das Betriebssystem des Falcon erlaubt die Installation mehrerer Unterprogramme im DSP-Speicher. Diese bleiben solange erhalten, bis sie das Hauptprogramm (des MC68030) löscht oder ein anderes Unterprogramm aus Platzgründen verdrängt. Folgenden Punkte sind außerdem zu beachten:

  1. Vor der Installation eines Unterprogramms muß das Hauptprogramm den DSP für andere Anwendungen sperren

  2. Die Startadresse der Routine ist stets P:$0. Sobald sie installiert ist, ist die tatsächliche Adresse im DSP-Speicher über den Host-Port erhältlich. Das Unterprogramm sollte diese Adresse stets abholen, selbst wenn es diese nicht benötigt.

  3. Ein Unterprogramm muß relokatibel, d.h. es darf keine festen Adressen verwenden

  4. Jegliche initialisierten Daten müssen sich Programmspeicher befinden

  5. Der X- und Y-Speicher von $3f00 bis $3fff steht für Variablen zur Verfügung. Da jedes Unterprogramm diesen Bereich nutzt, ist es nicht gewährleistet, daß abgelegte Daten bei einem erneuten Aufruf noch immer noch vorhanden sind.

  6. Da alle Unterprogramme wie Interruptroutinen behandelt werden, müssen sie mit dem Befehl »rti« (»return from interrupt«) enden

  7. Der Status des DSP kann sich zwischen zwei Aufrufen ändern und muß daher bei Bedarf zu Beginn stets neu gesetzt werden

  8. Die maximale Größe für ein Unterprogramm beträgt 1024 DSP-Worte

    char ptr; int ability, handle; long size; handle = Dsp_LoadSubroutine (ptr, size, ability); / opcode: 116 */

Mit dieser Funktion laden Sie ein Unterprogramm. »ptr« zeigt auf das Unterprogramm der Größe »size«. Auch Unterprogramme sind durch eine ID gekennzeichnet, die Sie in »ability« übergeben. Falls die Funktion das Unterprogramm installieren konnte, erhalten Sie als Rückgabewert ein positives »handle«, sonst den Wert Null. Unterprogramme bleiben solange bestehen, bis sie ein anderes Unterprogramm verdrängt oder sie das Hauptprogramm mit der Funktion

Dsp_FlushSubroutines (); /* opcode: 115 */

löscht. Wie auch »normale« Programme, erhalten Unterprogramme über die Funktion »Dsp_RequestUniqueAbility()« eine ID. Über sie lassen sich die verschiedenen Unterprogramme unterscheiden. Die Funktion

int handle, ability;
handle= Dsp_InqSubrAbility (ability) /*opcode: 117 */

liefert das »handle« des Unterprogramms mit der ID »ability«, falls dieses installiert ist, sonst den Wert Null.

Bislang ist das Unterprogramm zwar geladen, wurde aber noch nicht ausgeführt. Hierzu dient die Funktion

int status, handle;
status = Dsp_RunSubroutine (handle) /* opcode: 118 */

Sie startet das Unterprogramm mit der Kennung »handle«. Im Erfolgsfall enthält »status« den Wert Null. Ein negativer Wert signalisiert einen Fehler. Bevor Sie ein Unterprogramm starten, müssen Sie dieses über »DSP_InqSubrAbility()« oder »Dsp_LoadSubroutine()« eindeutig identifizieren.

Datentransfer

Auch für den Datenfluß vom Hauptspeicher des MC68030 zum DSP und umgekehrt stellt das XBIOS zahlreiche Funktionen zur Verfügung, die teilweise sogar im Hintergrund laufen. Die maximale Feldgröße für eine Übertragung beträgt stets 64 KByte.

char *data_in, *data_out;
long size_in, size_out;
Dsp_DoBlock (data_in, size_in, data_out, size_out) /* opcode: 96 */

überträgt »size_in« DSP-Worte an den DSP aus dem Puffer, auf den »data_in« zeigt. Anschließend verläuft die Übertragung umgekehrt, also »size_out« DSPWorte zum Puffer »data_out«. Beide Übertragungen verlaufen ohne Handshake. Die Funktion erwartet, daß der DSP seine Daten rechtzeitig abholt. Dsp_DoBlock() wartet in beiden Richtungen auf das erfolgreiche Senden des ersten Wortes, bevor die restlichen Daten folgen.

Sollen nur Daten zum DSP gelangen, muß size_out Null sein. Umgekehrt ist size_in immer dann Null, falls nur Daten vom DSP kommen.

char *data_in, *data_out;
long size_in, size_out;
Dsp_BlkHandShake (data_in, size_in, data_out, size_out); /* opcode: 97 */

Diese Funktion ist mit der vorherigen absolut identisch, abgesehen vom Handshake. Die Ausführungsgeschwindigkeit sinkt dabei entsprechend.

long *data_in, *data_out;
long size_in, size_out;
Dsp_BlkUnpacked (data_in, size_in, data_out, sizeout); /* opcode: 98 */

»Dsp_BlkUnpacked()« arbeitet nur mit Computern, deren DSP-Wortgröße kleiner oder gleich 4 ist. Die Funktion der Parameter bleibt gleich, nur handelt es sich bei den Feldeinträgen von data_in und »dataout« um Langworte. Bei der Übertragung bleiben die signifikantesten Bit (abhängig von der Wortgröße) jedoch unbeachtet. Liefert Dsp_GetWordSize() den Wert 3, beachtet die Funktion nur die unteren 24 Bit. Die oberen acht Bit eines ankommenden DSP-Wortes enthalten aber nicht zwangsweise den Wert Null und müssen bei Bedarf ausmaskiert werden.

int *data_in, *data_out;
long size_in, size_out;
Dsp_BlkWords (data_in, size_in, data_out, sizeout); /* opcode: 123 */

»Dsp_BlkWords()« erweitert 16-Bit-Werte vorzeichenrichtig auf DSP-Wortgröße und überträgt sie anschließend zum DSP. Umgekehrt schreibt die Funktion nur die unteren 16 Bits eines ankommenden DSP Wortes in den Empfangspuffer.

BYTE *data_in, *data_out;
long size_in, size_out;
Dsp_BlkBytes ( data_in, size_in, data_out, size_out /* opcode: 124 */

»Dsp_BIkByte()« überträgt vorzeichenlose 8-Bit-Werte. Die Erweiterung auf die DSP-Wortgröße findet ohne Berücksichtigung des Vorzeichens statt. Analog hierzu schreibt die Funktion nur die untersten 8 Bit eines DSP-Wortes in den Empfangspuffer.

long numsend numreceive;
struct dspblock
int blocktype;
/* 0 =long */
/* 1 = signed int
/* 2 = unsigned char */
long blocksize;
long blockaddr;
struct dspblock sendblocks[];
struct dspblock receiveblocks[];
Dsp_MultBlocks (numsend, numreceive, sendblocks, receiveblocks); /* opcode: 127 */

Um mehrere Puffer zu übertragen, ohne dabei jeweils eine XBIOS-Funktion aufrufen zu müssen, dient »Dsp_MultBlocks ()«. Die Parameter »numsend« und »numreceive« geben die Anzahl der Elemente vom Typ »dspblock«, getrennt für Empfangs- und Sendepuffer, an. »sendblocks« und »receiveblocks« sind die Anfangsadressen der beiden dspblock-Strukturen, die die Empfangs- und Sendedaten enthalten. Der Aufbau im Detail: »block_type« legt den Datentyp fest (long, int oder char). »block_size« bestimmt die Anzahl der zu übertragenden Elemente, wobei »block_adress« auf den entsprechenden Pufferanfang zeigt.

Im Gegensatz zu den besprochenen Funktionen kehren die nachfolgenden sofort nach dem Aufruf zurück. Der Datentransfer verläuft »im Hintergrund«.

char *data_in;
long block_size, num_blocks, *blocks_done;
Dsp_InStream (data_in, block_size, num_blocks, blocks_done); /* opcode: 99 */

Diese Funktion versorgt den DSP über einen speziellen Interrupt-Handler mit Daten aus dem Puffer, auf den data_in zeigt. Bei jedem Interrupt überträgt »Dsp_InStream()« »block_size« DSP-Worte - ohne Handshake. Der Vorgang endet, sobald »numblocks« Pakete bearbeitet wurden. Zwischenzeitlich teilt die Interrupt-Routine über den long-Pointer »block_done« dem Programm mit, wieviele Blöcke bereits übertragen wurden.

char *data_out;
long block_size, num_blocks, *blocks_done;
Dsp_OutStream (data_out, block_size, num_blocks, blocks_done); / * opcode: 100 * /

»Dsp_OutStream()« arbeitet analog zu Dsp_InStream, jedoch für den Datentransport vom DSP zum Hauptspeicher des MC 68030. Die ankommenden Daten gelangen in den Puffer, auf den »data_out« zeigt.

Ein Zusammenspiel der beiden letzten Funktionen ist

char *data_in, *data_out;
long block_insize, block_outsize;
long num_blocks, *blocks_done;
Dsp_IOStream (data_in, block_size, num_blocks, blocks_done); /* opcode: 101 */

Zunächst schickt »Dsp_IOStream()« einen Datenblock an den DSP. Sobald dieser einen Block zurückliefert, erhält er im Gegenzug einen weiteren. Die weitere Verwendung ist identisch mit »Dsp_InStream()« und »Dsp_OutStream()«. Natürlich muß gewährleistet sein, daß das DSP-Programm und das Hauptprogramm Hand in Hand arbeiten.

Die restlichen stellen wir ihnen in einer der kommenden Ausgaben ausführlich vor. Hierbei handelt es sich vornehmlich um DSP-Interrupt- und Portfunktionen. Wer richtig in die Architektur und Programmierung des DSP einsteigen will, sei [3] wärmstens empfohlen.

Literaturhinweise:

[1] „Falcon030 Developer Dokumentation“, 1. 10. 1992, Atari Corporation
[2] Hendricks, Herzlinger, Pittelkow: Das Buch zum Atari Falcon030, 1. Auflage 1992, Data Becker GmbH, ISBN 3-89011-622-1, 338 Seiten, 29,80 Mark
[3] „DSP56000/DSP56001 Digital Signal Processor User Manual“, Bestellnummer: DSP56000UM/AD Rev. 2, 62,30 Mark. Bezugsquelle: EBV-Elektronik, Hans-Pinsel-Straße 4, 8013 Haar b. München


Armin Hierstetter
Aus: TOS 03 / 1993, Seite 100

Links

Copyright-Bestimmungen: siehe Über diese Seite