Kapitel 1
Der Signalprozessor Analog Devices
ADSP-21369
1.1 Grundlagen
1.1.1 Einleitung
Im ersten Versuch wird der Aufbau und die Programmierung des Prozessors ADSP-21369
von Analog Devices erklärt. Um einen leichten Einstieg in die Projektphase im Anschluss
zu ermöglichen, wurde am IND eine Software erstellt. Die Prinzipien dieser Software sind
im zusätzlichen Dokument „Rapid Prototyping on Embedded targets: RTProcDSP“ erläu-
tert. Durch die Software-Vorgabe wird es den Studenten ermöglicht, den Prototypen eines
neuartigen Algorithmus zu erstellen, ohne allzuviel mit den Programmierungsdetails des
Echtzeit- Betriebssystems in Kontakt zu kommen.
Zur Programmierung der Hardware bietet Analog Devices eine Entwicklungsumgebung an,
die sich Visual DSP++ nennt. Diese wird schließlich kurz vorgestellt. Mit ihrer Hilfe wer-
den im Anschluss die Übungsbeispiele simuliert bzw. emuliert.
1.1.2 Grundstruktur der Signalprozessor-Architektur
In den letzten Jahren war bei Bauteilen in Hardware-Komponenten neuer Produkte eine
Spezialisierung auf den Anwendungsfall zu beobachten. Es macht keinen Sinn, für Algo-
rithmen zur reinen Signalverarbeitung die Kosten eines General-Purpose Prozessors (z.B.
Intel Pentium Prozessor) bezahlen zu müssen, wenn dieselben Algorithmen von einem Pro-
zessor ausgeführt werden können, der auf diese Art von Anwendungen spezialisiert ist.
Gleichzeitig erscheint es auch in vielen Bereichen sinnvoll, Algorithmen nicht in „Silicon“,
also Hardware, sondern in Software zu realisieren. Dadurch kann auf sich ändernde Sys-
temanforderungen bei existierenden Produkten schnell und umfassend reagiert werden, in-
dem zum Beispiel Software-Updates durchgeführt werden.
DSPs sind für Echtzeitanwendungen, z. B. im Bereich der Audio- und Videoverarbeitung,
entwickelt worden und verfügen über für ihren Einsatz angepasste Befehlssätze und Archi-
tekturen. Sie haben einen niedrigen Leistungsverbrauch und sind durch ihre Spezialisierung
in der Anschaffung sehr günstig. Ein Programmierer kann im Bereich der Signalverarbeitungs-
7
Algorithmen sehr kompakten Code erzeugen, was zu einer Minimierung des Speicherbe-
darfs in einem Produkt führt.
Die ersten Signalprozessoren waren modular aus Bit-Slice-Elementen aufgebaut. Der damit
verbundene hohe Aufwand beschränkte den Einsatz der digitalen Signalverarbeitung auf
spezielle Gebiete. Erst die Entwicklung monolithisch integrierter Signalprozessoren An-
fang der achtziger Jahre ermöglichte den Einsatz der DSV in vielen kommerziellen Berei-
chen. Mittlerweile existiert ein breites Angebot digitaler Signalprozessoren verschiedener
Hersteller.
Trotz ihrer unterschiedlichen und komplexen Architekturen enthalten die heutigen Signal-
prozessoren gemeinsame Grundstrukturen. Diese Grundstrukturen ergeben sich aus den An-
forderungen der DSV-Algorithmen.
Die für die DSV exemplarischen Algorithmen wie z. B. die Fourier-Transformation, die
Korrelation und die digitale Filterung enthalten in ihrem Kern vektorielle Skalarprodukte.
Um diese Skalarprodukte schnell berechnen zu können, sind Signalprozessoren mit einem
Hardware-Multiplizierer ausgestattet. Diesem ist ein Addierer/Akkumulator nachgeschal-
tet.
Abbildung 1.1 zeigt den prinzipiellen Aufbau der Recheneinheit (Daten-ALU (Arithmetic
Logical Unit)) am Beispiel eines 16 Bit Festkomma Signalprozessors. Die zu verarbeiten-
den Daten werden in Eingangsregistern zwischengespeichert und dem Multiplizierer zu-
geführt. Damit keine Genauigkeitsverluste auftreten, wird das Ergebnis der Multiplikation
zunächst mit einer Wortbreite von 31 bzw. 32 Bit dargestellt und zum Inhalt des Ausgangs-
registers addiert. Die mit insgesamt 40 Bit um 8 Bit größeren Wortbreiten von Addierer und
Ausgangsregister ermöglichen die überlauffreie Addition von bis zu 28 = 256 Produkten.
Abbildung 1.1: Daten-ALU eines 16-Bit Signalprozessors
Erst wenn das Ergebnis feststeht, also alle Multiplikationen und Additionen durchgeführt
wurden, verringert ein sogenannter Barrelshifter die Ergebniswortbreite auf das ursprüng-
liche 16 Bit Format. Dabei kann eine mathematische Rundungsoperation und eine betrags-
mäßige Begrenzung eingeschlossen sein.
Multiplizierer und Addierer arbeiten gleichzeitig. Das bedeutet, dass in einem Maschinen-
8
zyklus die Werte aus den Eingangsregistern multipliziert werden können und das Ergebnis
des Multiplizierers im Addierer verarbeitet werden kann. Damit werden zwei Rechenope-
rationen pro Zyklus ausgeführt. Das vektorielle Skalarprodukt y der Dimension N kann im
Prinzip in N Prozessorzyklen berechnet werden:
N−1
y = x · bT = bi · xi (1.1)
i=0
mit
x = {x0 , x1 , . . . , xN−1 } (1.2)
und
b = {b0 , b1 , . . . , bN−1 }. (1.3)
Das hier beschriebene gleichzeitige Addieren und Multiplizieren ist eines der wichtigsten
Merkmale eines Signalprozessors und wird durch spezielle multiply/accumulate-Befehle
unterstützt. Bei einigen Signalprozessoren werden die Multiplikation und die Addition in
zwei aufeinander folgenden Zyklen, d.h. im „Pipeline“ - Betrieb durchgeführt. Im Akku-
mulator wird dabei jeweils das Produktergebnis aus dem vorhergehenden Befehlszyklus
auf das Ausgangsregister aufaddiert. Da der Multiplizierer und der Akkumulator gleichzei-
tig arbeiten, dauert die Berechnung des Skalarproduktes der Dimension N insgesamt N + 1
Takte.
Selbstverständlich bietet ein DSP auch die Möglichkeit, nur zu addieren oder nur zu multi-
plizieren. Weiterhin gehören Bit-Manipulationsbefehle und logische Operationen zum Be-
fehlsrepertoire eines Signalprozessors.
Im vorgestellten Beispiel müssen nach jedem Maschinenzyklus zwei neue Eingangsdaten
bereitstehen, wenn die Daten-ALU ausgelastet werden soll. Zusätzlich muss in jedem Pro-
grammschritt ein Befehlswort gelesen werden. Das bedeutet drei Datenübertragungen pro
Zyklus. Diese hohe Übertragungrate wird durch eine sogenannte „Harvard-Architektur“ er-
möglicht. Diese Architektur verfügt im Gegensatz zur „von Neumann-Struktur“ über se-
parate Programm- und Datenspeicher sowie über separate Daten- und Adressbusse. Da-
durch sind Schreib- und Leseoperationen im Datenspeicher möglich, während gleichzeitig
Instruktionen aus dem Programmspeicher gelesen werden.
Eine Super-Harvard-Architektur bietet zusätzlich die Möglichkeit, Instruktionen in einem
Cache zwischenzuspeichern. Der betrachtete Prozessor ADSP-21369 entspricht in seinem
Aufbau einer Super-Harvard Architektur. Abbildung 1.2 zeigt das Prinzipschaltbild eines
derartigen Signalprozessors mit erweiterter Harvard-Architektur.
Neben der Daten-ALU, dem Programm- und dem Datenspeicher sind zwei weitere Kom-
ponenten zu erkennen: die Programm-Ablaufsteuerung und die Adress-ALU.
Die Adress-ALU berechnet bei dieser Architektur pro Maschinenzyklus die beiden Spei-
cheradressen der Daten für die nächste Operation der Daten-ALU. Erst dadurch wird si-
chergestellt, dass in jedem Befehlszyklus neue Daten zu den Eingangsregistern übertragen
werden können.
Der Befehlssatz der Adress-ALU ist auf die Belange der DSV abgestimmt. Hervorzuheben
ist z. B. die in der Regel verfügbare Modulo-Adressarithmetik. Bei dieser Adressierungsart
9
Abbildung 1.2: Prinzipielle Architektur eines digitalen Signalprozessors
(DSP)
erzeugt die Adress-ALU aufeinanderfolgende Adressen innerhalb eines bestimmten Berei-
ches. Ist das Bereichsende erreicht, so wird mit dem Bereichsanfang fortgefahren, ohne
dass dazu zusätzliche Programmschritte notwendig sind. Diese Eigenschaft lässt sich z. B.
vorteilhaft in Programmen nutzen, in denen Daten blockweise verarbeitet werden (Modulo-
Adressierung, vgl. Abschnitt 1.2.5).
Die Leistungsmerkmale der heute verfügbaren Prozessoren unterscheiden sich bezüglich
• Arithmetik
• Speicherkapazität
• Geschwindigkeit
• Architektur und Befehlssatz.
Für eine kleine, repräsentative Auswahl aus dem derzeitigen Angebot wurden in Tabelle 1.1
einige Angaben zum Entwicklungsstand integrierter Signalprozessoren zusammengestellt.
10
Firma Typ Jahr Zyklus/Frequenz Wortbreite Arithmetik Daten-RAM
Infineon Carmel 10xx 4 ns/250 MHz 16 bit Festkomma (← Core)
Carmel 20xx
(Core) a
Tricore 97-99 6 ns/167 MHz 16/32 Gleitkomma 32k b
(MCU-DSP)
Texas TMS320C25 1987 80 ns/12,5 MHz 16 Festkomma 4k x 16
Instruments TMS 320C40 33 ns/30 MHz 32 Gleitkomma 2k x 16
TMS 320C50 25 ns/40 MHz 16 Festkomma 10k x 16
TMS 320C62 1997 5 ns/200 MHz 16 Festkomma 2 x 512k
TMS 320C67 1997 6 ns/167 MHz 32 Gleitkomma 2 x 512k
TMS 320C64 2000 0,9 ns/1100 MHz (← Core)
(Core)
Motorola 56001 1987 100 ns/10 MHz 24 Festkomma 2 x 512k x 24
56301 12,5 ns/80 MHz 24 Festkomma 2 x 2k x 24
56800E 2000 5 ns/200 MHz 8-32 Festkomma (← Core)
(MCU-DSP)
56853 2001 8,3 ns/120 MHz 32k
Analog ADSP-21060 1995 25 ns/40 MHz 32 Gleitkomma 2 x 2M
Devices ADSP-21065L 1995 16 ns/60 MHz 32 Gleit/Fest 1M
2192 (Dual) 2000 6,25 ns/160 MHz 16 Festkomma (← Core)
21116 ($5) 2000 10 ns/100 MHz 32 Gleitkomma
ADSP-21369 2005 2,5 ns/400 MHz 32 Gleit/Fest 16M
a
CLIW and power plugs support
b
Caches-support
Tabelle 1.1: Auswahl von aktuellen und alten DSPs
11
1.2 Signalprozessor Analog Devices ADSP-21369
1.2.1 Architektur
Zunächst soll ein Überblick über die Prozessorarchitektur und seine für den Versuch re-
levanten Komponenten vorgestellt werden. Abschnitt 1.2 ist als eine kurze Einführung zu
verstehen. Für weitere Informationen wird auf die „ADSP-2136x SHARC Programming
Reference“ verwiesen, die im Folgenden kurz „Handbuch“ genannt wird.
Der in Versuch 4 und der anschließenden Projektphase eingesetzte Signalprozessor ADSP-
21369 ist ein 32 Bit Gleitkommaprozessor, der bei einer Taktfrequenz von 333 MHz arbeitet
und 8 MB on-chip-Speicher hat (2 MB SRAM und 6 MB ROM). Er hat darüberhinaus einen
Cache-Speicher, der 32 Befehle halten kann, und eine große Anzahl von Registern (spezielle
Speicherstellen, die z. B. Operanden und Ergebnisse enthalten). Dank paralleler Ausführung
großer Teile der ALU werden SIMD-Befehle (Single Instruction Multiple Data) unterstützt,
so dass der Prozessor bei voller Auslastung 2 GFLOPS (Giga Floating Point Operations
per Second) verrichten kann. Multi-Prozessor-Einsatz wird durch Link Ports unterstützt.
Mit dem DMA (Direct Memory Access) Controller lassen sich große Datenblöcke ein- und
ausgeben, ohne dass die Rechenkapazität des Prozessors reduziert wird.
Auf der Versuchshardware ist ein solcher Prozessor vorhanden. Desweiteren befindet sich
auf derselben Platine ein externer Speicher (SDRAM), der Platz für 4 Mega-Wort Daten
beinhaltet (32-Bit Worte) sowie ein 1 MB großer nichtflüchtiger Speicher (Flash-EEPROM).
Zur Peripherie gehören außerdem ein AD/DA-Wandler (AD1835A), über den die Audioein-
und ausgabe abläuft, sowie eine UART-Schnittstelle (ADM3202 RS-232), die der Laufzeit-
Komunikation mit dem PC dient. Die Audiodaten werden mit einer Abtastfrequenz von bis
zu 96 KHz, in der Regel aber 48 KHz eingelesen (bei einem DAC sogar bis zu 192Hz) und
mit 16, 20 oder 24 Bit quantisiert. Die UART-Schnittstelle hat eine Datenübertragungsrate
von 9600 bis 115200 Baud und dient der Übergabe von Variablen vom PC an den DSP und
andersherum. Die einzelnen Prozessor-Kennzahlen sind in Tabelle 1.2 zusammengefasst.
Prozessortyp ADSP-21369
Datentyp Gleitkomma (32 oder 40 Bit)
Festkomma (16 oder 32 Bit)
Speicher 2 MB SRAM + 6 MB ROM
+ 512 kBit SRAM + 4 Mx32 SDRAM
+ 8 MBit parallel flash + 2 Mbit SPI flash
Taktfrequenz 333 MHz
Analoge Ausgänge 4 Stück, Stereo
Analoge Eingänge 1 Stück, Stereo
Abtastraten bis zu 96 kHz 16, 20 oder 24 Bit
Analoge Verstärkung Vor- und Nachverstärkung
Schnittstelle UART, 16550, 115200 Baud
Tabelle 1.2: Prozessor-System-Merkmale
Abbildung 1.3 zeigt das vereinfachte Blockschaltbild des ADSP-21369. Es handelt sich um
12
eine „Super Harvard-Architektur“ (daher SHARC) mit SIMD-Funktionalität.
Abbildung 1.3: Vereinfachtes Blockschaltbild des ADSP-21369
Der Prozessorkern (core processor) des ADSP-21369 besteht aus zwei Verarbeitungseinhei-
ten (PEX und PEY), von denen jede aus drei Recheneinheiten (ALU, Multiplizierer, Shif-
ter) besteht sowie einem Programm-Steuerwerk, zwei Daten-Adressrechnern (DAG1 und
DAG2) mit jeweils acht Registern für indirekte Adressierung, zwei Timern, dem Befehls-
Cache und aus 16 Data-Registern im Data Register File. Darüber hinaus existieren meh-
rere Modus- und Statusregister, die die Funktion des Prozessors steuern (z. B. die Register
MODE1 und MODE2) und Aussage über Rechenergebnisse geben (z. B. das ASTAT Register).
Diese Register sind nicht in Abbildung 1.3 eingezeichnet.
Weiterhin sind in obiger Abbildung die DMA-Controller, acht full-duplex-fähige seriel-
le Schnittstellen, eine digitale Audioschnittstelle, die vier Präzisionstaktgeber (PCG), ei-
ne Eingabeschnittstelle (IDP), ein S/PDIF-Empfänger/-Sender, acht Kanäle mit jeweils ei-
nem asynchronen Abtastratenkonverter, acht serielle Schnittstellen, eine 16-bit Parallelein-
gabeschnittstelle (PDAP) und eine flexible Signal-Routing-Einheit (DAI SRU) enthält so-
wie ein Digital-Peripheral-Interface dargestellt, das drei Taktgeber, eine I2C-Schnittstelle,
zwei UARTs, zwei serielle periphere Schnittstellen (SPI) und eine flexible Signal-Routing-
Einheit (DPI SRU) enthält.
Wie oben schon erwähnt, verfügt der ADSP-21369 über SIMD-Funktionalität. Generell
wird die Verarbeitungseinheit PEX benutzt. Eine gleichzeitige Verwendung der Einheit PEY
kann durch Setzen des PEYEN-Mode-Bits im MODE1-Register aktiviert werden. Ist dies
der Fall, so werden die selben Instruktionen in beiden Verarbeitungseinheiten ausgeführt,
operieren allerdings auf verschiedenen Daten. Diese Architektur ist besonders bei rechen-
intensiven Algorithmen effizient.
Das Einschalten des SIMD-Modus’ hat auch eine Auswirkung auf die Art und Weise, wie
Daten zwischen Speicher und den beiden Verarbeitungseinheiten übertragen wird. Ist dieser
Modus aktiviert, so wird eine doppelte Datenbandbreite gebraucht, um den Rechenfluss
in beiden Einheiten aufrecht zu erhalten, so dass im SIMD-Modus mit jedem Zugriff auf
13
den Speicher oder die Register also zwei Datenwerte transferiert werden. Dementsprechend
müssen die Daten auch im Speicher auf korrekte Weise abgelegt werden müssen.
Bevor nun die Einheiten des Prozessors näher betrachtet werden, sollen erst die zur Verfü-
gung stehenden Datenformate erwähnt werden:
1.2.2 Datenformate
Der ADSP-21369 bietet sowohl Fest- als auch Gleitkomma-Formate an. Mit der Festkomma-
darstellung wird eine Zahl durch 32 Bits repräsentiert. In einem Register im Data Register
File belegen sie die obersten 32 der 40 Bits. Man unterscheidet zwischen den Darstellungen
signed und unsigned bzw. integer und fractional. Beim signed-Format ist das ers-
te Bit das Vorzeichenbit. Negative Zahlen werden durch das Zweierkomplement dargestellt.
Der Wert des LSBs ist für signed integer gleich 1 bzw. für signed fractional 2−31 . So ergeben
sich die Zahlenbereiche
− 231 ≤ x ≤ 231 − 1 (signed integer, SI) (1.4)
−31
−1 ≤ x ≤ 1 − 2 (signed fractional, SF). (1.5)
Mit dem unsigned-Format können nur positive Zahlen dargestellt werden. Es gelten die
Zahlenbereiche 0 ≤ x ≤ 232 − 1 (unsigned integer, UI) bzw. 0 ≤ x ≤ 1 − 2−32 (unsigned
fractional, UF). Die Gewichte der einzelnen Bits sind in Abbildung 1.4 verdeutlicht.
Abbildung 1.4: Die Gewichte einzelner Bits verschiedener Festkommafor-
mate bei Speicherung in einem 40-Bit Register
Zusätzlich zu der Festkommadarstellung bietet der ADSP-21369 ein Gleitkomma-Format
mit entweder 32 Bits oder 40 Bits an. Es besteht aus einem Vorzeichenbit s, 8 Bits für den
Exponenten e und 23 bzw. 31 Bits für den Bruchteil f der Basis, der durch 1. f dargestellt
wird. 1. f ist dabei eine Zahl zwischen 1 und 2 − 2−23 bzw. 1 und 2 − 2−31 . Der Wert einer
Zahl x ergibt sich dann aus dem Zusammenhang
x = (−1)s · (1. f ) · 2e−127 (1.6)
Abbildung 1.5 zeigt die Bitbelegung im Speicher.
Durch die Gleitkommadarstellung lassen sich Quantisierung und Übersteuerungsprobleme
weitgehend vermeiden. Sie bietet sich deswegen bei der Entwicklung neuer Algorithmen
und Prototypen an.
14
Abbildung 1.5: Gleitkomma-Formate für 32 bzw. 40 Bits
1.2.3 Recheneinheiten und Datenregister
Der Prozessorkern enthält wie in Abbildung 1.6 veranschaulicht drei unabhängige Rechen-
einheiten: eine ALU, einen Multiplizierer und einen Shifter.
Abbildung 1.6: Aufbau der Core Unit
Alle drei Datenformate können verarbeitet werden: 32 Bit Festkomma, 32 Bit Gleitkomma
bzw. 40 Bit Gleitkomma. Zwischen den Recheneinheiten und den Datenbussen befinden
sich die Datenregister (register file). Sie bestehen aus 16 Registern der Breite 40 Bit, in de-
nen Rechenoperanden und Ergebnisse gespeichert werden. Da alle Rechenoperationen nur
einen Prozessorzyklus benötigen, können die Ergebnisse eines Zyklus’ im darauffolgenden
Zyklus beliebig weiterverwendet werden.
Ein Register wird mit „F“ und einer Nummer 0-15 angesprochen, wenn es für Gleitkomma-
operationen verwendet wird, und mit „R“ und einer Nummer für Festkommaoperationen.
Das MR Register dient zur Speicherung eines Fixed-Point-Multiplikations Ergebnisses.
15
Um einen schnellen Kontextwechsel zu ermöglichen, stehen alle Register in zweifacher
Ausführung zur Verfügung. Die Modifikation eines entsprechenden Statusbits verursacht,
dass alle Prozessoroperationen auf diesen „Schatten“-Registern ausgeführt werden.
ALU
Die ALU verfügt über eine Vielzahl arithmetischer und logischer Operationen für sowohl
Festkomma- als auch Gleitkommadarstellungen. Für eine umfassende Liste dieser Opera-
tionen sei auf das Handbuch verwiesen. Hier ein kleines Beispiel:
Beispiel:
1. Addiere den Inhalt des Registers F0 zu dem des Registers F1 und spei-
chere das Ergebnis in F0
F0 = F0 + F1;
2. Nimm den Betrag von F0 und speichere das Ergebnis in F1
F1 = ABS F0;
3. Nimm den maximalen Wert von R1 und R2 und speichere das Ergebnis
in R0
R0 = MAX (R1, R2);
Alle Befehle sowie „F“ und „R“ können entweder groß oder klein geschrieben werden,
da der Assembler Groß- und Kleinschreibung nicht unterscheidet. In dem Statusregister
ASTAT befinden sich Flaggen, die nach dem Beenden einer ALU-Operation gesetzt wer-
den. Z. B. wird die Flagge AZ (Bit 0) gesetzt, wenn das Ergebnis der Operation Null ist.
Abbildung 1.7 zeigt die Bedeutung der einzelnen Bitstellen des Statusregisters ASTAT.
Abbildung 1.7: Aufbau des ASTAT-Registers
Die ALU wird auch für bitbezogene Operationen wie logische AND, OR, NOR und NOT
verwendet. Beispielsweise können mit AND einzelne Bits herausmaskiert und mit OR ein-
16
zelne Bits gesetzt werden. Außerdem kann mit XOR kontrolliert werden, in welchen Posi-
tionen sich zwei Register unterscheiden. Die logischen Operationen sind nur für Festkom-
maoperanden definiert und haben nur einen Einfluss auf die oberen 32 Bits eines Registers.
Die letzten 8 Bits werden zu Null gesetzt.
Abbildung 1.8: Beispiel für ALU-Operationen
Im Register MODE1 befinden sich drei Flaggen, mit denen u.a. entschieden wird, ob das
Ergebnis einer Festkommaoperation ggf. gesättigt werden soll und wie Gleitkommaresultate
gerundet werden sollen, siehe Handbuch, Abschnitt 2.5.2. Eine vollständige Beschreibung
des MODE1-Registers befindet sich im Handbuch auf den Seiten E-14 und E-15.
Multiplizierer
Mit dem Multiplizierer können Festkomma- oder Gleitkommamultiplikationen durchge-
führt werden. Das Resultat einer Festkommamultiplikation kann in einem der beiden Mul-
tiplikatorregister (MRF oder MRB) oder in einem Datenregister R0 - R15 gespeichert wer-
den. Bei Nutzung der Multiplikatorregister kann im selben Zyklus eine Addition zu oder
Subtraktion von dem letzten Inhalt des Registers vorgenommen werden.
Beispiel:
1. Multipliziere den Inhalt des Registers R0 mit dem von R1. Das Ergebnis
wird zum Inhalt von MRF addiert.
MRF = MRF + R0 · R1;
Auf diese Art und Weise kann sehr einfach eine multiply/accumulate-Operation implemen-
tiert werden.
Das Standardformat für Festkommamultiplikationen ist signed fractional. Wenn eine
andere Darstellung erwünscht ist, muss diese explizit angegeben werden. Dies erfolgt, in-
dem nach dem Befehl in Klammern die Buchstaben S oder U bzw. I oder F angefügt werden,
siehe Abschnitt 2.6.6 des Handbuches. Hierbei bezeichnen:
• S signed
• U unsigned
• I integer
17
• F fractional
Beide Operanden müssen entweder vom Typ integer oder fractional sein. Signed und unsi-
gned sind mischbar.
Beispiel:
1. Die Zahlen in R1 und R2 sind beide vom Format signed fractional
R0 = R1 · R2; oder
R0 = R1 · R2 (SSF);
2. Die Zahlen in R1 und R2 sind beide vom Format unsigned integer
R0 = R1 · R2 (UUI);
3. Die Zahl in R1 ist unsigned fractional und die in R2 signed
fractional. Das Ergebnis soll abgerundet werden
R0 = MRF - R1 · R2(USFR);
Eine Gleitkomma multiply/accumulate-Operation wird durch parallele Nutzung der ALU
und des Multiplizierers durchgeführt. Dabei ist jedoch zu beachten, dass für die Addition in
dem Beispiel der Wert in F8 zur Berechnung gewählt wird, der einen Takt zuvor bestimmt
wurde. Weiterhin sind die zu verwendenden Register einer Begrenzung unterworfen (siehe
hierzu auch Handbuch S. B-95).
Beispiel:
1. Berechne C := A · B + C
Die Werte von A, B und C liegen in F1, F2 bzw. F12.
F8 = F1 · F2, F12 = F8 + F12;
Genau wie die ALU hat auch der Multiplizierer eine Wirkung auf das Statusregister. Bei-
spielsweise kann mit dem MN-Bit im ASTAT-Register kontrolliert werden, ob das Ergebnis
einer Multiplikation negativ ist.
Shifter
Der Shifter stellt die dritte Recheneinheit dar. Mit dem Shifter kann z. B. der Inhalt eines Re-
gisters verschoben werden. Der Shifter arbeitet grundsätzlich nur in Festkommaarithmetik,
d.h. nur mit den oberen 32 Bits eines Registers. Die letzten 8 der 40 Bits im Resultatre-
gister werden immer zu Null gesetzt. Der Shifter hat auch eine Wirkung auf das ASTAT
Register. Zusätzlich zu den normalen Shift-Funktionen bietet der Shifter die Möglichkeit
zur Manipulation und zum Test einzelner Bits. Der Befehl zur Verschiebung des Inhaltes
eines Registers heißt LSHIFT (Logic shift). Die Wirkung wird durch das folgende Beispiel
illustriert.
1.2.4 Das Programm-Steuerwerk
Das Programm-Steuerwerk (Program Sequencer), siehe Abbildung 1.3, steuert den Ablauf
des Programms. Dieser ist meistens linear (sequentiell). Abweichungen von diesem linearen
Fluss werden durch die fünf Programmstrukturen loop, jump, subroutine, interrupt
und idle ermöglicht. Diese Strukturen sind in Abbildung 1.10 dargestellt.
18
Abbildung 1.9: Beispiel für Funktionalität des Shifters
Der Kern des Programmsteuerwerks besteht aus dem Programmzähler (Program Counter,
PC). Dieser enthält am Anfang eines Zyklus’ die Adresse des durchzuführenden Befehls
und wird während des Zyklus’ hochgezählt. Ein Sprung im Programm geschieht, indem
dem PC eine neue Adresse gegeben wird. Alle oben erwähnten Programmstrukturen beste-
hen auf die ein oder andere Weise aus Programmsprüngen. Ein Sprung erfolgt im allgemei-
nen zu einem in Assembler deklarierten „label“ (Sprungmarke).
Die Sprungmarke besteht aus einer willkürlichen, nicht reservierten Zeichenfolge gefolgt
von einem Doppelpunkt, z. B. „counter:“ oder „loop1:“. Bei Schleifen gibt die Sprungmarke
den letzten Befehl der Schleife an.
Kommentare können im Assemblerprogramm wie in C-Programmen zwischen „/*“ und
„*/“ eingebettet werden. Sie helfen später sowohl dem Programmierer als auch anderen, die
mit dem Quellencode zu tun haben, das Programm zu verstehen. Die Programmstrukturen
werden im folgenden kurz erklärt:
Loop
Eine Schleife (loop) lässt sich sehr bequem durch den loop counter (LCNTR) implementieren.
In LCNTR wird dabei zuerst die Zahl der Wiederholungen angegeben. Mit dem Befehl DO
< label > UNTIL LCE (loop counter expired) wird die Schleife ausgeführt. Das notwendige
Label < label > bezeichnet den letzten Befehl des zu iterierenden Loops.
Beispiel:
Zähle R0 von 0 bis 100
R0 = 0;
LCNTR = 100, DO counter UNTIL LCE;
counter: R0 = R0 + 1; /* this is the last
instruction of the loop */
Jump
Durch einen Sprung (jump) lässt sich eine Programmverzweigung implementieren. Der
Sprung kann entweder unbedingt oder bedingt sein. Bei einem bedingten Sprung wird zu-
19
Abbildung 1.10: Verschiedene Programmstrukturen
erst eine Flagge in einem Register, das u. a. vom ASTAT-Register abhängig ist, kontrolliert.
Wenn diese Flagge eins ist, erfolgt der Programmsprung, im anderen Fall nicht.
Unter anderem gibt es die Flaggen
EQ Result equal to zero
NE Result not equal to zero
LT Result less than zero
LE Result less than or equal to zero
AV Addition overflow
MV Multiplier overflow
Beispiel:
Wenn R0 − R1 ≤ 0, springe nach negative
R2 = R0 -R1;
IF LE JUMP negative;
positive: ...
negative: ...
Mit dem JUMP-Befehl kann auch eine Schleife implementiert werden.
20
Beispiel:
R0 = 0;
R1 = 100;
increase: R0 = R0 + 1; COMP (R0,R1);
IF NE JUMP increase;
...
Subroutine
Eine Subroutine kann mit einem Unterprogramm verglichen werden, zu dem jederzeit ge-
sprungen werden kann und von dem automatisch an die richtige Stelle im Hauptprogramm
wieder zurückgesprungen wird, wenn das Unterprogramm zu Ende ist. Mit dem Befehl
CALL wird der Sprung initiert und mit RTS (Return From Subroutine) wird die Subroutine
beendet. Vor dem Sprung wird die aktuelle Programmadresse auf den Stack gelegt. Durch
RTS wird sie wieder dem Programmzähler zurückgegeben. Ein CALL kann genau wie JUMP
sowohl unbedingt als auch bedingt sein.
Beispiel:
IF MV CALL overflow;
...
overflow:
/* multiplier overflow*/
...
/* do something! */
RTS;
Man beachte den Unterschied zwischen JUMP und CALL. Bei JUMP handelt es sich um einen
einfachen Sprung zu einer anderen Stelle im Programm, wo das Programm weiter läuft. Mit
CALL soll nur gesprungen werden, wenn durch RTS auch ein Sprung zurück gemacht wird.
Es ist wichtig, dass alle Paare CALL-RTS zusammenpassen. Wird ein Sprung durch RTS ver-
langt, ohne dass zu der Subroutine mit CALL gesprungen wurde, folgt ein Programmfehler,
weil auf dem Stack die falsche (oder gar keine) Retour-Adresse vorhanden war.
Interrupts
Interrupts stellen eine sehr flexible und effiziente Lösung für das Handhaben von Ereignis-
sen dar und müssen von jedem Assembler-Programmierer beherrscht werden. Ein Interrupt
ist ein Ereignis, das einen Sprung zu einer vordefinierten Adresse auslöst. Das Ereignis kann
entweder intern im Prozessor auftreten (z. B. ein ALU-Overflow) oder extern (z. B. wenn ein
neuer Abtastwert vorliegt). Der ADSP-21369 hat vier externe Hardware-Interrupts, inklusi-
ve eines speziellen Reset-Interrupts, und eine Vielzahl interner Interrupts, siehe Abbildung
1.11.
Für die Programmsteuerung bei Interrupts gibt es die Interruptvektortabelle, vergleiche den
beispielhaften Programmablauf in Abbildung 1.12.
Beim Auslösen eines Interrupts wird zu der dem Interrupttyp entsprechenden Adresse ge-
sprungen (Schritt 1). Im allgemeinen enthält diese Adresse den Befehl JUMP, so dass das
21
Interrupt Vector Table ADSP-21369
Emulator (read-only, HIGH PRIORITY
0x90000 + 0x00 0 EMUI
non-maskable)
Reset (read-only
0x04 1 RSTI
non-maskable)
Illegal input condition
0x08 2 IICDI
detected
Status loop or mode
0x0C 3 SOVFI stack overflow; or PC
stack full
0x10 4 Timer=0 (high
TMZHI
priority option)
0x14 5 SPERRI SP error interrupt
Hardware breakpoint
0x18 6 BKPI
interrupt
0x1C 7 Reserved
0x20 8 IRQ2I IRQ2I asserted
0x24 9 IRQ1I IRQ1I asserted
0x28 10 IRQ0I IRQ0I asserted
... ... ... ...
User Software
0x98 28 SFT0I
Interrupt 0
User Software
0x9C 29 SFT1I
Interrupt 1
User Software
0xA0 30 SFT2I
Interrupt 2
User Software
0xA4 31 SFT3I LOW PRIORITY
Interrupt 3
Abbildung 1.11: Inhalt der Interrupt-Vector-Tabelle
Programm mit einer Interrupt-Subroutine fortfahren kann (Schritt 2). Am Ende der Interrupt-
Subroutine wird mit RTI (Return From Interrupt) zu der Adresse im Programm zurückge-
sprungen, die der Programmzähler enthielt, als der Interrupt ausgelöst wurde (Schritt 3).
Bei einem externen Interrupt wird zuerst der Inhalt des ASTAT-Registers und des MODE1-
Registers sowie die Adresse des Programmzählers (PC) vor dem Sprung auf den Stack ge-
legt. Durch RTI werden diese Registerwerte vom Stack zurückgenommen. Für externe Inter-
rupts und für den Timer-Interrupt ist eine Verschachtelungstiefe (nesting) von vier möglich.
Folgendes Beispiel erläutert die Verwendung von Timer-Interrupts:
Beispiel:
1. Es soll ein Timer-Interrupt jede 1ms ausgelöst werden. 1ms entspricht
6000010 = EA6016 Zyklen bei 60 MHz, vergleiche Abschnitt 11
des Handbuches (zu beachten: Der Prozessor verfügt über zwei völlig
gleichwertige Timer-Sektionen 0 und 1)
/* timer interrupt address (high Prio timer IRQ)*/
.SEGMENT/PM pm_tmzhi;
JUMP timer_interrupt;
.ENDSEG;
...
22
Abbildung 1.12: Programmablauf beim Auslösen des Interrupts IRQ1
/* global interrupt disable*/
BIT CLR MODE1 IRPTEN;
/* Timer disable and setup first*/
BIT CLR MODE2 TIMEN0; /* stop timer */
BIT SET MODE2 PWMOUT0; /* set PWMOUT modus */
TPERIOD0 = 0xEA5F; /* 60000 - 1 */
/* timer interrupt enable*/
BIT SET IMASK TMZHI;
/* start timer (set counter to 0)*/
BIT SET MODE2 TIMEN0;
/* global interrupt enable*/
BIT SET MODE1 IRPTEN;
...
timer_interrupt: ...
/* Process timer-interrupt service*/
RTI;
Beim Start oder Reset eines Programms startet der Prozessor immer bei der Adresse des
Reset-Interrupts. Diese ist für den ADSP-21369 gleich 0x90005. Dementsprechend muss
an dieser Speicherstelle ein Sprung zum Programmanfang vorhanden sein. Dies kann durch
den folgenden Programmcode erreicht werden.
23
Beispiel:
/* reset interrupt address */
.SEGMENT/PM pm_rti;
JUMP prog_start;
.ENDSEG;
...
/* the program begins here */
prog_start: ...
Idle
Mit dem Befehl IDLE wird der Prozessor angehalten bis ein externer Interrupt oder ein
Timer-Interrupt auftritt. Im Gegensatz zu einer endlosen Warteschleife, die mit dem Be-
fehl JUMP implementiert werden kann, wird mit IDLE der Prozessor in einen „low-power“-
Zustand versetzt. Nach dem Rücksprung aus der Interrupt-Subroutine wird die Programma-
barbeitung mit dem Befehl fortgesetzt, der IDLE folgt.
1.2.5 Datenadressierung
Für die Berechnung von Adressen im Programm- und Datenspeicher stehen zwei Daten-
adressrechner (data address generators, DAG) zur Verfügung. Mit diesen wird eine indirekte
Adressierung ermöglicht, d.h. anstatt die tatsächliche Speicherstelle im Programm anzuge-
ben, wird sie durch den DAG berechnet.
In jedem der zwei DAG gibt es acht Adressregister, die I-Register oder Index-Register ge-
nannt werden. Der DAG1, der Adressen für den Datenspeicher (DM) (siehe Abschnitt 4.2.6)
berechnet, enthält die Register I0 bis I7, während der DAG2, der Adressen für den Pro-
grammspeicher (PM) berechnet, über die Register I8 bis I15 verfügt.
Die Zuordnung der Adressierungs-Register zu Programm-/Datenspeicher ist durch die Tat-
sache bedingt, dass der Programm-Speicher Adressbus nur 24 Bit breit ist. Somit sind auch
alle Register der zweiten Adressierungslogik nur 24 Bit breit.
Mit den zugehörigen Modify-Registern (M0-M7 für DAG1 bzw. M8-M15 für DAG2) kann eine
Adresse vor oder nach dem Speicherzugriff hoch- oder runtergezählt (inkrementiert bzw.
dekrementiert) werden. Hierdurch wird u.a. eine laufende Abspeicherung oder ein laufen-
des Auslesen von Daten ermöglicht, ohne dass dem Index-Register manuell ein neuer Wert
gegeben werden muss. Die Adressrechner werden mit den Befehlen DM bzw. PM angespro-
chen. Diese Befehle haben zwei Argumente: ein I-Register bzw. ein M-Register oder einen
konstanten Modify-Wert. Bei post-modify Adressierung erfolgt erst der Speicherzugriff und
dann die Berechnung des neuen Inhalts vom I-Register. Die Syntax lautet dann DM (Ia,
Mb) oder PM (Ic, Md), wobei a und b Zahlen zwischen 0 und 7 und c bzw. d Zahlen zwi-
schen 8 und 15 sind. Bei pre-modify wird die neue Adresse berechnet bevor der Zugriff
erfolgt. Hierfür wird die Syntax DM(Ma, Ib) oder PM(Mc, Id) verwendet.
24
Beispiel:
1. Lade in R6 den Inhalt der Speicherstelle 0x0000C000 des Datenspei-
chers.
R6 = DM(0x0000C000);
oder
I0 = 0x0000C000;
R6 = DM (I0, 0);
2. Lade in R6 den Inhalt der DM-Speicherstelle, die in I5 vorgegeben ist,
und zähle danach die Adresse in I5 um 2 hoch
R6 = DM (I5, 2);
3. Addiere die Adresse in I8 mit dem Wert in M10, und speichere danach
den Inhalt von R0 an der neu berechneten PM-Adresse (M10 kann negativ
sein)
PM (M10, I8) = R0;
Beispiel:
1. Berechne die Summe N der Elemente in den Speicherstellen DM(I0)
bis DM(I0+N-1), für N=10.
#define N 10 /* number of elements */
F0=DM (I0,1);
F1=DM(I0,1);
LCNTR = N-1, DO endloop UNTIL LCE;
/* result in F0 */
endloop: F0 = F0 +F1, F1 = DM (I0, 1);
Ablauf der Schleife: in DM(I0) bis DM(I0+N-1) sind die Zahlen
a0 ... aN−1 vorhanden.
Iteration LCNTR F0 F1
Initialisierung N-1 a0 a1
i=1 N-2 a0 +a1 a2
...
N−2
i = N-2 1 m=0 ak aN−1
N−1
i = N-1 0 m=0 ak undefiniert
Zyklische Daten-Puffer
Zyklische Daten-Puffer werden für effiziente Implementierungen von Filtern, Verzögerungs-
gliedern und anderen, in der digitalen Signalverarbeitung häufig vorkommenden Struktu-
ren eingesetzt. Die Implementierung zyklischer Daten-Puffer, sog. Ringspeicher (circular
buffer), wird durch Modulo-Adressierung erheblich erleichtert. Hierfür werden die base-
Register (B) und die length-Register (L) verwendet. Ein B-Register enthält dabei die Basi-
sadresse eines Ringspeichers und L die Anzahl der Elemente (die Länge). Das Register L0
und das Register M0 gehören immer zusammen, usw..
25
Abbildung 1.13: Beispiel eines zyklischen Puffers
Nach Initialisierung der B und L-Register wird zuerst die Basisadresse dem entsprechenden
I-Register automatisch übergeben. Bei einer post-modify Modifizierung des I-Registers (ei-
ne pre-modify Modifizierung ist bei zyklischen Puffern nicht erlaubt) wird kontrolliert, ob
die neue Adresse innerhalb des Bereichs zwischen B und B+L-1 liegt. Wenn dies der Fall
ist, wird die neue Adresse behalten. Wenn sie zu groß ist, wird die Länge L subtrahiert, und
dementsprechend wird L addiert, wenn sie kleiner als B ist. Dadurch ergibt sich immer die
neue Adresse durch den Zusammenhang
Ia := Ba + (Ia + Mb − Ba) mod La (1.7)
a und b können verschiedene Zahlen aus den Bereichen 0-7 oder 8-15 sein (DM bzw PM).
Es ist notwendig, dass |Mb| < La. Wie die Register der ALU stehen auch die Register
der Adressierungslogik doppelt zur Verfügung, um einen schnellen Kontext Wechsel zu
ermöglichen.
Beispiel:
1. Es soll ein Verzögerungsglied implementiert werden. Die Verzögerung
beträgt 20 Abtastwerte. Der Eingangswert ist beim Aufruf der Subrou-
tine delay_20 (wird mit call aufgerufen) in DM(I6) vorhanden und der
(verzögerte) Ausgangswert soll in DM(I7) geschrieben werden.
/* base address*/
B0 = 0x0C200;
/* length 2010 = 1416 */
L0 = 0x14;
...
/* read oldest data from buffer */
delay_20: F0 = DM(I0,0):
26
/* write output data */
DM(I7, 0) = F0;
/* read input data */
F0 = DM (I6, 0);
/* put data in circular buffer,
* address is increased by one*/
DM(I0, 1) = F0; RTS;
1.2.6 Speicher
Der Programm- und Datenspeicher
Der im Praktikum eingesetzte Prozessor ADSP-21369 hat 2 MB on-chip und 4 Mega Wort
externen Speicher, der grob in zwei Kategorien aufgeteilt wird: der Programmspeicher (PM
- Program Memory) und der Datenspeicher (DM - Data Memory). Die Wortbreite 32 Bit
wird für Fest- und Gleitkommadaten verwendet. Es können auch 48-Bit Wörter definiert
werden, die entweder Befehle oder 40-Bit Gleitkommadaten enthalten. Schließlich steht
auch ein 16-Bit Format für Festkommadaten zur Verfügung.
Die Aufteilung des gesamten Speichers in Segmente kann weiterhin für jede Anwendung
spezifisch bestimmt werden. Hierfür wird ein sogenanntes Link-Description-File (LDF)
angegeben, in dem Symbolen (Namen) physikalische Adressen zugeordnet werden.
• Im Adressraum sind die internen Speicherbereiche in Blöcke aufgeteilt, weiterhin
gibt es jeweils einen Normal-Word- und einen Short-Word-Bereich. Die vorgegebe-
nen Adress-Grenzen müssen dementsprechend auch im Link-Description-File einge-
halten werden, für eine genaue Zuordnung der Blöcke zu den Adressen sei auf das
Handbuch verwiesen.
• Der externe Speicher ist ebenfalls im Adressraum des ADSP 21369 vorgesehen, der
schliesslich verwendete Adressbereich ist schaltungstechnisch bedingt von Adresse
0x00200000 bis 0x08FF0000 anzusprechen, näheres hierzu kann der Beschreibung
der EZKIT-Hardware entnommen werden. Auf dieselbe Art und Weise sind auch die
Steuerungs- und Datenregister der anderen Peripherien (z. B. UART ADM3202) Teil
des Adressraums (siehe wiederum Manual zu EZKIT).
Im PM können sowohl Programmbefehle als auch Daten gespeichert werden. Im Datenspei-
cher (DM) können ausschließlich Daten gespeichert werden. In sowohl PM als auch DM kön-
nen auch Wörter anderer Breiten verwendet werden. In diesem Fall ändert sich die Adres-
sierung, siehe auch hierzu Kapitel „External Memory Interface“ im Handbuch.
Instruktionen müssen über den 32 Bit breiten Programmspeicher Adressbus angefordert
werden, weshalb diese sich nur in den entsprechenden Teilen des Speichers befinden kön-
nen.
Die PM/DM-Datenbusse
Über die PM und DM Datenbusse (PMD bzw. DMD ) werden die Befehle und Daten zwischen
dem Speicher und den verschiedenen Einheiten des Prozessors verschoben. Wie im PM so-
27
wohl Befehle als auch Daten gespeichert werden können, so kann der PM Datenbus Befehle
zum Programmrechenwerk oder auch Daten zu z. B. den Registern transportieren, jedoch
nicht gleichzeitig. Der parallele Datenbus ermöglicht zwei gleichzeitige Speicherzugriffe.
So kann im selben Zyklus sowohl ein neuer Befehl vom PM als auch ein Wert vom DM gelesen
werden. Hierbei wird die Adresse im PM vom Programmsteuerwerk über den PM Adressbus
(PMA) und die Adresse im DM über den DM Adressbus (DMA) vermittelt.
Alternativ können, wenn der auszuführende Befehl schon im Cache-Speicher vorhanden
ist, beide Datenbusse für den Transport von Daten verwendet werden. Vom PM und DM kann
dann jeweils ein Wert einem Register übergeben werden - alles innerhalb eines Zyklus’.
Dieses Verfahren wird dual data access genannt. Um dieses zu ermöglichen, empfiehlt es
sich immer, die zu verarbeitenden Daten zu trennen (z. B. Filterkoeffizienten im Programm-
speicher und Abtastwerte im Datenspeicher). Weiter muss der Befehl in der Form
compute, DM read or write, PM read or write;
sein, zum Beispiel
R0=R0+R1, DM(I1, M1)=R0, R1=PM(I8, M8);
Wenn der Befehl nicht im Cache-Speicher liegt, werden zusätzliche Zyklen benötigt.
1.3 Entwicklungsumgebung: Visual DSP++
1.3.1 Einführung
Im Laufe der letzten Jahre wurde die Hardware im Bereich Prozessoren immer weiter ent-
wickelt. Die heutigen Architekturen sind im allgemeinen sehr leistungsstark, und die Wahl
eines DSPs für einen Algorithmus wird somit auch von Faktoren jenseits der Performance
mitbestimmt. Zu diesen anderen Faktoren zählt insbesondere die zur Verfügung stehende
Software. Die „Time to market“, also der Zeitrahmen, dessen es bedarf, ein Produkt auf
den Markt zu bringen, kann durch guten Software-Support drastisch verkürzt werden. C-
Compiler gehören hierbei zum Standard.
Analog Devices liefert zu den Sharc-DSPs die Software Visual DSP++. In Anlehnung
an die Entwicklungsumgebung Visual Studio unter Windows können Algorithmen hiermit
schneller implementiert werden.
Im Folgenden soll eine kurze Einführung in die Umgebung Visual DSP++ gegeben werden:
Das Software-Entwicklungspaket von Analog Devices ist eine IDE (Integrated Develop-
ment Environment). Dazu gehören Tools zur Erstellung der Software (z. B. Editor) sowie
welche zur Verifikation des erstellten Software (z. B. Debugger).
Da die Release von Visual DSP++, die im Praktikum verwendet wird, regelmässig auf den
neusten Stand gebracht wird, können sich zu den im folgenden präsentierten Bildern unter
Umständen leichte Abweichungen ergeben.
28
Abbildung 1.14: Aufruf der Entwicklungsumgebung Visual DSP++
1.3.2 Implementierung des Source-Codes
Für die Entwicklung eines Algorithmus’ unter Visual DSP++ sowie unter allen Visual-
Umgebungen ist der Begriff Projekt von großer Bedeutung:
Ein Projekt umfasst die Gesamtheit aller zu der Implementation einer Anwendung gehö-
rigen Module. Ein Modul kann zum Beispiel eine Text-Datei sein, in der Code in einer
Programmiersprache gespeichert ist, oder eine vorkompilierte Bibliothek, auf deren Metho-
den zugegriffen wird. Die Module eines Projektes werden gegebenenfalls kompiliert und
gelinkt. Sie bilden somit die Basis für einen ausführbaren Algorithmus. Im einfachsten Fall
besteht ein Projekt aus einer Datei, die den gesamten Source-Code enthält.
Starten der Entwicklungsumgebung
Die Entwicklungsumgebung Visual DSP++ wird wie unter Windows üblich aus dem Start-
Menü gestartet (siehe Abbildung 1.14).
Die Bedienung gestaltet sich wie in den meisten Fällen sehr intuitiv. Sollte in einer vorher-
gehenden Sitzung ein Projekt bearbeitet worden sein, so erscheint dieses Projekt direkt zu
Beginn. Es können Unterfenster für das Projekt selbst (A), aber auch für Quellcode (B) bzw.
Prozessor-Zustände (z. B. Disassembly(C)) angezeigt werden (siehe Abbildung 1.15).
Im Projekt-Fenster wird angezeigt, welche Dateien alle Teil des Projekts sind. Sollte hier
noch kein Projekt angezeigt werden, so muss erst eins angelegt werden bzw. ein vorhande-
nes eingeladen werden.
29
A
C
B
Abbildung 1.15: Fenster nach Programmstart
Menüpunkte
In Abbildung 1.15 ist zu erkennen, welche Menüpunkte es bei Visual DSP++ gibt. Die
wichtigen Unterpunkte werden im folgenden kurz aufgelistet:
• Menüpunkt File:
In diesem Menüpunkt können Dateien geladen oder gespeichert werden. Dabei wer-
den Projekte und Textdateien im gleichen Dialog ausgewählt. Desweiteren kann auf
den DSP ein ganzes Programm bzw. in den Debugger die Symboltabelle eines Pro-
gramms geladen werden (Unterpunkte Load ...).
Für einen beschleunigten Zugriff stehen Links zu kürzlich geöffneten Projekten usw.
bereit (Unterpunkte Recent ...).
• Menüpunkt Edit:
In diesem Menüpunkt kann Text editiert werden. Dabei stehen die üblichen Opera-
tionen wie Copy/Paste zur Verfügung.
• Menüpunkt Session:
In diesem Menüpunkt kann die aktuelle Session gewählt werden. Im Falle des Prak-
tikums dient dies insbesondere der Wahl zwischen der Emulation mithilfe des JTAG-
Interfaces (V3+Projektphase) und der Simulation (V1+V2). Andere Sessions werden
im Rahmen dieses Praktikums nicht verwendet.
• Menüpunkt View:
In diesem Menüpunkt können Fenster angezeigt bzw. nicht angezeigt werden. Ins-
besondere kann wie in Abbildung 1.19 angedeutet (Plot) auch ein Speicherbereich
grafisch ausgegeben werden.
30
Abbildung 1.16: File-Dialog
• Menüpunkt Projekt:
In diesem Menüpunkt können Projekt spezifische Operationen vorgenommen wer-
den. Abbildung 1.20 veranschaulicht die Möglichkeiten, die wichtigsten Funktionen
im einzelnen:
– Add to Project:
Hiermit können einem Projekt Module hinzugefügt werden.
– Build File:
Hiermit können einzelne Module kompiliert werden.
– Build Project:
Hiermit werden alle Module eines Projekts kompiliert (sofern notwendig) und
zusammengelinkt.
– Rebuild all:
Hiermit werden sämtliche Projekte wie unter Menüpunkt Build Project bearbei-
tet, die momentan geöffnet sind.
– Project Options:
Compile- und Link-Options können hier spezifiziert werden.
• Menüpunkt Register:
Dieser Menüpunkt dient dem Debuggen eines gerade auf dem DSP ablaufenden Pro-
gramms. Es können hier Register des DSPs zur Anzeige ausgewählt werden. Für eine
Benutzung im Rahmen des Praktikums ist insbesondere die Anzeige der inaktiven
Register wichtig (Inactive Registers).
• Menüpunkt Memory:
In diesem Menüpunkt können Speicherbereiche angezeigt werden. Die Daten an den
31
Abbildung 1.17: Edit-Dialog
entsprechenden Speicherstellen können in verschiedenem Format dargestellt werden.
In den meisten Fällen ist eine Darstellung im Format Two Column sinnvoll. Mit dem
Befehl Dump können ganze Speicherbereiche auch direkt in einer Datei gespeichert
werden. Dies ist nützlich, um Rechenergebnisse zum Beispiel mit Matlab zu verifi-
zieren.
• Menüpunkt Debug:
Im Menüpunkt Debug kann der Debugger gestartet und kontrolliert werden.
• Menüpunkt Settings:
In diesem Menüpunkt können Einstellungen vorgenommen werden. Insbesondere
können hier Breakpoints aufgelistet und Interrupts simuliert werden (siehe V1).
Vorgehensweise bei der Erzeugung von Objekt-Codes (DSP Executable)
Im Laufe der Versuche dieses Praktikums und in der anschließenden Projektphase soll das
Rahmenprogramm erweitert und auf die Applikation angepasst werden. Hierzu sollen dem
Projekt Module hinzugefügt werden. Nachdem dies geschehen ist, werden alle Module er-
neut zusammengelinkt. Dabei werden eventuell aufgetretene Fehler im Ausgabefenster an-
gezeigt. Sofern die Anwendung komplett erzeugt wurde, wird das auszuführende Programm
direkt in den Programmspeicher des DSP geladen. Der Prozessor hält direkt an und erwartet
weitere Instruktionen.
Ausführen des Programms
Zur weiteren Ausführung des geladenen Sourcecodes wird F5 gedrückt. Alle Anzeige-
Fenster werden zu diesem Zeitpunkt deaktiviert. Sofern ein zuvor eingestellter Breakpoint
32
Abbildung 1.18: Session-Dialog
erreicht wird, hält die Ausfühung an, und alle Ausgabefenster werden aktualisiert. Mit
SHIFT-F5 kann der Prozessor auch zu jedem anderen Zeitpunkt angehalten werden.
Setzen von Breakpoints
Mit F9 kann an der Stelle der aktuellen Eingabeposition ein Breakpoint gesetzt werden.
Dies ist jedoch nur möglich, wenn der Prozessor gerade nicht im laufenden Zustand ist.
Ausführung Instruktion für Instruktion
Wenn der Prozessor angehalten wurde, sei es aufgrund von Breakpoints oder manuell, kann
mit F11 der Programmcode Schritt für Schritt abgearbeitet werden (sog. durchsteppen).
Zu bedenken ist jedoch, dass auch im angehaltenen Modus Interrupts ausgelöst werden
können. Aus diesem Grunde springt der Prozessor zumeist beim Durchgehen Instruktion
für Instruktion in die nächste Interrupt Service Routine höherer Ordnung.
Vorgehen beim Debuggen
Ein DSP-Object-Code wird wie beschrieben eingeladen. Daraufhin kann der Code gedebug-
ged werden. Zumeist wird nur ein Teilaspekt untersucht, der in einer Unterfunktion imple-
mentiert wurde. Um den zugehörigen Source-Code anzuzeigen, muss der Program-Counter
(PC) auf einer Stelle stehen, die zu der zu untersuchenden Funktion gehört.
Nach Programmstart muss hierfür der Disassembly-Output nach einem Symbol in der Funk-
tion ( z. B. Funktionsname ) durchsucht werden. Dies geschieht durch Betätigung der rech-
ten Maustaste sowie Aufruf der Goto-Funktionalität. Die Aktivierung von Browse führt zu
33
Abbildung 1.19: View-Dialog
einer Auflistung aller existierenden Symbole. Nach Auffinden des Symbols wird ein Break-
point gesetzt. Nun wird der Programmcode bis zum Breakpoint ausgeführt (Run, F5).
1.4 Versuchsdurchführung
1.4.1 Einführung
Die nun folgenden Programmbeispiele sollen schrittweise in die Programmierung des ADSP-
21369 einführen. Beachten Sie bitte, dass die ersten zwei Programme im Simulator-Modus
ausgeführt werden und erst das dritte Beispiel auf dem DSP emuliert wird. Ziel der hier
durchgeführten ersten beiden Versuche ist das Erlernen und Verstehen von Prinzipien Hard-
ware-naher Programmierung and der Funktionsweise des DSPs auf Assembler-Programm-
Ebene. Der dritte Versuch dient der Einarbeitung in die vorgegebene Software RTProcDSP.
Da dieses Programm in C/C++ programmiert wurde, geschieht von diesem Zeitpunkt an
auch die Programmierung der Algorithmen in C/C++. Es sei jedoch darauf verwiesen, das
es im Verlauf des Projekts notwendig sein kann, Fragmente der Programme in Assembler
zu realisieren, um die Ausführungsgeschwindigkeit des DSPs zu erhöhen. Für die Spezifi-
kation der Schnittstelle zwischen C/C++ und Assembler sei auf die Anleitung in der DSP
Literatur „C/C++ Compiler and Library Manual for Sharc Processors“ verwiesen.
• Im Programm 1 werden Daten interruptgesteuert ein- und ausgelesen. Die von ei-
ner Datei in einen Ringspeicher eingelesenen Werte können in einer Interruptroutine
modifiziert werden und anschließend in einen Speicherbereich ausgegeben werden.
Hierbei behandeln wir auch den Aufbau von Ringspeichern mit Hilfe der Modulo-
Adressierung.
34
Abbildung 1.20: Project-Dialog
• Das Programm 2 zeigt die Anwendung von Befehlen für die parallele Verarbeitung
von Multiplikations- und Additionsbefehlen anhand eines Tiefpass-Filters.
• Zum Schluss wird das Programm RTProcDSP vorgestellt. Da es im folgenden die
Grundlage zur Umsetzung der Projekte bildet, werden alle funktionalen Merkmale
an kleinen Übungsbeispielen verdeutlicht.
1.4.2 Programm 1: Interruptgesteuerte Ein-/Ausgabe
Der Sinn dieses Programms besteht darin, einen schnellen Einstieg in die Assemblerpro-
grammierung zu geben, insbesondere sollen zyklische Datenspeicher und die Programm-
steuerung durch Interrupts dargestellt werden.
Unter einem Interrupt (engl. für Unterbrechung) versteht man eine von dem Prozessor ge-
steuerte Unterbrechung des sequentiellen Programmablaufs. Stellen wir uns den sequenti-
ellen Programmablauf so vor, dass ein Ablaufzeiger (oder auch Program Counter) nachein-
ander die Speicherstellen anzeigt, die das Programm abarbeiten soll, dann ist ein Interrupt
eine Unterbrechung, in der der Zeiger auf eine vorher vereinbarte Speicherstelle „umge-
bogen“ wird. An dieser Stelle kann dann auch ein Verweis auf ein Unterprogramm stehen
oder direkt ein kurzes Unterprogramm vorhanden sein, das bearbeitet wird. Nach dem Ende
dieses Unterprogrammes wird der Programmcode wieder an alter Stelle aufgenommen und
weiter sequentiell bearbeitet.
Starten Sie das VisualDSP++ Environment und wählen Sie zunächst eine Session zur Pro-
zessor-Simulation aus (sollte so eine Session auf Ihrem Rechner noch nicht vorhanden sein,
kontaktieren Sie den Betreuer). Laden Sie anschliessend das Projekt V1. Um das Programm
auf dem DSP auszuführen, müssen Sie zunächst alle Dateien kompilieren und linken (Pro-
35
Abbildung 1.21: Register-Dialog
ject → Build Project oder F7) und dann die Programmausführung beginnen (Debug → Run
oder F5). Die Ausführung des Programms bleibt nun zu Beginn der _main-Routine an ei-
nem Breakpoint stehen. Von hier aus können alle Schritte des Programms durch F11 einzeln
ausgeführt werden.
Programmcode von Projekt 1
Das vorliegende Programmbeispiel V1.asm aus dem Projekt V1 liest interruptgesteuert einen
Wert aus dem mit input angegebenen Speicherbereich in ein Register. Anschliessend wird
dieser Wert an den mit output bezeichneten Speicherplatz weitergegeben. Es ist als ein Pro-
grammierbeispiel anzusehen und soll später gemäß der Aufgabenstellung modifiziert wer-
den. Es ist sinnvoll, den Programmcode parallel zu dieser Beschreibung zu lesen.
Der physikalische Speicherplatz wird durch ein sogenanntes Link-Description-File (LDF)
in logische Segmente aufgeteilt. Durch den Linker werden die Symbole (z. B. Namen von
Variablen) im Programmcode mit den physikalischen Speicheradressen verbunden, wobei
das LDF dafür die Regeln spezifiziert (in „Linker & Utilities Manual for ADSP-21xxx
Family DSPs“ wird dieser Vorgang illustriert). Den verschiedenen Segmenten werden wei-
terhin unterschiedliche Bedeutungen als Program- oder Datamemory zugewiesen. Zur Ver-
anschaulichung hier nun der Auszug aus dem verwendeten Link-Description-File V1.ldf:
36
Abbildung 1.22: Debug-Dialog
ARCHITECTURE(ADSP-21369)
MEMORY
{
seg_rth { TYPE(PM RAM) START(0x00090000) END(0x000900ff) WIDTH(48) }
seg_pmco { TYPE(PM RAM) START(0x000902D0) END(0x000903FF) WIDTH(48) }
seg_pm64 { TYPE(PM RAM) START(0x0004c6c0) END(0x0004dfff) WIDTH(64) }
seg_dm64 { TYPE(DM RAM) START(0x0005d800) END(0x0005d81f) WIDTH(64) }
seg_pm40 { TYPE(PM RAM) START(0x00090400) END(0x000904ff) WIDTH(48) }
seg_dm40 { TYPE(DM RAM) START(0x000b0000) END(0x000b01ff) WIDTH(48) }
seg_pm32 { TYPE(PM RAM) START(0x00098c00) END(0x00098cff) WIDTH(32)}
seg_dm32 { TYPE(DM RAM) START(0x000b8400) END(0x000bafff) WIDTH(32) }
}
Das LDF wird bearbeitet, indem mit der rechten Maustaste auf die Datei geklickt wird, um
dort dann die Option Open with Source Window zu wählen. Die symbolischen Namen der
Segmente im LDF, zum Beispiel seg_dm32, werden im Quellcode von V1.asm wiederver-
wendet, um dort Variablen einem bestimmten Speicherbereich zuzuordnen.
Eine besondere Rolle spielt das Segment seg_rth, in dem der Programmcode des Interrupt-
Vector-Tables abgelegt wird. Zugehörig findet man das Assembler-Programm in der Datei
21369_IVT.asm. Im folgenden soll nicht weiter auf das Link-Description-File eingegangen
werden, da dieses in der Regel unverändert übernommen werden kann.
Die beiden Assembler Programmcode-Dateien 21369_IVT.asm und V1.asm werden nun
einzeln erläutert. In der Datei 21369_IVT.asm werden die Instruktionen im Interrupt-Vector-
Table spezifiziert. Der Programmcode ist eine Eins-zu-Eins-Abbildung der Tabelle in Ab-
bildung 1.11. Insbesondere wird hier das auszuführende Programm angesprungen, sobald
ein Reset-Interrupt ausgelöst wurde (Zeile 15). Um eine Interrupt gesteuerte Eingabe zu
ermöglichen, ist weiterhin eine jump-Instruktion an der Position von IRQ0, Zeile 69, einge-
37
Abbildung 1.23: Settings-Dialog
fügt. Diese dient dem Aufruf der Funktion _convolution bei jedem Eintritt der Interrupt-
Ereignisses.
In der Datei V1.asm wird der tatsächliche Programmcode der _main- Funktion sowie der
Interrupt-Service-Routine _convolution aufgelistet. Bei Programmausführung dient die
_main-Funktion der Initialisierung. In dieser Funktion werden die Adress-Generatoren so
eingestellt, dass anschliessend die Einlese-Operation erfolgreich verläuft. Am Ende der
_main-Routine versetzt sich der Prozessor in den idle-Modus, um Energy zu sparen (Zeile
53).
Aufgaben
• Öffnen Sie die Datei V1.ldf im Source Window. Lokalisieren Sie die Speicher-
Segmente und identifizieren sie die verwendeten Segmente in den beiden Programm-
codedateien. An welcher Adresse liegt die Variable output?
• Starten Sie die Programmausführung. Diese hält am ersten Breakpoint sofort wieder
an. Das Programm und alle Variablen wurden zu diesem Zeitpunkt bereits in den
Speicher geladen. Öffnen Sie die folgenden Fenster zur Betrachtung
– des Disassembly-Outputs und des zugehörigen Programmcodes.
– des relevanten Adress-Generators (DAG).
– des Datenspeichers
– der Interrupt-Register.
Plotten Sie das Signal, das im Speicher zur Variable input gespeichert ist. Wählen
Sie dafür die Option View → Debug Windows → Plot → New. Wählen sie den dar-
zustellenden Speicherbereich mithilfe der Browse-Funktionalität aus. Beachten Sie,
38
dass sie das Zahlenformat (Float) und die Länge richtig einstellen. Verfizieren Sie
die Richtigkeit der Darstellung anhand der Zahlenwerte, die sie durch Darstellung
des Speichers (Memory → Two Column) ablesen können.
• Führen Sie das Programm nun Schritt für Schritt bis zum Erreichen der IDLE-Instruktion
aus (durchsteppen), indem Sie die Taste F11 bedienen. Beobachten Sie die Initia-
lisierung der Interruptregister und der für die Verwaltung des zirkulären Speichers
notwendigen Register. Was bedeuten die Kürzel A,D,P,F und der gelbe Pfeil?
• Nach der Initialisierung kann das Programm mit F5 (Run) ausgeführt werden. Da wir
uns im Simulations-Modus befinden kann ein auftretender externer Interrupt nur si-
muliert werden. Dies geschieht durch Auswahl von Settings → Interrupts . Wählen
Sie den Interrupt IRQ0 und eine sinnvolle Eistellung für die Interrupt-Periode, sowie
die verbleibenden Einstellmöglichkeiten. Was bedeuten die einzelnen Parameter? Be-
achten Sie, dass die Aktivierung des Interrupts durch Hinzufügen (ADD) geschieht.
• Bevor Sie nun die Ausführung mit F5 weiterlaufen lassen, muss ein Breakpoint an
der ensprechende Stelle im Interrupt Vector Table eingefügt werden. Überlegen Sie,
an welcher Stelle dieses sinnvoll ist
• Verändern Sie das Programm in der Interruptroutine, indem Sie den Befehl R0 = ABS
R0; benutzen, um den Betrag des Eingangssignals am Ausgang zu erhalten. Erklären
Sie das (unerwartete) Ergebnis.
• Für die Experten: In der Vorgabe ist ein Speicher-Zugriffsfehler. Können Sie diesen
identifizeren und wenn ja, wie ist er zu beheben?
Programmcode 21369_IVT.asm
// 21369 Interrupt Vector Table
//
.extern _main;
.extern _convolution;
.section/pm seg_rth;
__EMUI: // 0x00: Emulator interrupt (highest priority, read-only, non-maskable)
nop;
nop;
nop;
nop;
__RSTI: // 0x04: Reset (read-only, non-maskable)
nop; // <-- (this line is not executed)
jump _main;
nop;
nop;
__IICDI: // 0x08: Illegal Input Condition Detected
jump (pc,0);
jump (pc,0);
jump (pc,0);
jump (pc,0);
__SOVFI: // 0x0C: Status loop or mode stack overflow or PC stack full
jump (pc,0);
jump (pc,0);
jump (pc,0);
39
jump (pc,0);
jump (pc,0);
jump (pc,0);
jump (pc,0);
Programmcode V1.asm
/*****************************************************************************
* V1.asm
*****************************************************************************/
#include <def21369.h>
.SECTION/PM seg_dm32;
#define SAMPLES 9
/* Input */
.VAR input[SAMPLES]= -4.,-3.,-2.,-1. ,0., 1.,2.,3.,4.;
/* Output */
.VAR output[SAMPLES]= 0.,0.,0.,0.,0.,0.,0.,0.,0.;
.ENDSEG;
.SECTION/PM seg_pmco;
/* Set the scope of the ’_main’ symbol to global. This makes the symbol
available for reference in object files that are linked to the current
one. Use ’.EXTERN’ to refer to a global symbol from another file.*/
.GLOBAL _main;
/* Declare the ’_main’ symbol. */
_main:
/* Set bit called IRPTEN to enable interrupts */
BIT SET MODE1 IRPTEN;
/* Set irq0i interrupt bit */
BIT SET IMASK IRQ0I;
/* set value for modify-register M0 */
M0=1;
/* set length of ringbuffer B0 */
L0=@input;
/* name buffer B0, I0 is set at the same time */
B0=input;
/* set length of ringbuffer B1 */
L1=@output;
/* name buffer B1, set I1 */
B1=output;
/* Switch to low power mode and expect interrupt */
end:
44
idle;
JUMP end;
/* Delimit the ’_main’ symbol so the linker knows which code is associated
with the symbol. */
._main.END:
.GLOBAL _convolution;
_convolution:
/* put the current value of buffer addressed by I0 to register
F0 and increase buffer with M0 */
R0=DM(I0,M0);
/* Here the modification for the input value can be placed:
Calculate the absolute value (ASM insn abs) */
/* put value from register F0 to buffer B1 and
increase with M0 */
DM(I1,M0)=R0;
_convolution.END:
RTI;
.ENDSEG;
1.4.3 Programm 2: Parallele Verarbeitung von Befehlen
In diesem Programm sollen die besonderen Vorzüge des DSP für eine schnelle Verarbeitung
von Signalen anhand eines Tiefpass-FIR-Filters dargestellt werden.
Das Programm realisiert eine Faltung:
m−1
y(n) = a(k)x(n − k), (1.8)
k=0
wobei a(k) die Filterkoeffizienten, x(n-k) die um k verzögerten Eingangswerte und y(n) die
berechneten Ausgangswerte sind.
Das FIR-Filter benutzt vordefinierte Eingangswerte, die in einem Puffer eingeladen sind,
und Koeffizienten, die in einer Datei abgelegt sind. Zeitlich zurückliegende Eingangswerte
werden in einem zyklischen Speicher als sogenannte States berücksichtigt.
Das Programm stellt ein Filter der Länge 23 dar, das auf 200 vordefinierte Werte angewendet
wird. Es wird durch einen Interrupt wiederholt aufgerufen.
Das zugehörige Projekt V2 ist im Verzeichnis V2 abgelegt. Zusätzlich zu den Quellcode-
Dateien werden Variablenfelder in Dateien angegeben:
• Eine Datei mit Filterkoeffizienten fircoefs.dat
• Eine Datei mit dem Eingabesignal rect.dat
Die Filterkoeffizienten werden bei Versuchstermin 4 (s. Kap. 4.8.4) wiederverwendet.
Ziel dieses Versuches ist, die Funktion des Programmes durch Debuggen zu verstehen. Da
der Algorithmus (vom prinzip her) bekannt ist, soll zunächst eine Referenzimplementierung
in Matlab erstellt werden, die das Verständnis des DSP Codes erleichtern soll.
45
Aufgaben
• Vorarbeit: Laden Sie die Datenfelder in den Dateien fircoefs.dat und rect.dat als FIR
Filterkoeffizienten and Eingangswerte respektive in Matlab ein. Zum Einladen dient
der load-Befehl. Realisieren Sie das FIR Filter als Matlab Funktion und verifizieren
Sie Ihre Version mit der filter Funktion. Realisieren Sie Ihre Funktion in der Form,
dass die Filterzustände als Parameter mit übergeben und wieder zurückgegeben wer-
den (analog zur filter-Funktion und der DSP Version).
• Öffnen Sie nun das Projekt V2. Verwenden Sie den Interrupt IRQ0 wie bei der ers-
ten Aufgabe und setzen Sie einen Breakpoint an der Stelle, von wo aus jeweils die
_filter-Routine angesprungen wird.
• Vollziehen Sie die Ausführung des Programms nach. Versuchen Sie, die Verarbei-
tung von Hand (auf Papier) auszuführen. Was bedeutet der Zusatz (dB) in Zeile 99?
Welche Variable im DSP Code entsprechen den Größen h(k), x(k) und x(k − N)?
Was genau passiert während der Interrupt-Service-Routine? Wozu dient die XOR-
Verknüpfung in Zeile 146? Was passiert, wenn statt der Register R8, R12 und R4
andere Register verwendet werden? Plotten Sie inbuf und outbuf, wenn die Verar-
beitung aller Eingangssamples beendet ist. Plotten Sie auch die Koeffizienten im Feld
coefs.
• Wie könnte man die FIR-Routine verkürzen, wenn eine Rechengenauigkeit von 32
bit integer ausreicht?
Programmcode V2
/********************************************************
*
* V2.ASM FIR filterprogram for ADSP21369
* Filename: V22.asm
*
* Description:
*
* The program shows how to effectivly perform a convolution
* with the multiple instructions feature of the ADSP-21369
*
* Registers affected:
*
* F0 F8 I0 I1
* F4 F12 I8 I2
*
* Used parameters:
* F0 = input sample x(n)
* R1 = number of taps in the filter minus 1
* B0 = address of the delay line buffer
* M0 = modify value for the delay line buffer
* L0 = length of the delay line buffer
* B8 = address of the coefficent buffer
* M8 = modify value of the coefficent buffer
* L8 = length of the coefficent buffer
*
********************************************************/
#define SAMPLE 200 /*number of input samples to be filtered*/
#define TAPS 23 /*length of filter*/
46
#include <def21369.h>
.SECTION/PM seg_pm32;
/*FIR coefficients stored in file */
.VAR coefs[TAPS]=quot;fircoefs.datquot;;
.ENDSEG;
.SEGMENT/DM seg_dm32;
/*buffer that holds the delay line*/
.VAR dline[TAPS];
/*input samples stored in file */
.VAR inbuf[SAMPLE] = quot;rect.datquot;;
/*buffer that contains output coefficients*/
.VAR outbuf[SAMPLE];
.ENDSEG;
.SECTION/PM seg_pmco;
/* Set the scope of the ’_main’ symbol to global. This makes the symbol
available for reference in object files that are linked to the current
one. Use ’.EXTERN’ to refer to a global symbol from another file.*/
.GLOBAL _main;
/* Declare the ’_main’ symbol. */
_main:
/*enable interrupts*/
BIT SET MODE1 IRPTEN;
/* enable circular buffering */
BIT SET MODE1 CBUFEN;
L0=TAPS;
/*delay line buffer pointer initialisation*/
B0=dline;
/*set modify-register M0=1*/
M0=1;
/*set modify-register M1=1*/
M1=1;
L1=@inbuf;
/*input buffer pointer initialisation*/
B1=inbuf;
L2=@outbuf;
/*output buffer pointer initialisation*/
B2=outbuf;
L8=TAPS;
47
/*coefficient buffer pointer initialisation*/
B8=coefs;
/*set modify-register M8=1*/
M8=1;
/*initialize delay line buffer to zero*/
CALL fir_init (DB);
/* Set coefficient to second filter coeff */
MODIFY(I8,M8);
R0=TAPS;
/*enable interrupt IRQ0I*/
BIT SET IMASK IRQ0I;
done:
/*wait for interrupt */
JUMP done;
fir_init:
LCNTR=R0, DO zero1 UNTIL LCE;
zero1:
/*initialize the delay line to 0*/
DM(I0,M0)=0;
R0=SAMPLE;
LCNTR=R0, DO zero2 UNTIL LCE;
zero2:
/*initialize the output buffer to 0*/
DM(I2,M1)=0;
RTS;
/* Delimit the ’_main’ symbol so the linker knows which code is associated
with the symbol. */
._main.END:
.GLOBAL _filter;
_filter:
/* Filter one input sample. */
CALL fir(DB);
/* At first setup the loop counter for circular buffer */
R1=TAPS-3;
/* Read in h(1) (the second filter coefficient) and x(k-1) */
R8=R8 XOR R8, F0=DM(I0,M0), F4=PM(I8,M8);
/* ======> Call fir subroutine */
/*result is stored in outbuf */
DM(I2,M1)=F0;
RTI;
48