0% fanden dieses Dokument nützlich (0 Abstimmungen)
8 Ansichten50 Seiten

DSP C6000: Architektur & Programmierung C/ASM

Dieses Dokument beschreibt die Architektur des DSP C6000, insbesondere des TMS320C6678. Es erläutert die Eigenschaften der DSP und deren Verwendung in Echtzeitsystemen. Das Dokument enthält auch Informationen zur Programmierung in C und Assembler für den DSP C6678.

Übersetzt von

ScribdTranslations
Copyright
© All Rights Reserved
Wir nehmen die Rechte an Inhalten ernst. Wenn Sie vermuten, dass dies Ihr Inhalt ist, beanspruchen Sie ihn hier.
Verfügbare Formate
Als PDF, TXT herunterladen oder online auf Scribd lesen
0% fanden dieses Dokument nützlich (0 Abstimmungen)
8 Ansichten50 Seiten

DSP C6000: Architektur & Programmierung C/ASM

Dieses Dokument beschreibt die Architektur des DSP C6000, insbesondere des TMS320C6678. Es erläutert die Eigenschaften der DSP und deren Verwendung in Echtzeitsystemen. Das Dokument enthält auch Informationen zur Programmierung in C und Assembler für den DSP C6678.

Übersetzt von

ScribdTranslations
Copyright
© All Rights Reserved
Wir nehmen die Rechte an Inhalten ernst. Wenn Sie vermuten, dass dies Ihr Inhalt ist, beanspruchen Sie ihn hier.
Verfügbare Formate
Als PDF, TXT herunterladen oder online auf Scribd lesen

DSP C6000 BAHTAT Mounir

DSP C6000
Architektur & Programmierung C/ASM

Verfasst von: Mounir BAHTAT

Type de cours :TP guidé

Kategorie des Kurses: Eingebettete Systeme & Echtzeit


Voraussetzungen : -

Aktualisiert am: 18-12-2012

Einfach Lernen
[Link]

0
DSP C6000 BAHTAT Mounir

Zusammenfassung

Teil 1 : Architektur & Eingebettete C-Programmierung


Kapitel 1: DSP und Architektur

Kapitel 2: Unser erstes Programm in eingebettetem C

Kapitel 3: Optimierungstechniken in C

Teil 2 : ASM C66x Programmierung


Kapitel 1: Unser erster Assemblersprachencode

Kapitel 2: Einen optimierten Assembliercode schreiben

1
DSP C6000 BAHTAT Mounir

Einführung
Eine zentrale Verarbeitungseinheit (CPU: Central Processing Unit) ist das Element, das die Rolle eines Gehirns in allem spielt.
System. Dieses Element ermöglicht jedoch nur die Durchführung grundlegender arithmetischer und logischer Operationen, sowie
dass E/A-Operationen [Eingabe/Ausgabe]. Seit 1970 hören die CPUs (genauer gesagt die Mikroprozessoren) nicht auf
sich zu optimieren. Der Echtzeitbedarf in Anwendungen, die mit der Signalverarbeitung zu tun haben, hat das Auftreten hervorgebracht
einer neuen Art von optimierten Prozessoren, die das Akronym DSP (Digital Signal Processor) tragen.

Die DSP und die eingebetteten Echtzeitsysteme

Ein eingebettetes System ist ein autonomes elektronisches [Hardware] und computerbasiertes [Software] System, das oft erforderlich ist,
Echtzeitanforderungen berücksichtigen. Die wesentlichen Merkmale, die normalerweise gefordert werden für
Ein eingebettetes System sind:

- Die Kosten müssen so niedrig wie möglich sein


- Möglichst niedriger Energieverbrauch, bedingt durch die Verwendung von Batterien [im Allgemeinen]
- Möglichst geringe Verkleinerung
- Leistung, die speziell für eine bestimmte Anwendung zugeschnitten ist [Rechenleistung, …]

Die Architektur eines eingebetteten Systems besteht hauptsächlich / gewöhnlich aus einem Verarbeitungselement (das
kann GPP [Allzweckprozessor] und/oder DSP [Digital Signalprozessor] und/oder SoC [System-on-Chip] basierend auf
FPGA/ASIC), von Speicher und Kommunikationsschnittstellen mit Peripheriegeräten [als Beispiel: Bildschirm
taktil, GPS, ...]. Der Softwareteil, der in Flash-Speichern vorhanden sein wird, um von einem ...
Mikroprozessor wird als "Firmware" bezeichnet. Im Folgenden ein Beispiel für ein eingebettetes System:

Gumstix Overo COM [Computer On Module], mit Wifi und Bluetooth

Teil 1 : Architektur & Embedded C Programmierung


Kapitel 1: DSP und Architektur
Die DSPs traten um 1978 auf, um den Echtzeiterfordernissen der Signalverarbeitungsalgorithmen gerecht zu werden.
haben sich hauptsächlich durch herkömmliche Prozessoren mit der MAC-Operation (Multiplikation & Akkumulation) ausgezeichnet
in einem einzigen Taktzyklus, während diese letzte Operation in Bezug auf Zyklen bei anderen Arten kostspielig ist.
Mikroprozessoren.

Die DSP verfügen auch über eine modifizierte Harvard-Architektur, die im Gegensatz zu den Von-Architekturen...
Neumann ermöglicht den gleichzeitigen Zugriff auf das Programm und die Daten über dedizierte Busse.

Die Verwendung dieses Prozessortyps in einem Signalverarbeitungsrahmen erfordert Umwandlungsinterfaces.


analog/digital (Abtastung mit einer bestimmten Frequenz), da der DSP die Daten nicht verarbeiten kann.
wie die folgende Abbildung zeigt:

2
DSP C6000 BAHTAT Mounir

ADC=[Analog-Decimal Converter] ; DAC=[Decimal-Analog Converter]

Texas Instruments (TI) occupe 70% du marché des DSP, laissant 30% aux autres concurrents (Motorola, Analog
Geräte, Lucent Technologies, …)

Während dieses geführten TP interessieren wir uns für einen DSP, der spezifisch für Texas Instruments ist. Diese DSPs werden in 3 Kategorien eingeteilt:

C2000; C5000 und C6000. Die Merkmale/Anwendungen jeder Familie sind unten aufgeführt:

Insbesondere werden wir uns mit den leistungsfähigen DSPs C6000 beschäftigen, die weiterhin in 2 Kategorien unterteilt sind: Gleitkomma
fest und im Gleitkomma. Die Gleitkomma-DSP können Operationen mit Gleitkomma (reelle Zahlen) in 1 durchführen
Zyklus, während es mehrere Zyklen auf einem DSP mit festem Komma (spezifisch für ganze Zahlen) dauern sollte; Dies ist
aufgrund der Anwesenheit von Materialblöcken in der Architektur des DSP, die auf Gleitkommaoperationen spezialisiert sind,
Während für einen DSP mit festem Komma eine Fließkommaberechnung in eine Kombination mehrerer Operationen übersetzt wird.
Behebt [ganzzahlige] Fehler, was während der Ausführung zu einer erheblichen Erhöhung der Zyklen führt.

Die DSPs von TI der C6000-Familie, sortiert nach Leistung, sind unten aufgeführt:

3
DSP C6000 BAHTAT Mounir

DSP mit fester Komma

DSP mit Gleitkommazahl

4
DSP C6000 BAHTAT Mounir
Der DSP, der in diesem TP verwendet wird, ist der neueste TMS320C6678 mit festem und schwankendem Komma. Dieser DSP präsentiert sich
in Form eines integrierten Schaltkreises wird auf einer Entwicklungsplatine mit anderen Peripheriegeräten bereitgestellt
Erinnerungen, um seinen Test und die Nutzung zu erleichtern. Die Karte sieht folgendermaßen aus:

Hier findet man hauptsächlich den DSP, Schnittstellen für Hochgeschwindigkeitskommunikationsprotokolle (Ethernet,
HyperLink, AMC), mit 512 MB dynamischem Speicher, einem XSD100v1-Emulator, der die Rolle übernimmt, um
einen PC mit dem DSP zur Fehlersuche kommunizieren: Laden des Softwarecodes in die Speicher
zur Ausführung durch die DSP-Kerne, Pause/Start jedes einzelnen Kerns, direkter Zugriff auf den Speicher, ...

18 DIP-Schalter ermöglichen die statische Konfiguration der Karte (Uhr, PCIe-Protokoll, …).
émulateur externe à haute vitesse peut être connecté via 60-pin afin d’effectuerdes opérations de débogage rapide.
Der AMCd-Anschluss im obigen Bild transportiert Hochgeschwindigkeits-Kommunikationsprotokolle wie
RapidIO oder PCIe (Datenraten bis zu 5 Gbps [Gigabit pro Sekunde]). Schließlich gibt es Druckknöpfe für "Reset".
ebenfalls verfügbar, der Ausdruck "warm reset" beschreibt einen Reset, ohne die Geräte auszuschalten.
Komponenten.

Die Werkseinstellung [Standard] der DIP-Schalter ist wie folgt:

Diese anfängliche statische Konfiguration wählt aus:

- Ein Betrieb im 'Little Endian'; der Unterschied zwischen 'Little Endian' und 'Big Endian' liegt auf der Ebene von
Die Adressierung eines Bytes innerhalb eines Wortes (32-Bit) wird in der folgenden Abbildung dargestellt:

5
DSP C6000 BAHTAT Mounir

- I2C-Boot-Mastermodus -> Lesen nach dem Einschalten, Programmcode aus einem Speicher
EEPROM mit 128Ko über das serielle I2C-Protokoll
- Modul PCIe deaktiviert

Die interne Architektur des DSP C6678 ist wie folgt:

Der DSP enthält 8 Kerne, die jeweils wie ein unabhängiger Prozessor fungieren können und bis zu ...
1,25 GHz. Mehrere Speicherstufen sind vorhanden:

- L1 lokal für jeden Kern, unterteilt in 2, L1D (für die Daten) und L1P (für das Programm) von Größe
32Ko chacun; es ist das Niveau, das dem Herzen am nächsten ist, was zu einem möglichst schnellen Zugang führt, ohne
Latenzen (Durchsatz von 16 Bytes pro Zyklus)

6
DSP C6000 BAHTAT Mounir
- L2-Cache lokal für jeden Kern mit einer Größe von 512 KB; der Zugriff auf diesen Speicher ist langsamer als in L1.

L1 oder L2 können auch im CACHE-Modus verwendet werden. Während dieses Modus speichern wir den Zugriff auf die entfernten Daten.
von externer dynamischer DDR3-Speicher beispielsweise) durch das vorausladende Speichern in L1 oder L2.

- MSM (Multicore Shared Memory) ist ein statischer SRAM-Speicher von 4 Mocommune für alle Kerne.
- Ein DDR3 (Double Data Rate) Hauptspeichercontroller kümmert sich um die verschiedenen Operationen von
Kontrolle für den externen DDR3-Speicher (periodische Auffrischung, LESEN, SCHREIBEN, ...); Der Zugriff auf diesen Typ
Der Speicher ist im Vergleich zu statischen Speicher relativ langsam.

Im Folgenden eine kurze Beschreibung der anderen Module der Architektur:

- Der Peripheriegerät 'Debug und Trace' kümmert sich um die Debugging-Operationen zwischen DSP und PC.
- Der "Boot ROM"-Speicher ist ein nichtflüchtiger Speicher mit einer Größe von 128 KB, der zum Speichern des Codes dient.
Programme, selbst nach dem Ausschalten der Peripheriegeräte des DSP
- Der Block 'Semaphore' schützt den gleichzeitigen Zugriff auf Geräte durch mehrere Master.
spricht 8 Kerne
- Der PLL (Phasenregelkreis) ermöglicht die Erzeugung von Signalen mit den gewünschten Frequenzen.
- L’EDMA (Enhanced Direct Memory Access) ermöglicht den Speichertransfer ohne Eingreifen der CPU, dies
bringt ein wichtiges Hauptmerkmal für Anwendungen, die den Zugriff auf eine große Menge von
Gedächtnis
- Der EMIF16-Bereich ist ein Controller für einen externen ROM-Speicher.
- Mehrere Hochgeschwindigkeitskommunikationsprotokolle sind ebenfalls auf dem DSP verfügbar: PCIe (5 Gbps),
RapidIO (5 Gbps), ETHERNET (1 Gbps), TSIP (32 Mbps), HyperLink (50 Gbps, zwischen 2 DSP C6678), I2C, UART
SPI
- Schließlich hat der "Multicore Navigator" die Aufgabe, den Transfer der Pakete aus den Schnittstellen zu optimieren.
Hochgeschwindigkeitskommunikation über den TeraNet-Bus sowie die Reduzierung der Latenzen bei Speicherübertragungen
zwischen Herzen

Jeder der C66x CorePacs wird im folgenden Schema dargestellt:

7
DSP C6000 BAHTAT Mounir
Man findet nach Bank:

- 32 Register von 32-Bit mit Namen A0 -> A31 / B0 -> B31


- 4 Einheiten .L / .S / .M / .D
o.L: Arithmetisch-logische Einheit, die in der Lage ist, arithmetische Anweisungen auszuführen (Addition,
Subtraktionen) und logische (UND, ODER, …), jede der Einheiten ist in der Lage, maximal 2 zu machen
Additionen/Subtraktionen 32-Bit Fließkomma/ feste Werte pro Zyklus; beachten Sie, dass alle Anweisungen
agieren nur auf den 64 verfügbaren Registern pro Kern und akzeptieren keine verbundenen Operanden.
zur externen Erinnerung.
o.S : Einheit zum Branching und zur Versatzbildung, die in der Lage ist, Branching-Operationen (Sprung) auszuführen
des Ausführung eines Programms an eine genau bestimmte Position] oder das Verschieben von Registern.
Diese Einheit ist auch in der Lage, Fließkomma-Additionen/ Subtraktionen auszuführen oder
fixe (maximal 2 Operationen pro Einheit)
o.M : Einheit zur Multiplikation, in der Lage, maximal 4 32-Bit Fließkomma-/Festkomma-Multiplikationen auszuführen
o.D : Lade- und Speichereinheit, die in der Lage ist, Daten auf 64-Bit zu laden/speichern

So ist das Herz in der Lage, 8 verschiedene Anweisungen parallel pro Zyklus auszuführen (eine Anweisung pro Einheit).

Das Herz ist ebenfalls in der Lage, 8 32-Bit-Float/Fix-Multiplikationen pro Zyklus auszuführen [unter Verwendung der beiden
Multiplikationseinheiten], und 8 Addition/ Subtraktionen 32-Bit Fließkomma/ Festkomma pro Zyklus [unter Ausnutzung der 4 Einheiten
.L1/.L2/.S1/.S2]

Dieser Multi-Core-DSP [mit einer 40nm-Technologie], angesichts seiner enormen Rechenleistung, erweist sich als der beste
Vorhandener Prozessor, der eine Echtzeitanforderung mit dem geringsten Energieverbrauch erfüllen kann.
Effekt gemäß den zertifizierten Benchmarks des BDTI übertrifft dieses DSP in der Leistung alle Plattformen.
industrielle Ähnlichkeiten :

Der TMS320 C6678 ist der erste DSP mit 10 GHz. Er hat eine Rechenleistung von 320 GMACS in Festkomma und 160
GFLOPS in einfacher Genauigkeit, 40 GFLOPS in doppelter Genauigkeit. Mit einer
Durchschnittlicher Verbrauch von nur 10W. Bietet somit eine maximale Effizienz von 4 GFLOPS/W [DP].

8
DSP C6000 BAHTAT Mounir
Die Architektur eines Kerns dieses DSP ist vom Typ VLIW (Very Long Instruction Word), der über mehrere Einheiten verfügt.
Funktionale Einheiten, die mehrere heterogene Anweisungen in 1 Taktzyklus ausführen können [ILP, Instruction Level
Parallelismus]. In unserem Fall geht es darum, 8 verschiedene Anweisungen gleichzeitig auszuführen, was dank ...
zu einem 256-Bit-Bus [der 8 Anweisungen von jeweils 32 Bit transportiert], wie in der Abbildung der Busse gezeigt
nächste :

Die Architektur des VLIW ist einfacher als die eines CISC/RISC, da die Parallelität der Instruktionen nicht spezifiziert ist.
in Material, aber der Software überlassen [Aufgabe des ASMoudu-Programmierers oder des C-Compilers]. Der Nachteil, dass
kann diese neue Art von Architektur die Größe des Quellcodes präsentieren, tatsächlich, im Falle, dass wir über
Weniger als 8 Anweisungen, die parallel ausgeführt werden müssen, müssen NOP-Anweisungen verwendet werden, um die Nicht-
Nutzung einiger funktionaler Einheiten; folglich muss Speicher für das Programm reserviert werden.
jede verwendete NOP-Anweisung. Wenn wir beispielsweise im Durchschnitt nur über 4 ausgeführte Anweisungen verfügten
Zyklen, dann wäre die Hälfte des Quellcodes NOPs! Eine neue Verbesserung im Vergleich zum Standard-VLIW wurde eingeführt.
implementiert von TI auf C66x mit dem Namen "VelociTI", wodurch das Phänomen erheblich reduziert wird
taille du code source :

Die folgende Abbildung veranschaulicht den Datenpfad des Busses B in einem C66x [Standardgröße eines Busses ist 64-Bit]:

9
DSP C6000 BAHTAT Mounir

Man kann sehen, dass die Einheiten .L und .S jeweils 2 Operanden von 64 Bit [src1/src2] und einen Ausgangsport haben können.
[dst] de 64-Bit ebenfalls. Die Einheit .M verfügt über einen Ausgangsport von 128-Bit [dst1/dst2].

2 Wege zwischen den 2 Bänken sind verfügbar [1Xet2X], genannt "Kreuzweg". Der Weg 1X transportiert 64-Bit-Daten.
Daten vom Bank B zu Bank A, während der Pfad 2X 64-Bit vom Bank A zu Bank B transportiert. Schließlich,
Schlussfolgerung, dass die Kommunikation zwischen den Banken auf 64-Bit-Daten in beide Richtungen pro Zyklus beschränkt ist.

Kapitel 2: Unser erstes Programm in Embedded C


Das Debugging-Tool, das es uns ermöglicht, unseren C- oder ASM-Code zu schreiben, seine Kompilierung sowie deren
Ladungen auf dem Ziel-DSP sind: Code Composer Studio v5.1 von Texas Instruments

10
DSP C6000 BAHTAT Mounir
Es handelt sich um ein leistungsstarkes und kostenloses Werkzeug, das in der Lage ist, Emulation durchzuführen (wenn man über eine Entwicklungsplatine verfügt)

für DSP) oder die Simulation (wenn man keinen DSP hat, liefert der Simulator praktisch ähnliche Ergebnisse wie der
realer Fall

Das Tool kann über den folgenden Link heruntergeladen werden.: [Link]
5.1.0 mit einer Größe von 1200 Mo)

Ein neues Projekt kann erstellt werden über: Datei -> Neu -> CCS-Projekt

Geben Sie einen Namen für Ihr Projekt an, der Projekttyp muss ausführbar sein (keine Funktionsbibliothek); da
Unser DSP von Interesse ist C6678, es muss die Familie C6000 angegeben werden, vom generischen Typ C66xx. Validieren, um zu erstellen.
neues leeres Projekt.

Dann muss eine Konfiguration des gewünschten Ziels (Target Configuration) erstellt werden, die es uns ermöglicht, auszuwählen
die genaue Referenz unserer DSP. Dazu klicken Sie auf "Neue Zielkonfigurationsdatei" im Fenster "Ziel
Konfigurationen » (machenSieAnsicht -> Zielkonfigurationen wenn wir das betreffende Fenster nicht sehen)

Eine Aufforderung fordert Sie auf, den Namen der Konfigurationsdatei .ccxml anzugeben. Nach der Bestätigung wählen Sie das Ziel aus.
gewünscht:

11
DSP C6000 BAHTAT Mounir
Im Falle der Emulation muss zunächst der Verbindungstyp auf "Texas Instruments XDS100v1" ausgewählt werden.
USB-Emulator«, dann TMS320C6678 auswählen. Im Fall der Simulation wählt man „C6678 Gerät Zyklus
Ungefähre Simulation, Little Endian »

Es muss danach die erstellte Konfiguration mit Ihrem CCS-Projekt verknüpft werden. Klicken Sie dazu mit der rechten Maustaste auf Ihre Konfigurationsdatei.

im Fenster „Zielkonfigurationen“ und dann Datei mit Projekt verknüpfen -> Name Ihres Projekts:

Sobald dies erledigt ist, wird die Datei hinzugefügt und unter den Dateien Ihres Projekts sichtbar sein.

Eine weitere wichtige Information, die dem Compiler bereitgestellt werden muss, ist auf welcher Speicherebene (L1, L2, MSM, DDR3,
ROM) man muss das Programm laden, das man schreibt. Diese Information ist in einer .cmd-Datei anzugeben, die diese
Minimale Struktur:

-Stapel Größe des Stacks in Bytes

-Halde Größe von dynamischen Zuordnungen in Bytes

MEMORY { } Liste der verfügbaren Speicher auf dem Ziel-DSP

SECTIONS { } Das Programm ist in Abschnitte unterteilt, die auf mehrere Speicher abgebildet werden können.

Die Liste der verfügbaren Speicher für TMS320C6678 ist wie folgt:

Mehrere Abschnitte sind vordefiniert für ein Programm, das in C geschrieben ist; einige wichtige Abschnitte sind
nachfolgend beschrieben:

12
DSP C6000 BAHTAT Mounir

Ein Beispiel für eine .cmd-Konfigurationsdatei ist wie folgt:

-stapel0x5000
-Heap0x5000

GEDÄCHTNIS
{
L2SRAM :o= 0x00800000 ,l= 0x00080000
}

SECTIONS
{
L2SRAM
.daten> L2SRAM
.cinit> L2SRAM
.const> L2SRAM
.cio> L2SRAM
.far> L2SRAM
.nahe> L2SRAM
.fardata> L2SRAM
.sysmem> L2SRAM
.stack> L2SRAM
}

Im Bereich MEMORY sind die verwendeten Speicher der Zieladresse aufgelistet, mit ihren Ursprungsadressen (o) und Größen (l).

Im Abschnitt SECTIONS wird jede der definierten Sektionen der ID einer Arbeit zugewiesen.

Die im Datei eingegebenen Werte sind im hexadezimalen Format. Wir speichern die Datei mit der Erweiterung .cmd und wir
Platziere im selben Verzeichnis unseres Projekts [das sich im Workspace befinden muss]

Schließlich schlagen wir vor, das folgende Testprogramm auszuführen:

#include<stdio.h>

voidmain(void) {
printf("c66x Test Ende\n");
}

FaitesProject -> Build Project, puisRun -> Debug

13
DSP C6000 BAHTAT Mounir
Das Ergebnis der Kompilierung ist die Erzeugung einer Datei [Link] (binäre Übersetzung eines Assemblercodes), die
wird anschließend in einen Speicher geladen.

Ein Fenster wird Sie auffordern, die Herzen auszuwählen, die Sie verwenden möchten. Nach der Bestätigung wird das Programm sich darum kümmern.

In den angegebenen Speichern müssen Sie in der Debug-Fenster alle Kerne sehen können, bereit für
exécuter le programme :

Nach der Ausführung auf einem Kern sollten Sie das Ergebnis der printf-Anweisung in der Konsole sehen.

[TMS320C66x_0] c66x Test beendet

Kapitel 3: Optimierungstechniken in C
In diesem Kapitel werden wir die gängigsten Techniken in C auf einem C66x-Prozessor durchgehen.
Essay, ein komplexer Algorithmus für die Matrizenmultiplikation als Anwendung zu implementieren und zu optimieren.

Le produit matriciel est un algorithme essentiel dans un nombre de blocs de traitement du signalainsi que d’autres
Anwendungen, die als Beispiele genannt werden: DFT (Diskrete Fourier-Transformation), BF (Beamforming), Inversion der
Matrizen, Lösung von linearen Systemen, Bestimmung von Determinanten, LQR (optimale Steuerung), ...

Man betrachtet quadratische Matrizen der Größe N, wobei jedes der Elemente der Matrizen eine komplexe Zahl ist.
(mit einem Realteil und einem Imaginärteil)

Das Matrixprodukt der Matrizen A und B ergibt eine quadratische Matrix C der Größe N, die folgende Bedingung erfüllt:

14
DSP C6000 BAHTAT Mounir

Zunächst brauchen wir einen Datentyp, der eine komplexe Zahl speichern kann (imaginärer Teil &
reelle Partei). Die einfachste Lösung besteht darin, unseren eigenen Typ zu erstellen, der durch eine Struktur mit zwei ...
champs : réel et imaginaire[NB : le typefloatest un type supportant la virgule flottante, codé sur 4 octets]:

typedef struct{
floatre;
floatim;
}komplex;

Die Matrizen werden dann durch zweidimensionale Arrays dargestellt, so:

#defineN 12

complexinpA[N][N];
complexinpB[N][N];
komplexausg[N][N];

Man wählt dann, dass das Element A(i,j) äquivalent zur Notation A[i][j] ist (i für die Zeile, j für die Spalte):

Es ist auch interessant zu wissen, wie die Daten im Speicher organisiert sind und zu wissen, in welcher
Die Adresse speichert jedes Element der Matrix. Man kann den Speicher als eine Liste von Adressen darstellen.
unidimensional, auf folgende Weise :

Es ist zu beachten, dass die Reihenfolge der Felder in der Struktur die Art und Weise beeinflusst, wie die Elemente platziert werden.
Gedächtnis. Im Allgemeinen kann die zweidimensionale Notation [i][j] in die eindimensionale Notation übersetzt werden.

15
DSP C6000 BAHTAT Mounir
[i*N+j]. Beachten Sie, dass inpA (die Identifikation unseres zweidimensionalen Arrays) in C nur eine Adresse ist, die zeigt auf
Das erste Element unseres Arrays, die Notation inpA+1 erhöht den Zeiger um eine Menge, die der Größe des
Typ der Daten, auf den inpA zeigt (in unserem Fall handelt es sich um den Typ complex, mit einer Größe von 8 Bytes)

Es wird vorgeschlagen, eine Matrixmultiplikationsfunktion mit dem folgenden Prototyp zu schreiben:

void produkt_matriziell(complexinpA[N][N], complexinpB[N][N], complexoutp[N][N], unsigned int n);

Wir werden dieser Funktion 3 Zeiger auf die 3 betroffenen Matrizen sowie die verwendete Größe zur Verfügung stellen.

Zwei Operationen sind notwendig, um ein Matrizenprodukt zu erstellen: komplexe Summe & komplexe Multiplikation:

On écrira alors les 2 fonctions C suivantes :

complexsomme(complexargA,complexargB) {
komplexe Ressourcen

[Link]=[Link]+[Link];
[Link]=[Link]+[Link];
Rückgaberes;
}

complexprodukt(complexargA, complexargB) {
komplexe Ressourcen

[Link]=[Link]*[Link]*[Link];
[Link]=[Link]*[Link]+[Link]*[Link];
Rückgabeergebnisse;

Durch Anwendung der bekannten Formel des Matrixprodukts wird die Funktion der komplexen Multiplikation geschrieben:

void produkt_matrik(complexinpA[N][N], complexinpB[N][N], complexoutp[N][N], unsigned int n) {


inti,j,k;
für(i=0;i<n;i++) {
für(j=0;j<n;j++) {
complexsum={0.0 , 0.0};
für(k=0;k<n;k++) {
sum=somme( sum , produit(inpA[i][k] , inpB[k][j]) );
}
outp[i][j]=sum;
}
}
}

Das Programm ist in 3 verschachtelten Schleifen geschrieben, die Iterationen. Der Aufruf dieser Funktion wird in der
funktion principalemainde der Weise folgend:

produit_matriciel(inpA,inpB,outp,N);

Um die Richtigkeit unserer Multiplikationsfunktion auf DSP zu testen, müssen die Matrizen A und B initialisiert werden.
schlagen Sie vor, es auf folgende Weise zu tun:

voidinputs_init(complexinpA[N][N],complexinpB[N][N]) {
inti,j;

16
DSP C6000 BAHTAT Mounir

für(i=0;i<N;i++) {
für(j=0;j<N;j++) {
inpA[i][j].re=1.0/(i+j+1);
inpA[i][j].im=1.0/(i+j+2);
inpB[i][j].re=2.0/(i+j+1);
inpB[i][j].im=2.0/(i+j+2);
}
}
}

Endlich eine Funktion zur Anzeige einer Matrix:

void affiche_matrice(complexmatr[N][N]) {
inti,j;
für(i=0;i<N;i++) {
für(j=0;j<N;j++) {
printf("%f+i%f\n",matr[i][j].re,matr[i][j].im);
}
}
}

Der Aufruf dieser Funktionen in main wird wie folgt erfolgen:

voidmain(void) {
inputs_init(inpA,inpB);
produit_matriciel(inpA,inpB,outp,N);
affiche_matrice(outp);
printf("c66x Testende\n");
}

Setzen Sie N auf 2, kompilieren Sie das Programm und führen Sie es aus, Sie sollten folgendes Ergebnis haben:

[TMS320C66x_0] 1.777778+i2.666667
[TMS320C66x_0] 0.833333+i1.638889
[TMS320C66x_0] 0.833333+i1.638889
[TMS320C66x_0] 0.375000+i1.000000
[TMS320C66x_0] c66x test end

Bei der Optimierung eines komplexen Algorithmus ist es interessant, die Genauigkeit und die Präzision der
Ergebnisse mit einem sogenannten Referenzmodell. Im Verlauf dieses Dokuments werden wir dann den Vergleich anstellen mit
Matlab, da die Mehrheit der Algorithmen, die wir benötigen, in seinen Bibliotheken existieren. Das Skript .m macht die
Das Gleiche wie unsere Implementierung ist das Folgende:

clc;

N=12;

inpA=zeros(N,N);
inpB=zeros(N,N);

füri=0:N-1
für j=0:N-1
inpA(i+1,j+1)=1.0/(i+j+1)+1i*1.0/(i+j+2);
inpB(i+1,j+1)=2.0/(i+j+1)+1i*2.0/(i+j+2);
Ende
Ende

outp=inpA*inpB

17
DSP C6000 BAHTAT Mounir
Ein Unterschied zwischen C und Matlab besteht darin, dass die Indizes von Arrays in C bei 0 beginnen, während
dass es in Matlab bei 1 beginnt.

Iterationen, jede der Iterationen macht: 1 komplexes Produkt + 1 komplexe Addition, was
Unser C-Code wird ausgeführt
entspricht: 4 reale Multiplikationen auf 32-Bit (Float) + 4 reale Additionen/Subtraktionen auf 32-Bit.

Die Anzahl der durch das Programm durchgeführten Akkumulationsmultiplikationen (MAC) ist dann: MAC

Es wird daran erinnert, dass der C66x-Prozessor in der Lage ist, 8 32-Bit Fließkomma/Ganzzahl-Multiplikationen pro Zyklus auszuführen [unter Verwendung der
zwei Multiplikationseinheiten und 8 Addition/Subtraktionen 32-Bit Fließkomma/Festpunkt pro Zyklus [unter Ausnutzung der 4 Einheiten
.L1/.L2/.S1/.S2]. Anders ausgedrückt, ein Kern ist in der Lage, 8 MAC pro Zyklus auszuführen.

Die Anzahl der Zyklen, die erforderlich sind, um ein komplexes Matrixprodukt auf einem C66x-Kern auszuführen, ist dann =

cycles = Zyklen

Für N=1000 kann das Matrixprodukt in 500.000.000 Zyklen von einem einzelnen Kern C66x ausgeführt werden, also ein
Zeit von 500 ms (bei einer Betriebsfrequenz von 1 GHz)

Anschließend beabsichtigen wir, die Ausführungszeit unseres Codes zu messen und zu sehen, wie weit wir davon entfernt sind.
Die erwartete ideale Leistung, dann definieren wir die Effizienz der Implementierung C durch:

Es wird erwartet, dass die Effizienz des C-Codes von der idealen abweicht, obwohl der Compiler von Texas
Instruments ist sehr leistungsfähig, in den meisten Fällen werden wir die maximale Effizienz nur erreichen können, indem wir codieren.
Unsere Routinen direkt in ASM.

Um die Anzahl der Zyklen einer Routine zu messen, wird das TSC-Modul (Time Stamp Counter) der Bibliothek verwendet.
CSL (Chip Support Library) bereitgestellt von Texas Instruments. TI schlägt zwei Zugriffsmöglichkeiten auf ein DSP-Gerät vor.
ab der Anwendungsschicht: Registerebene oder Funktionsebene:

Der Register-Level besteht darin, die konfigurierten und direkten Zugriff auf die in den Speicher gemappten Register zu haben, um
ein bestimmtes Peripheriegerät (wie EDMA, TIMER, …) in Betrieb nehmen. Während auf funktionaler Ebene die Komplexität der
Die Konfiguration registerweise ist verborgen durch die Verwendung der in Libraries wie bereitgestellten C-Funktionen.
CSL.

Der CSL existiert im MCSDK (Multicore Software Development Kit) und insbesondere im PDK (Programmiersatz).
Entwicklungskit), herunterladbar über den folgenden Link:

18
DSP C6000 BAHTAT Mounir
[Link] (für Windows, von Größe
900 Mo)

Nach der Installation im Standardverzeichnis können Sie die CSL-Bibliothek im Pfad sehen:

C:\Programme\Texas Instruments\pdk_C6678_1_0_0_9_beta2\Pakete\ti\csl

In der Datei csl_tsc.h finden Sie die Prototypen aller Funktionen des TSC-Moduls, kopieren Sie diese Datei zu
Verzeichnis Ihres Projekts. Sie finden die Definition der Funktionen des Moduls in einer Assemblierungsdatei unter:

C:\Program Files\Texas Instruments\pdk_C6678_1_0_0_9_beta2\packages\ti\csl\src\ip\tsc

Kopieren Sie die .asm-Datei ebenfalls in das Verzeichnis Ihres Projekts.

Um die Module der CSL-Bibliothek aus unserem C-Code einbinden zu können, gehen Sie zu Projekt -> Eigenschaften

Fügen Sie den folgenden Pfad aus dem Datei-System hinzu:

C:\Programme\Texas Instruments\pdk_C6678_1_0_0_9_beta2\Pakete

Fortan wird dieser Pfad bei der Suche nach .h-Dateien, die über die Direktive #include eingebunden sind, berücksichtigt.

Die Funktionen des Moduls TSC, die wir verwenden werden, sind die folgenden:

void_CSL_tscAktivieren(void);

CSL_Uint64_CSL_tscLesen(void);

Die 1. Funktion löst die Inkrementierung eines 64-Bit-Zählers bei jedem Zyklus aus, der sich unter den
Register des Herzens C66x, bezeichnet [ TSCL (32-Bit) und TSCH (32-Bit) ].

Die 2èmeFunktion liest den aktuellen Wert der Registerpaare TSCL und TSCH und gibt das Ergebnis als einen zurück
unsigned integer auf 64-Bit.

19
DSP C6000 BAHTAT Mounir
Die Verwendung dieser Funktionen zur Messung der Anzahl der Zyklen unserer Multiplikationsfunktion erfolgt wie folgt:

#include"csl_tsc.h"

voidmain(void) {
unsigned long long db, fn, mes;
_CSL_tscEnable();
inputs_init(inpA, inpB);
db=_CSL_tscRead();
produkt_matrize(inpA, inpB, outp, N);
fn=_CSL_tscRead();
mes=fn-db;
affiche_matrice(outp);
printf("Anzahl der Zyklen : %lld\n",mes);
printf("c66x Test Ende\n");
}

Da sich die .h-Datei im selben Verzeichnis Ihres Projekts befindet, verwenden wir die Syntax #include "." anstelle.
de#include <.>

Der Zähler wird zweimal abgelesen, bevor und nachdem die gewünschte Funktion aufgerufen wurde. Die Subtraktion der beiden abgelesenen Werte
muss erlauben, die Ausführungszeit in Bezug auf Zyklen zu haben.

Um unangenehme Warnungen bei der Kompilierung bezüglich des Aufrufs von Funktionen des TSC-Moduls zu vermeiden,
Ändern Sie die Datei csl_tsc.h, indem Sie den Prototypen von jeweils 2 Funktionen einen fehlenden Bindestrich in ihren Namen hinzufügen.
wie folgt :

extern void _CSL_tscEnable(void);

Nach dem Kompilieren und Ausführen sollten Sie ein ähnliches Ergebnis wie das folgende haben:

[TMS320C66x_0] 1.777778+i2.666667
[TMS320C66x_0] 0.833333+i1.638889
[TMS320C66x_0] 0.833333+i1.638889
[TMS320C66x_0] 0.375000+i1.000000
[TMS320C66x_0] Anzahl der Zyklen: 1768
[TMS320C66x_0] c66x-Test beendet

Beachten Sie: Es ist nicht notwendig, nach jeder Neuübersetzung des Projekts auf Ausführen -> Debuggen zu klicken.

Wir werden uns auch mit der Anzeige der Effizienz des Codes beschäftigen, der C-Code der Funktion main wird:

voidmain(void) {
unsigned long long db, fn, mes, parfait = N * N * N / 2;
_CSL_tscAktivieren();
inputs_init(inpA, inpB);
db=_CSL_tscRead();
produit_matriciel(inpA,inpB,outp,N);
fn=_CSL_tscRead();
mes=fn-db;
affiche_matrice(outp);
printf("Anzahl der Zyklen : %lld\n",mes);
printf("Effizienz : %lf %%\n", 100.0 * perfekt / (double) mes);
printf("c66x Test Ende\n");
}

Die Effizienz beträgt 0,280962 % für N=12 !! Die Effizienz unseres C-Codes ist sehr gering. Wir werden uns vorschlagen.
Danach unsere C-Implementierung zu optimieren, um eine bessere Übersetzung in Assembler-Code durch den Compiler zu erreichen.

20
DSP C6000 BAHTAT Mounir
Wir werden beginnen, die Optimierungsoptionen des Compilers zu aktivieren über: Projekt -> Eigenschaften; im Abschnitt
Basisoptionen, wählen Sie 3 für Optimierungsstufe:

Wählen Sie auch im Abschnitt Optimierungen 5 für Geschwindigkeit optimieren:

Für N=12 ergibt sich diesmal eine Effizienz von 0,362223 %. Eine leichte Steigerung ist zu bemerken.

In Wirklichkeit gibt es einen Assembly-Befehl DADDSP, der es ermöglicht, zwei 32-Bit-Summen gleichzeitig zu berechnen, was uns
Lass unsere Funktion, die wir geschrieben haben, um die Summe von 2 komplexen Zahlen zu bilden, sich daran erinnern (die 2 benötigt).
Ergänzungen 32-Bit ebenfalls). Glücklicherweise haben wir die Möglichkeit, diese Anweisung über C aufzurufen via sein
Schnittstelle namens eineinstruction intrinsèque. Der Prototyp dieser Funktion ist wie folgt:

21
DSP C6000 BAHTAT Mounir

__float2_t_daddsp (__float2_tsrc1,__float2_tsrc2);

Der Typ __float2_test, der auf 64-Bit codiert ist und mit dem Typ double kompatibel ist. Die durchgeführte Operation wird schematisch dargestellt auf der

nächstes Bild :

Eine weitere intrinsische Anweisung, die das gesamte komplexe Produkt übernimmt, steht ebenfalls zur Verfügung:

double_complex_mpysp (doublesrc1,doublesrc2);

In Argument liefert man zwei komplexe Zahlen auf 64-Bit [src1/src2] (32-Bit für den Realteil und 32-Bit für die
Imaginärer Teil), im Gegenzug erhalten wir das Ergebnis des komplexen Produkts. Dennoch sind die reellen und imaginären Teile
müssen in folgender Reihenfolge angeordnet werden [das Illusorische zuerst, dann das Reale an der folgenden Position]:

Um diese Funktion zu nutzen, muss die Reihenfolge, die wir in der Struktur festgelegt haben, in diese geändert werden:

typedef struct{
floatim;
schweben
}komplex;

Die bisher in unserer C-Implementierung verwendeten Zeiger sind vom Typ complex* (Zeiger auf ...
Daten eines Typs der als komplex definierten Struktur, die nicht mit einem Zeiger auf ein Doppel kompatibel ist.
(obwohl sie die gleiche komplexe Menge auf jeweils 64-Bit beschreiben können). Oder die intrinsischen Anweisungen, die man
möchte akzeptieren, dass ein Parameter vom Typ double und nicht weiter kompiliert werden kann
mit Parametern unseres Typs (aus der Strukturkomplex); eine Möglichkeit, dieses Problem zu beheben, besteht darin, zu ändern
der Typ der Zeiger im Prototyp unserer Matrixmultiplikationsfunktion zu:

void produkt_matriks(double* inpA, double* inpB, double* outp, unsigned int n)

Außerdem sind die Zeiger nicht mehr bidimensional, sondern eindimensional, die Formel zur Translation zu diesen
Das letzte ist bekannt und wurde zuvor zitiert, die Funktion der Matrixmultiplikation wird:

void produkt_matrikel(double* inpA, double* inpB, double* outp, unsigned int n) {


inti,j,k;

22
DSP C6000 BAHTAT Mounir

für(i=0;i<n;i++) {
für(j=0;j<n;j++) {
doublesum=0.0;
für(k=0;k<n;k++) {
sum=_daddsp( sum , _complex_mpysp (inpA[i*N+k] , inpB[k*N+j]) );
}
outp[i*N+j]=sum;
}
}
}

Auf der Ebene des Aufrufs der Funktion main ist ein Cast (Typänderung von Zeigern) erforderlich:

produit_matriciel((double*)inpA,(double*)inpB,(double*)outp,N);

Führen Sie für N=12 aus und kommentieren Sie die Anzeige-Funktion, erhält man eine Effizienz von: 28.973843 %

Eine deutlich wichtige Verbesserung ist dank der intrinsischen Anweisungen zu bemerken.

Un autre mécanisme important à prendre en compte est le cache. Trois mémoires pouvant être configurées comme
Cache auf einem Cœur C66x: L1P, L1D und L2. Der Cache hat die Aufgabe, die Daten eines Speichers im Voraus zu speichern.
Fern/external (Zugriffszeit) in einem Speichermodul, das näher am Kern liegt (zum Beispiel L1), um zu
die Speich latenzen minimieren. Das Prinzip der Lokalität wird angewendet, das besagt, dass wenn die CPU auf eine Daten zugreift,
Es ist also sehr wahrscheinlich, dass er versucht, auf die benachbarten Daten zuzugreifen, der Cache nutzt dies aus und lädt die Daten.
Nachbarn auf einem näheren Speicherniveau.

L1P, das im Cache aktiviert ist, wird sich um das Laden des auszuführenden Programmcodes kümmern. L1D und L2 können verwendet werden.
um die Daten zu "verstecken", auf die das Herz zugreift.

Wenn die Daten in DDR3 gespeichert sind, ist es vorteilhaft, den Cache für L2 und L1D zu aktivieren. Während wenn man positioniert
Unsere Daten im L2, nur L1D wird einen Gewinn im Cache bringen. Da wir in unserem Fall L2 verwendet haben, um
Alle Matrizen speichern, wir werden uns anschließend mit der Struktur des L1D-Caches beschäftigen:

Der L1D-Speicher mit einer Größe von 32 Ko kann vollständig im Cache aktiviert werden, man kann ihn als zusammengesetzt darstellen von
mehrere Zeilen, jede mit einer Größe von 64 Bytes:

Man nimmt dann an, dass das Herz versucht, auf einen relativ langsamen Speicher mit Zugriff (L2, MSM oder DDR3) zuzugreifen.
dass wir auch schematisch darstellen werden durch:

23
DSP C6000 BAHTAT Mounir

Wie dargestellt, kann man den Speicher sehen, auf den das Herz über den Cache zugreifen wird, unterteilt in Blöcke von der
Größe des Cache (in diesem Fall 32 KB). Jeder Block erhält eine Kennung, die als „Tag“ bezeichnet wird.

Wenn man annimmt, dass das Herz auf die allererste Adresse des Speichers zugreift, sucht der Cache-Mechanismus alles.
Zunächst, wenn die Daten bereits im Cache vorhanden sind, und wenn sie nicht vorhanden sind (dieser Fall wird als "Read Miss" bezeichnet), dann nicht nur die

donnée requise est transférée au cache, mais aussi toute la ligne voisine (de taille 64 octets) est transférée. Et afin
sich daran zu erinnern, von welchem Block dieser Transfer gemacht wurde, wird jede Zeile des Caches einer Nummer des Tags zugeordnet:

Diese beschriebene Funktionsweise wird als "Direkt abgebildeter Cache" bezeichnet, der jede Adresse mit einer eindeutigen Zeile im Cache verknüpft;
Die tatsächliche Struktur ist leicht unterschiedlich und optimiert, was weiter unten in diesem Dokument beschrieben wird. Der Cache ist
vollkommen transparent im Betrieb, das heißt, man muss sich nicht um die Vorgänge kümmern, die er ausführt
oder ihn zu bitten, ein bestimmtes Gedächtnis zu verbergen, geschieht alles automatisch nach seiner Aktivierung. Das Modul
Der CSL, der den Zugriff zur Aktivierung des Caches ermöglicht, enthält diese 3 Hauptfunktionen:

void CACHE_setL2Size(CACHE_L2Size newSize)

24
DSP C6000 BAHTAT Mounir

voidCACHE_setL1DSize(CACHE_L1SizenewSize)

voidCACHE_setL1PSize(CACHE_L1Size newSize)

Sie ermöglichen die Wahl einer neuen Größe für den Cache, die Typen CACHE_L2Size und CACHE_L1Size sind ...
Aufzählungen, die in csl_cache.h definiert sind, wie folgt:

typedefenum{
/** Kein Cache */
CACHE_L1_0KCACHE=0,
/** 4KB Cache */
CACHE_L1_4KCACHE=1,
/* 8KB Cache */
CACHE_L1_8KCACHE=2,
/** 16KB Cache */
CACHE_L1_16KCACHE=3,
/** 32KB Cache */
CACHE_L1_32KCACHE=4,
/** MAX Cache Größe */
CACHE_L1_MAXIM1=5,
/** MAX Cache Größe */
CACHE_L1_MAXIM2=6,
/** MAX Cache-Größe */
CACHE_L1_MAXIM3=7
}CACHE_L1Size;

Schließlich schreiben wir unsere Cache-Aktivierungsfunktion wie folgt:

#include<ti/csl/csl_cache.h>
#include<ti/csl/csl_cacheAux.h>

voidcache_init() {
CACHE_setL2Size(CACHE_0KCACHE);
CACHE_setL1DSize(CACHE_L1_32KCACHE);
CACHE_setL1PSize(CACHE_L1_32KCACHE);
}

Et ne pas oublier son appel dans la fonctionmaintout au début :

cache_init();

Nach der Ausführung bemerkt man keine Verbesserung, da der Cache standardmäßig aktiviert ist. Trotzdem ist es immer noch
ein wesentlicher Schritt, um sicherzustellen, dass die dem Cache zugewiesene Größe im Verhältnis zu der von
unser Programm (wie SRAM). Um die Auswirkung des Caches auf die Effizienz zu sehen, setzen wir eine Größe von 0K für L1D ein und
L1P, man erhält eine Effizienz von: 6,661527 % (eine Verringerung im Vergleich zum Wert mit aktivem Cache:
28,973843 %)

Diese Abnahme der Effizienz ohne Cache ist auf die zusätzlichen Speicherlatenzen zurückzuführen. Man kann schreiben:

Anzahl der gemessenen Zyklen=Anzahl der Zyklen CPU+Latenzen [Speicher]

Die Anzahl der CPU-Zyklen entspricht der Anzahl der Befehls-Pakete (bis zu maximal 8, da wir 8 Einheiten haben).
im Herzen) welches Herz C66x ausführt. Die Speicherlatenzen 'Memory Stalls' sind die Anzahl der Zyklen
währenddessen der Prozessor in den Standby-Modus versetzt wird, bis die Daten aus dem Speicher eintreffen.

Andere Begriffe werden verwendet, um die Funktionsweise des Caches zu beschreiben, darunter:

- L1D Zugriffsanzahl: Anzahl der Zugriffe auf den Speicher über L1D
- L1D Miss Count : Nombred’accès à unedonnéen’existant pas dans le cache

25
DSP C6000 BAHTAT Mounir
- L1D Trefferanzahl: Anzahl der Zugriffe auf vorhandene Daten im Cache

Man fragt sich dann, wie oft der Prozessor aufgrund des Zugriffs auf den Speicher pausiert wird.
Die Ausführung der Matrixproduktfunktion („L1D Miss“)?

Die Größe der gelesenen Daten (der 2 Matrizen A und B) ist komplexe Zahlen, sei Octette

Für N=12 haben wir 2304 Bytes zu lesen. Nach unserem Verständnis der Funktionsweise des Cache-Mechanismus, es
Es wird 2304/64=36 L1D Lese-Fehlzugriffe geben, das ist wahr, da 2304 Bytes < 32 KB des aktivierten Caches.

Das Werkzeug Code Composer Studio ermöglicht es uns auch, diese Art von Ereignissen zu messen (L1D Read Miss, L1D Hit,
…). Um dies zu tun, beginnen Sie damit, die zu messende Routine durch Breakpoints einzurahmen (Rechtsklick auf eine Zeile der...
code puis,Breakpoint (Code Composer Studio) -> Breakpoint) :

Die Breakpoints ermöglichen es uns, die Ausführung eines Programms an einem bestimmten Punkt im Code zu stoppen, das wird uns
ermöglicht das Initialisieren der Ereignismessung, kurz bevor die gewünschte Funktion ausgeführt wird, und das Ergebnis zu sehen.
Messung, direkt nach der Ausführung unserer Funktion.

Lancer l’exécution sur un certain cœur, le programme s’arrête au premier breakpoint,faitesRun -> Clock -> Enable,
puisRun -> Uhr -> Einrichtung …und das [Link] wählen:

Starten Sie das Programm erneut (Fortsetzen oder F8), das Programm hält am zweiten Haltepunkt an und zeigt die
Ergebnis der Messung: 37 (praktisch, dasselbe theoretische Ergebnis)

26
DSP C6000 BAHTAT Mounir
Um den Zähler zurückzusetzen: Ausführen -> Uhr -> Zurücksetzen

Um die Haltepunkte zu entfernen: Ausführen -> Alle Haltepunkte entfernen

Ebenfalls messen wir andere wichtige Ereignisse:

- Anzahl der CPU-Zyklen ([Link]): 2709 Zyklen


- Speicherlatenzen ([Link].L1D) : 268 Zyklen
- Nombre total des cycles ([Link]) : 2984 cycles

Hinweis: In diesem Dokument sind alle angegebenen Ergebnisse die des Simulators (in Emulation, es ist vorgesehen, dass es ...
hat einen kleinen Unterschied)

An diesem Punkt fragt man sich, warum wir nicht in der Lage sind, eine so hohe Effizienz zu erreichen (gerade einmal 30 %).
Derzeit). Um diese Frage zu beantworten, werden wir versuchen, die Anweisungen auf einer niedrigeren Ebene zu bewerten.
kann auf der Ebene des HerzC66x durchgeführt werden, angesichts der verfügbaren Ressourcen, um unser Produkt zu implementieren
matrix

Die allererste Operation, die wir in unserem Matrixprodukt durchführen sollen, besteht darin, zwei Zahlen zu entladen.
Komplexe von jeder der beiden Matrizen, ihr Matrixprodukt berechnen und das Ergebnis akkumulieren:

Die Einheiten D1 und D2 können jeweils 64-Bit-Daten laden, daher wird jede der Einheiten eine Anzahl laden
Komplexe pro Zyklus. Eine Einheit. Ein M1-Multiplikator kann in 1 Zyklus die 4 erforderlichen 32-Bit-Multiplikationen durchführen.
eine komplexe Multiplikation der geladenen Daten. Die Einheiten L1 und S1 können die 4 32-Bit-Akkumulationen durchführen.
verbleibenden.

Die mögliche Reihenfolge der Anweisungen im Zeitverlauf (pro Zyklus) ist unten schematisch dargestellt:

27
DSP C6000 BAHTAT Mounir

Im ersten Zyklus verwenden wir .D1 und .D2, um Daten zu laden, die im folgenden Zyklus verfügbar sein werden.
Nutzen Sie das .M1, um die geladenen Daten zu multiplizieren. Im folgenden Zyklus werden die Ergebnisse der Multiplikationen bereit sein.
durch .L1 zunächst addiert werden, um die Berechnung des komplexen Produkts abzuschließen, und dann durch .S1 akkumuliert
im folgenden Zyklus. Es handelt sich um die ideale Sequenz von Anweisungen, die man haben kann; in diesem Fall sind wir in der Lage zu
faire4 MAC pro Zyklus, noch weit entfernt von der maximalen Rechnungsleistung eines C66x: 8 MAC pro Zyklus
(in Bezug auf das wir die Effizienz unserer Implementierung C berechnen). Dies erklärt die reduzierte Effizienz unseres
Code (<30%), das Maximum lag übrigens nur bei 50%.

Anmerkung: Hier haben wir angenommen, dass jede der ausgeführten Anweisungen das Ergebnis an den folgenden Zyklus zurückgibt, in Wirklichkeit
das ist nicht der Fall (zum Beispiel benötigt eine Multiplikation 4 Zyklen, bevor sie Daten zurückgibt), aber man hat das
Recht, es an diesem Punkt zu vermuten, da wir uns im Zustand der vollen Pipeline befinden, haben wir fast ein neues Ergebnis.
Zyklus.

Der Hauptgrund für die Unfähigkeit, die anderen 50 % der Rechenleistung unseres Herzens zu nutzen, liegt darin, dass man
kann keine weiteren Daten laden (zum Beispiel mit .M2 multiplizieren). Man bemerkt einen maximalen Druck auf die
unités de chargement, pourtant, la quantité chargée n’est pas suffisante afin d’exploiter la totalité de la puissance du
Prozessor.

Kann man die Anzahl der Zugriffe auf den Speicher reduzieren, um den Druck auf die Einheiten .D1 und .D2 zu minimieren und
Danach das Maximum des Herzens nutzen? Man weiß, dass man mindestens auf den Speicher zugreifen muss. Mal,
aber in unserem aktuellen Programm machen wir es Das bedeutet, dass jede komplexe Daten der Matrizen A und
B ist N-mal in den Registern des Herzens geladen. Eine mögliche Lösung wäre es, eine Datenquelle maximal auszunutzen.
bevor man eine andere lädt, und somit nicht gezwungen ist, sie häufig neu zu laden.

Man bemerkt zum Beispiel, dass dasselbe 1ÄraDie Daten der Matrix B werden mit jedem der ersten Elemente verwendet.
der Zeilen der Matrix A, also waren wir gezwungen, sie N-mal zu laden. Wenn wir 2 Zeilen der Matrix A gleichzeitig behandeln,
sorte, dass in der1ÄraIteration des Algorithmus lädt 2 Elemente der Matrix A und 1 einziges Element von der
Matrix B und mit 2 Multiplikationen und 2 Akkumulationen wie dargestellt:

28
DSP C6000 BAHTAT Mounir
Man reduziert dann die Anzahl der Ladevorgänge der Elemente der Matrix B um 2, da man dieselben Daten verwendet.
Matrize B zweimal. Man kann es besser machen, indem man auch 2 Spalten der Matrix B parallel bearbeitet, das wird
Ähnlich der Gewinn, die Anzahl der Ladevorgänge der Elemente der Matrix A um 2 zu reduzieren:

Die mögliche Reihenfolge der Anweisungen im Zeitverlauf (pro Zyklus) ist unten schematisch dargestellt:

Bei der Aufladung werden in den ersten 2 Zyklen 4 komplexe Zahlen verwendet, bei denen 4 komplexe Multiplikationen durchgeführt werden.
Einheiten .M nach dem Laden, die Akkumulationen werden durch die Einheiten .L und .S sichergestellt

Es fällt auf, dass die Iterationen durch einen Zyklus getrennt sind, in dem keine Operationen ausgeführt werden, man kann nicht
effectivement charger des données durant ce cycle, sinon, les opérations de multiplications dans le cycle qui suit
werden fehlerhafte Daten behandeln.

Mit dieser Lösung haben wir eine ideale Leistung von 16 MAC/3 Zyklen = 5,33 MAC/Zyklus erreicht. Dies ist
besser als das klassische Produkt, das nur 4 MAC/Zyklus als maximale Spitzenwert erlaubte.

Man kann noch besser abschneiden, indem man 4 Zeilen der Matrix A und 2 Spalten der Matrix B gleichzeitig bearbeitet:

Die mögliche Reihenfolge der Anweisungen im Laufe der Zeit (pro Zyklus) ist unten schematisch dargestellt:

29
DSP C6000 BAHTAT Mounir

Wir laden 4 Daten aus der Matrix A und 2 Daten aus der Matrix B während der ersten 3 Zyklen, wir können
Beginnen Sie mit der Verwendung der Multiplikationseinheiten ab 3.èmeZyklus (in dem mindestens 3 Daten geladen sind), in
In diesem Fall wären 8 Multiplikationen während 4 Zyklen zu erledigen.

Es ist unerlässlich, dass die letzte Multiplikationsoperation .M1/2 in einer Iteration mit unterschiedlichen Daten umgeht.
die von .D1/2 während des ersten Iterationszyklus beauftragt wurden, um dasselbe Problem zu vermeiden
zuvor und dass die Multiplikationseinheiten die richtigen Daten verarbeiten.

Dank dieser Konfiguration konnten wir die Anzahl der Ladeoperationen der Matrix A um 2 reduzieren und die Anzahl der
Ladungen der Matrix B durch 4. Dies wird deutlich in der vorgeschlagenen Ausführungsfolge, dass die Einheiten .D1
et .D2 werden nicht mit voller Leistung genutzt, und sind dennoch ausreichend, um 8 komplexe Multiplikationen zu versorgen.
Die Iteration erstreckt sich in diesem Fall über 4 Zyklen.

Diese Lösung ermöglicht eine ideale Leistung von 8 MAC/Zyklus und kann somit das gesamte Potenzial ausschöpfen
Leistung geliefert vom Herz C66x.

Der mögliche Nachteil, den dieser neue Algorithmus haben kann, ist, dass N ein Vielfaches von 4 sein muss, damit das funktioniert.
funktioniert. Man kann dem durch die Erweiterung auf die nächstgelegene Matrixgröße, die ein Vielfaches von 4 ist, abhelfen.

Es bleibt nur noch, diese neue Modifikation des Algorithmus in C zu implementieren, das wird den folgenden Code ergeben:

void produkt_matrize(double* inpA, double* inpB, double* outp, int n) {


inti,j,k;
doublea0b0,a0b1,a1b0,a1b1,a2b0,a2b1,a3b0,a3b1;
doublea0,a1,a2,a3,b0,b1;
für(i=0;i<n;i+=4) {
für(j=0;j<n;j+=2) {
a0b0=a0b1=a1b0=a1b1=a2b0=a2b1=a3b0=a3b1=0.0;
für(k=0;k<n;k++) {
a0=inpA[i*N+k];
a1=inpA[(i+1)*N+k];
a2=inpA[(i+2)*N+k];
a3=inpA[(i+3)*N+k];
b0=inpB[k*N+j];
b1=inpB[k*N+j+1];

a0b0=_daddsp( a0b0, _complex_mpysp ( a0 , b0 ) );


a0b1=_daddsp( a0b1, _complex_mpysp ( a0 , b1 ) );
a1b1=_daddsp( a1b1, _complex_mpysp ( a1 , b1 ) );
a1b0=_daddsp( a1b0, _complex_mpysp ( a1 , b0 ) );
a2b0=_daddsp( a2b0, _complex_mpysp ( a2 , b0 ) );
a3b0=_daddsp( a3b0, _complex_mpysp ( a3 , b0 ) );
a2b1=_daddsp( a2b1, _complex_mpysp ( a2 , b1 ) );

30
DSP C6000 BAHTAT Mounir

a3b1=_daddsp( a3b1, _complex_mpysp ( a3 , b1 ) );

}
outp[i*N+j]=a0b0;
outp[i*N+j+1]=a0b1;
outp[(i+1)*N+j+1]=a1b1;
outp[(i+1)*N+j]=a1b0;
outp[(i+2)*N+j]=a2b0;
outp[(i+3)*N+j]=a3b0;
outp[(i+2)*N+j+1]=a2b1;
outp[(i+3)*N+j+1]=a3b1;
}
}
}

Diesmal mit einer Effizienz von 44,216991 %

Es handelt sich um die Mehrheit der Optimierungen, die man in C vornehmen kann, es erweist sich als schwierig für den Compiler, einen
Assemblersprache, die der Sequenz von Anweisungen entspricht, die wir konkret auf der Ebene des Herzens umsetzen wollen.
Die Alternative, die die maximale Kontrolle über die Optimierung bietet, besteht darin, unsere Routinen direkt in Assembler zu codieren.
der Gegenstand des nächsten Teils sein wird.

Schließlich werden einige Richtlinien genannt, die verwendet werden, um dem Compiler mehr Informationen zu liefern:

#pragma MUST_ITERATE(min, max, mehrfach de)

Diese Zeile ist direkt vor einer Schleife positioniert, um dem Compiler Informationen bereitzustellen, die sich auf das beziehen
Anzahl der Iterationen (minimale Anzahl an Iterationen, maximale Anzahl, ist diese Zahl ein Vielfaches von einer)
bestimmte Werte)

Hinzugefügt zu unserer Multiplikationsfunktion wie folgt:


doublea0,a1,a2,a3,b0,b1;
#pragmaMUST_ITERATE (1,,)
for(i=0;i<n;i+=4) {
für(j=0;j<n;j+=2) {

Man erhält eine Effizienz von 44,307692 %

Hinweis: Lepragma ist eine Direktive, die den Compiler darüber informiert, dass das ausführbare Programm auf eine andere Weise erzeugt werden soll als

Zelle standardmäßig

Eine 2èmeähnliche Anweisung ist :

_nassert(assertion)

Dem Compiler Optimierungstipps in Form von echten Aussagen geben. In unserem Matrixprodukt...
kann wie folgt verwendet werden :


doublea0b0,a0b1,a1b0,a1b1,a2b0,a2b1,a3b0,a3b1;
doublea0,a1,a2,a3,b0,b1;
_nassert(n%4==0);
_nassert(n>=4);
#pragmaMUST_ITERATE (1,,)
für(i=0;i<n;i+=4) {
für(j=0;j<n;j+=2) {

31
DSP C6000 BAHTAT Mounir

Qui donne une efficacité de44.353183 %

Ein weiterer Mechanismus ist das Entfalten von Schleifen (unrolling), das es ermöglicht, mehrere Iterationen zu fusionieren.
Eine Schleife in einer einzigen, das kann die Leistung verbessern, erhöht aber die Größe des generierten Codes:

#pragma UNROLL(Anzahl der Entpackungen)

Eine letzte wichtige Pragma-Direktive ist:

#pragma DATA_SECTION(Variable , Section)

Dies positioniert eine Variable in einem bestimmten Abschnitt, der in der .cmd-Befehlsdatei angegeben ist. Das kann sein
auf diese Weise verwendet:

komplexinpA[N][N];
complexinpB[N][N];
komplexausg[N][N];

#pragmaDATA_SECTION(inpA,".mysect")

Die [Link] ist keine vordefinierte Sektion, sondern vom Programmierer in der Befehlsdatei angegeben.
so:

GEDÄCHTNIS
{
L2SRAM :o= 0x00800000 ,l= 0x00080000
MSMCSRAM :o= 0x0C000000,l= 0x00400000
}

ABSCHNITTE
{
L2SRAM
.daten> L2SRAM
.cinit> L2SRAM
.const> L2SRAM
.cio> L2SRAM
.far> L2SRAM
.nah > L2SRAM
.fardata > L2SRAM
.sysmem> L2SRAM
.stack> L2SRAM
.mysect > MSMCSRAM
}

32
DSP C6000 BAHTAT Mounir

Teil 2: ASM C66x Programmierung


Chapitre 1 : Notre premier code assembleur
Texas Instruments schlägt 3 Möglichkeiten vor, ein Programm für c66x zu schreiben: C/C++-Sprache; lineare Assemblersprache;
Assembler Standard C66x :

Wie oben gezeigt, verbirgt die C/C++-Programmierung die niedrigstufige Komplexität des C66x-Kerns, nämlich: [die
Auswahl der geeigneten Assembleuranweisungen, Auswahl der Register/Verteilung der Ausführung auf die 2 Bänke (A und B),
Mapping/Ordnung der Anweisungen auf die verschiedenen Einheiten, Pipeline], aber im Gegenzug, eine
Die maximale Leistung ist nicht garantiert. Auch kann das Verhalten desselben Codes in C/C++ je nach jedem variieren.
Aktualisierung/Version des Compilers [Portabilitätsproblem].

Eine neue Art der ASM-Programmierung ist der lineare Assembler, der die Möglichkeit bietet, die Wahl zu beeinflussen.
des Anweisungen des Assemblers zu verwenden, sowie die Art und Weise der Zuweisung von Registern. Die anderen Details jedoch
werden automatisch vom Compiler [Assembly-Optimierer] verwaltet. Dieser Programmiermodus ist einfacher
der Standardassembler und bietet im Allgemeinen bessere Leistung im Vergleich zu C/C++.

Der Standard-Assembler c66x verwaltet manuell auf niedriger Ebene die Operationen auf dem c66x-Kern und wählt das
angemessene Parallelität der Instruktionen [ILP]. Dieser Programmiermodus kann die maximale Leistung bieten.
möglich. Außerdem wird der geschriebene Code genau so vom Kern ausgeführt, was ebenfalls die maximale Portabilität bietet,
da dieser Code vom Compiler nicht geändert wird. Wir werden uns später mit dieser Programmierweise beschäftigen.

Der Code-Generierungsfluss für c66x ist wie folgt schematisiert:

33
DSP C6000 BAHTAT Mounir

Die Erweiterung ist die des linearen Assemblers, man wird dann direkt Standard-Assemblercode in .asm schreiben.
unabhängig von jedem Tool zur Nachoptimierung. Der Binärcode, der in den Speicher geladen wird, um
ausgeführt al'[Link]

Es wird angemerkt, dass der Assembler schwieriger zu erstellen ist als ein C-Code, im Vergleich zu den Leistungen. Während ...
Im Allgemeinen möchte man nur die Hauptkritischen Routinen optimieren, während andere Routinen nicht betroffen sind.
Interesse an so viel Optimierung (Initialisierungsroutinen, Datendeklarationen, Zuweisungen, ...). Wir schlagen vor
durch die Kombination der Verwendung von C und Standard-ASM; die Programmiersprache C für Routinen/Operationen, die nicht
müssen optimiert werden, und das ASM für die Haupt- und kritischen Funktionen zur Laufzeit.

Die minimale Struktur eines Assemblercodes ist wie folgt (Routine namens_myasm):

.global _myasm
_myasm:
; Anweisungen
B .S2 B3
NOP5

_myasm:ist ein Etikett, das den Beginn der Definition der ASM-Routine anzeigt. Die Direktive .global beeinflusst eine
globale Sichtbarkeit der Entität "_myasm", so dass sie aus einem C-Code aufgerufen werden kann. Ein
instruction assembleur a la syntaxe suivante :(nom d ’instruction) (.unité) (opérandes).Ainsi l ’instructionB
(Bruch) wird auf der Einheit S2 mit dem Operanden im Register B3 ausgeführt. Ein Bruch wird den Fluss ändern.
Die Ausführung eines Codes in einen anderen Bereich des Programmspeichers, in unserem Fall geht es darum, sich zu verbinden mit
Anweisungen, die sich unter der Adresse befinden, die in B3 gespeichert ist. Dieser Wert [Adresse] in B3 ist
die Anweisungen der Mutterfunktion, die unmittelbar nach dem Ende unserer Assembly-Routine ausgeführt werden müssen.
Daher ist es unerlässlich, sich am Ende unserer Routine an diese Adresse anzuschließen, um den normalen Fluss fortzusetzen.
Ausführung unseres Hauptprogramms. Beachten Sie, welche angemessene Adresse in B3 beim Aufruf gesetzt wird.
Funktionsassembler, man muss also seinen Wert sichern, um am Ende der Anweisungen darauf zugreifen zu können.

34
DSP C6000 BAHTAT Mounir
Man bemerkt auch die Verwendung von NOP 5[5 Zyklen ohne Operationen], direkt nach dem Sprunganweisung.
In der Tat wird der Anschluss erst 6 Zyklen nach seinem Start ausgeführt, somit beträgt die Wartezeit 5
Zyklen sind erforderlich. Dies ist der Fall für alle C66x Assembleranweisungen, die eine Latenz erfordern, die genannt wird
"Delay Slots" [DS], bevor ein Ergebnis/eine Ausführung zurückgegeben werden kann. Zum Beispiel im Fall eines Zweigs.
Finde einen "Delay Slot"=5, für eine Fließkomma-Multiplikation mit einfacher Präzision beträgt die Latenz DS=3.

Wenn eine Multiplikation 4 Zyklen dauert, um ein Ergebnis zurückzugeben, warum sagt man dann, dass man mit diesem DSP arbeiten kann?
8 multiplications par cycle ? Une autre caractéristique des instructions assembleurs c66x est la latence sur les unités
d’exécution "Functional Unit Latency" [FUL], die die Anzahl der Zyklen angibt, die die Assemblersprache anweisen muss
belegen auf einer Ausführungseinheit. Fast nur die Doppelpräzisionsanweisungen benötigen FUL>1, hingegen
Die anderen Anweisungen haben fast alle FUL=1. Das heißt, selbst wenn eine Fließkommavervielfältigung einfacher Genauigkeit
Es wird 4 Zyklen benötigen, um das Ergebnis zurückzubringen, es wird die Einheit .M während nur 1 Zyklus einnehmen, danach.
Man kann andere Multiplikationen auf der Einheit M nur im darauf folgenden Zyklus ausführen, selbst wenn die erste
Die Multiplikation hat noch kein Ergebnis zurückgegeben, wie im Folgenden skizziert:

Man sieht then, dass diese c66x-Anweisungen im "Pipeline"-Modus arbeiten können, letztendlich haben wir 1 Ergebnis.
die Multiplikation durch Zyklus.

Schließlich rufen wir unsere Assembleurroutine aus C auf, indem wir zunächst das Prototyp definieren.
"gewünscht" für die Funktion Assembleur, die beispielsweise so definiert ist:

void_myasm(void);

Der Anruf erfolgt ganz einfach über:

_myasm();

Anschließend schlagen wir vor, in Standardassembler c66x die Vektoraddition von zwei Vektoren A und B durchzuführen, von
Größe N, mit einfach präzisen Fließkommaelementen. Das Ergebnis der Summe ist der Vektor, der mit S bezeichnet wird.

Die Deklaration und die Zuweisung des Speichers für diese Vektoren wäre in C wie folgt:

#define N 1000

floatA[N];
floatB[N];
floatS[N];

Wir initialisieren die Eingangsvektoren mit Testwerten, so in der main-Funktion:

inti;
für(i=0;i<N;i++) {A[i]=(float)i;B[i]=(float)(i/2.0); }

Dann definieren wir das gewünschte Prototyp für unsere Assembler-Routine, die die Vektorsumme berechnen wird:

35
DSP C6000 BAHTAT Mounir
void_somme_asm(float*a,float*b,float*s,intn);

4 Argumente werden für unsere ASM-Funktion ausgewählt; die ersten 3 sind Zeiger auf die Vektoren A, B und S.
respektive. Der letzte Parameter ist die Größe N dieser Vektoren.

Unsere Routine muss dann die Daten der Vektoren A und B von den in den Argumenten angegebenen Adressen [Zeiger] lesen,
und muss das Ergebnis der Summe an der Adresse des Vektors S speichern, die ebenfalls als Argument angegeben wird.

Die an einen Assembler-Code übergebenen Argumente werden automatisch in bestimmten Registern platziert.
so:

Das erste Argument befindet sich in A4, das 2èmeam B4, dem 3.èmein A6, am 4.ème en B6 und so weiter. Beachten Sie gut, dass der
Das Register B3 ist für die Rückadresse zur Mutterfunktion, das Register A4 dient auch als Wert von
Rückkehr der Assemblierer-Funktion.

Es ist auch zu beachten, dass die Register A10 bis A15 und B10 bis B15 gespeichert werden müssen, wenn sie verwendet werden.
Assembler, da sie von der C-Schnittstelle verwendet werden:

36
DSP C6000 BAHTAT Mounir
Also fangen wir an, den minimalen Code für eine Assemblerroutine zu schreiben:

.global _somme_asm
_somme_asm:

B .S2 B3
NOP5

Der Code wird aus mehreren Iterationen [N Iterationen] bestehen, bei denen in jeder Iteration 2 Fließkommaelemente aus den Vektoren A geladen werden.
et B, dann berechnen wir die Summe und speichern schließlich das Ergebnis im Vektor S. Die Assemblersprache-Anweisung, die
Erlaubt das Laden eines 32-Bit [Gleitpunkt] Elements aus dem Speicher in ein Register der Bank ist 'LDW' [Load Word].

Die Informationen zur Verwendung der Assemblierungsanweisungen finden Sie im Handbuch von TI "TMS320C66x CPU und
Anleitungssatz-Referenzhandbuch, herunterladbar von der offiziellen Seite:

[Link]

Für jede Anweisung im Handbuch kann man wissen: die Syntax, die möglichen Ausführungseinheiten [.D1, .D2, …],
Beschreibung der Funktionsweise, Delay Slots, Latenz der Funktionseinheiten, Beispiele für die Verwendung.

Die LDW-Anweisung kann auf den Einheiten .D1 oder .D2 ausgeführt werden. Bei DS=4 wird die Daten tatsächlich geladen.
Nach 5 Zyklen werden 4 NOP benötigt. Eine mögliche grundlegende Syntax ist die folgende: LDW .D *addr,dst

Alle Operanden der Befehle dürfen nur Register der Bänke (A oder B) sein, addr muss ersetzt werden.
durch das Register, das die Adresse enthält, von der aus die Daten geladen werden sollen, ersetzt dst durch das Register, in das die Daten geladen werden.

geben.

Der Code wird:

.global _somme_asm
_somme_asm:
LDW .D1*A4,A31
|| LDW .D2*B4,B31
B .S2 B3
NOP5

Denken Sie daran, dass A4 und B4 die Adressen der Vektoren A und B enthalten, die als Argument übergeben wurden. Die
Der Code wird dann zwei Elemente in die beiden Register A31 und B31 laden. In der nächsten Iteration müssen wir die 2 laden.
Elemente, die folgen, daher ist die Inkrementierung der Zeiger A4 und B4 notwendig. In derselben Anweisung LDW, man
kann auch eine Post-Inkrementierung der Zeiger auf die folgenden Elemente durchführen:

.global _somme_asm
_somme_asm:
LDW .D1*A4++[1],A31; mit Nachverzögerung von 1 [zum nächsten Wort]
|| LDW .D2*B4++[1],B31; mit Postinkrementierung von 1 [zum nächsten Wort]
NOP4
B .S2 B3
NOP5

Es ist zu beachten, dass die Einheit .D1 grundsätzlich nur auf die Register der Bank A zugreift und dass die Einheit .D2 nicht darauf zugreifen kann.

qu'zu den Registern der Bank B. Es kann jedoch sein, dass eine Anweisung Operanden beider Banken A und B hat, dank
auf dem Weg des "cross path" [1X und 2X], der auf 64-Bit pro Richtung und Zyklus begrenzt ist.

Die 2 Ladeanweisungen werden parallel ausgeführt, angezeigt durch das Symbol „||“. Tatsächlich kann man ausführen
bis zu 8 Anweisungen parallel über die 8 verfügbaren Einheiten (.L1, .L2, .S1, .S2, .M1, .M2, .D1, .D2)

37
DSP C6000 BAHTAT Mounir
Jetzt ist der Befehl, der es ermöglicht, die Summe von zwei Fließkommazahlen einfacher Genauigkeit zu berechnen, FADDSP.
Die Anweisung kann auf den Einheiten ausgeführt werden: .L1, .L2, .S1 oder .S2. Mit der folgenden Syntax: FADDSP (.unit) src1, src2, dst
Dies wird die Summe von src1 und src2 bilden und das Ergebnis im Register dst speichern. Diese Anweisung hat eine Latenz von DS.
[delay slots] = 2.

Nur einige Operanden können im "Cross Path" verwendet werden, die im Handbuch durch "xop" vs. "op" gekennzeichnet sind.
die sich nicht so verwenden lassen.

Für den Befehl FADDSP gilt nur die 2èmeOperande [src2] kann im "Cross Path" verwendet werden.

Der Code wird:

.global _somme_asm
_somme_asm:
LDW .D1*A4++[1],A31; mit Postinkrementierung von 1 [zum nächsten Wort]
|| LDW .D2*B4++[1],B31; mit Postinkrementierung von 1 [zum nächsten Wort]
NOP4
FADDSP .L1X A31,B31,A30
NOP2
B .S2 B3
NOP5

Beachten Sie das Hinzufügen eines X zur gewählten Einheit .L1, das die Verwendung des "Cross Path" für den Operanden B31 anzeigt.

Anschließend muss das Ergebnis [A30] an die Adresse des Vektors S gespeichert werden. Dies geschieht mit der Anweisung STW [Speichern
Word].Cette instruction s’exécute sur les unités : .D1 ou .D2. Ayant DS=0. Une syntaxe basique possible est :
STW (.unit) src,*dst. Dies wird das 32-Bit-Register src an die Adresse speichern, die im Register dst enthalten ist:

.global _somme_asm
_somme_asm:
LDW .D1*A4++[1],A31; mit Postinkrementierung von 1 [zum nächsten Wort]
|| LDW .D2*B4++[1],B31; mit Post-Inkrementierung von 1 [zum nächsten Wort]
NOP4
FADDSP .L1X A31,B31,A30
NOP2
STW .D1 A30,*A6++[1]
B .S2 B3
NOP5

Finalement, il faut se brancher encore pour boucler l’exécution N [Link] va crée une étiquette nommée [boucle]
markiert den Beginn der Schleife wie folgt:

.global _somme_asm
_somme_asm:
boucle:
LDW .D1*A4++[1],A31; mit Postinkrementierung von 1 [zum nächsten Wort]
|| LDW .D2*B4++[1],B31; mit post-Inkrementierung von 1 [zum nächsten Wort]
NOP4
FADDSP .L1X A31,B31,A30
NOP2
STW .D1 A30,*A6++[1]
B .S2 Schleife
NOP5
B .S2 B3
NOP5

38
DSP C6000 BAHTAT Mounir

Wir werden auch ein Register benötigen, das die Anzahl der Iterationen zählt und den Verzweigungsfall bedingt.
wo N Iterationen vergangen sind:

.global _somme_asm
_somme_asm:
MV .S2 B6,B0; n
Schleife
LDW .D1*A4++[1],A31; mit Post-Inkrementierung von 1 [zum nächsten Wort]
|| LDW .D2*B4++[1],B31; mit Post-Inkrementierung von 1 [zum nächsten Wort]
|| SUB.S2 B0,1,B0
NOP4
FADDSP .L1X A31,B31,A30
NOP2
STW .D1 A30,*A6++[1]
[B0]B .S2 Schleife
NOP5
B .S2 B3
NOP5

Zunächst die Anweisung beachten:[B0]B .S2 Schleife; das Register B0 wird auf diese Weise festgelegt
Die Ausführung oder Nichtexekution des Sprungbefehls. Wenn B0 ungleich 0 ist, dann wird der Befehl ausgeführt, andernfalls wenn B0
ist null, der Befehl wird durch ein NOP ersetzt.

Alle Assemblierungsanweisungen c66x können bedingt ausgeführt werden. Die Bedingungsregister sind nur die
6 Folgende: A0, A1, A2, B0, B1, B2.

L’instruction assembleur:MV .S2 B6,B0; copie le contenu du registre B6 [qui contiendra la valeur de N] vers le
Register des Zustand B0 [DS=0]. Schließlich wird im Herzen der Schleife bei jeder Iteration der Wert dekrementiert.
B0 parallel zu den Ladeoperationen, über den Befehl SUB [Ganzzahlige Subtraktion, DS=0].

Dieser Code ist in der Lage, die Vektorsumme in einer Anzahl von CPU-Zyklen = 15N + 7 durchzuführen.

Der endgültige C-Code wird diese Form haben:

#include <stdio.h>
#include "csl_tsc.h"
#define N 1000
floatA[N];
floatB[N];
floatS[N];
void_somme_asm(float*a,float*b,float*s,intn);
void main(void) {
unsigned long longst, fn, total;
_CSL_tscEnable();

inti
für(i=0;i<N;i++) {A[i]=(float)i;B[i]=(float)(i/2.0); }

st=_CSL_tscRead();
_sommer_asm(A,B,S,N);
fn=_CSL_tscRead();

total=fn-st;
printf("Anzahl der Zyklen=%lld\n",total);
}

Im nächsten Kapitel werden wir Methoden zur Optimierung unseres C66x-Assemblercodes behandeln, um zu einem
möglichst kurze Ausführungszeit.

39
DSP C6000 BAHTAT Mounir

Anhang zum Kapitel 1: Anweisungen für Assembler nach Verarbeitungseinheit

Einheit .D :

Einheit .L :

40
DSP C6000 BAHTAT Mounir
Einheit .S :

Einheit .M :

41
DSP C6000 BAHTAT Mounir

Kapitel 2: Einen optimierten Assembler-Code schreiben

Im im ersten Kapitel entwickelten Assemblierungscode finden sich mehrere NOP-Operationen aufgrund von Latenzen.
Anweisungen c66x :

.global _somme_asm
_somme_asm:
MV .S2 B6,B0; n
Schleife
LDW .D1*A4++[1],A31; mit Post-Inkrementierung von 1 [zum nächsten Wort]
|| LDW .D2*B4++[1],B31; mit Post-Inkrementierung von 1 [zum nächsten Wort]
|| SUB.S2 B0,1,B0
NOP4
FADDSP .L1X A31,B31,A30
NOP2
STW .D1 A30,*A6++[1]
[B0]B .S2 Schleife
NOP5
B .S2 B3
NOP5

Die erste Optimierung besteht darin, die NOPs im Herzen der Schleife durch nützliche Operationen zu ersetzen. So, 5
Anweisungen aufgrund der Verbindung können wie folgt entfernt werden:

.global _somme_asm
_somme_asm:
MV .S2 B6,B0; n
Schleife:
LDW .D1*A4++[1],A31; mit Post-Inkrementierung von 1 [zum nächsten Wort]
|| LDW .D2*B4++[1],B31; mit Post-Inkrementierung von 1 [zum nächsten Wort]
|| SUB.S2 B0,1,B0
NOP2
[B0]B .S2 Schleife
NOP1
FADDSP .L1X A31,B31,A30
NOP2
STW .D1 A30,*A6++[1]
B .S2 B3
NOP5

Hier wird der Anschluss im Voraus gestartet, der tatsächlich nach 6 Zyklen angeschlossen werden muss; also, direkt danach
Die Ausführung des Speicherbefehls STW erfolgt erneut ohne Verzögerungen an das Etikett "Schleife".
hat zur Folge, die Anzahl der CPU-Zyklen von 15N+7 auf 9N+7 zu reduzieren.

Die Einheit .D ist in der Lage, eine 64-Bit-Datenmenge gleichzeitig zu laden/speichern, aber bisher wird sie nur verwendet für
Um ein 32-Bit-Element [Fließkommazahl] zu laden/speichern. Es wird dann vorteilhaft sein, die Anweisung LDDW zu verwenden.
der Platz von LDW; tatsächlich ermöglicht LDDW [Load Double Word] das Laden von 64-Bit-Daten in ein Paar von
Register, man verwendet ihn anschließend, um 2 Fließkommazahlen aus demselben Vektor gleichzeitig zu erwerben. Eine grundlegende Syntax
Möglich ist die folgende: LDDW .D *addr,dst_o:dst_e. Die Latenz DS=4 ebenfalls.

Der letzte Operand von LDDW ist ein Registerpaar, das eines der folgenden Paare sein muss:

42
DSP C6000 BAHTAT Mounir

Beachten Sie zum Beispiel, dass A2:A1 nicht als gültiges Registerpaar erkannt wird. Man wird oft einen
Paar von Registern durch die Notation :reg_o:reg_e; die Entität reg_o [ungerades Register] weist das ungerade Register (A1, A3, …) aus.
während reg_e [gerader Register] das gerade Register(A0,A2,…) anzeigen wird.

Für bestimmte Instruktionen werden sogar Quadruplets von Registern [reg_3:reg_2:reg_1:reg_0] verwendet, die müssen
einer von denen hier sein:

Für LDDW wird die Post-Inkrementierung unter Verwendung der Syntax ++[1] den Zeiger um ein Wort von 64 Bit inkrementieren; was
ist anders als das Verhalten bei der Nutzung von LDW, das in diesem Fall den Zeiger nur um eine erhöht.
Menge von 32-Bit.

Es ist anzumerken, dass für LDDW das Wort 32-Bit an der Adresse addrsera in endst_e gespeichert wird, während das an
addr+4stocké endst_o

Nach dem Laden von 2 Gleitkommazahlen aus jedem der 2 Vektoren A und B unter Verwendung von LDDW müssen dann 2 gemacht werden.
fließende Ergänzungen. Dies kann mit einer einzigen Anweisung erfolgen: DADDSP [Double Single Precision Addition]; DS=2; Die
Die Syntax ist wie folgt: DADDSP (.unit) src1_o:src1_e, src2_o:src2_e, dst_o:dst_e

Diese Anweisung hat die gleiche Wirkung wie die folgenden 2 Anweisungen:

FADDSP src1_o,src2_o,dst_o
||FADDSP src1_e,src2_e,dst_e

43
DSP C6000 BAHTAT Mounir

Schließlich steht uns zum Speichern von 64-Bit in den Speicher auch der Befehl STDW [Store Double Word] zur Verfügung.
Ersatz STW. DS=0 ; mit der Syntax :STDW (.unit) src_o:src_e,*dst

Es ist zu beachten, dass für STDW, src_esera an der Adresse dst gespeichert ist; während src_o an der Adresse dst+4 gespeichert ist.

Der Code wird:

.global _somme_asm
_somme_asm:
SHRU .S2 B6,1,B0; n/2
Schleife:
LDDW .D1*A4++[1],A31:A30
|| LDDW .D2*B4++[1],B31:B30
|| SUB.S2 B0,1,B0
NOP2
[B0]B .S2 Schleife
NOP1
DADDSP .L1X A31:A30,B31:B30,A29:A28
NOP2
STDW .D1 A29:A28,*A6++[1]
; Ende der Schleife
B .S2 B3
NOP5

Le nombre des itérations est réduit àN/2grâce à l ’instruction SHRU (Logical Shift Right Unsigned), faisant le
Rechtsverschiebung des 1erOperande [B6, der Wert von N] durch eine Menge von 2èmeOperand [einmal], speichert das
Ergebnis im letzten Operanden [B0]. Die möglichen Einheiten für SHRU sind .S1 oder .S2. DS=0.

Ein kleines Problem zu lösen ist, wenn N ungerade ist, muss eine verbleibende Summe außerhalb der Schleife hinzugefügt werden.
N/2 Iterationen. Der Code wird schließlich:

.global _somme_asm
_somme_asm:
SHRU .S2 B6,1,B0; [n/2] = partie entière de (n/2)
SUB.S2 B6,B0,B1; n-[n/2]
SUB.S2 B1,B0,B1; n-2[n/2] = 0 wenn N gerade ist, oder 1 wenn N ungerade ist
Schleife
LDDW .D1*A4++[1],A31:A30
|| LDDW .D2*B4++[1],B31:B30
|| SUB.S2 B0,1,B0
NOP2
[B0]B .S2 Schleife
NOP1
DADDSP .L1X A31:A30,B31:B30,A29:A28
NOP2
STDW .D1 A29:A28,*A6++[1]
; Ende der Schleife
; Behandlung des verbleibenden Falls und Ende der Routine

44
DSP C6000 BAHTAT Mounir
[B1]LDW .D1*A4,A31
|| [B1]LDW .D2*B4,B31
NOP2
B .S2 B3
NOP1
[B1]FADDSP .L1X A31,B31,A30
NOP2
[B1]STW .D1 A30,*A6

Beachten Sie die Bedingung, dass die Anweisungen des letzten Falls vom Register B1 abhängig sind, das 0 enthalten wird, wenn N gerade ist, oder 1
wenn N ungerade ist.

Die Anzahl der CPU-Zyklen wird somit von 9N+7 auf 9N/2+12 reduziert.

Pipeline-Software der Schleifen [SPLOOP : Software-Pipeline-Schleife]

Anschließend wird vorgeschlagen, den vorherigen Code durch einen Mechanismus zu optimieren, der im C66x-Kern vorhanden ist, genannt
"SPLOOP". Die Software-Pipeline ist eine Scheduling-Technik, die das Parallelisimus nutzt.
Anweisungen (ILP) durch die Iterationen einer Schleife. Die folgende Abbildung veranschaulicht das Prinzip von
fonctionnement :

Ohne das Ende einer Iteration abzuwarten, starten wir eine neue Iteration gleichzeitig. Dies hat zur Folge, dass
ein Pseudo-Parallellismus der Ausführung der Iterationen und anschließend die Anzahl der Zyklen erheblich reduzieren
resultiert. Man unterscheidet drei wesentliche Komponenten: Prolog (Übergangsteil des Starts), Kernel (der Zustand
permanent de la boucle), Epilog (état d’épuisement de la boucle à la fin).

Eine Iteration wird nach einer Anzahl von Zyklen des Starts der vorherigen Iteration gestartet, diese Anzahl wird als: ii notiert.
[Iterationsintervalle], wie in der untenstehenden Abbildung gezeigt. Die Anzahl der Zyklen, die eine einzige Iteration bilden.
wird genannt: dynamische Länge, notédynlen. So beträgt der Prolog duredynlen-ii Zyklen, was die gleiche Dauer ist wie
der des Epilogs, während der Kernel eine Dauer von ii Zyklen hat, die sich während der gesamten Schleife wiederholen.

Schließlich fällt auf, dass darauf geachtet werden muss, dass das Intervall der Iteration [ii] so klein wie möglich ist, um
die Leistung der Implementierung einer Schleife zu erhöhen.

45
DSP C6000 BAHTAT Mounir

Lecœurc66x verfügt über einen spezialisierten Puffer für den SPLOOP-Mechanismus, der den Code einer einzigen Iteration speichert, und
répétant son exécution suivant le fonctionnement expliqué ci-dessus. Ceci a plusieurs avantages par rapport aux
Traditionelle Schleifen [mithilfe der Verbindungen]:

- Reduzierter Quellcode, da Prolog und Epilog nicht manuell ausgedrückt werden müssen.
Assembler
- Die Anweisungen werden einmal aus dem Programmspeicher in den Puffer geladen [während der 1ÄraIteration
nur], daher hat dies zur Folge, dass: die Nutzung der Bandbreite zum Speicher sowie die
Leistungsaufnahme
- Erlaubt sehr enge Schleifen und gibt eine Einheit .S für die Verbindung frei. Solange die Verbindung
benötigt 6 Zyklen, um sich anzuschließen!

Der SPLOOP-Puffer auf c66x kann bis zu 14 Ausführungspakete speichern. Dadurch ist der Iterationsintervall (ii)
darf 14 nicht überschreiten

Der Zugriff auf den SPLOOP-Puffer erfolgt hauptsächlich in Assemblersprache über die Schlüsselwörter SPLOOP und SPKERNEL, wie
montré en exemple ci-dessous :

Zunächst wird die gewünschte Anzahl der Iterationen festgelegt, indem man in das Register ILC (Internal Loop) schreibt.
Counter). Das Schreiben in diesem Register erfolgt durch einen speziellen Befehl: MVC wird auf der Einheit ".S2" ausgeführt.

46
DSP C6000 BAHTAT Mounir
Antragsteller DS=3. Danach wird die Anweisung SPLOOP den Beginn der Schleife markieren, ihr Operanden ist: das Intervall
d’itération [ii]. Dès son exécution, der Buffer SPLOOP wird sich mit den folgenden Anweisungen [I1, I2,
…]. Die Anweisung SPKERNEL zeigt das Ende der Iteration an und stoppt das Befüllen des Puffers SPLOOP.

Die Projektion dieses Mechanismus auf unser anfängliches Problem der Vektorsumme wird die Anordnung ergeben.
möglich folgend:

Beachten Sie, dass die Definition einer Iteration geändert werden musste von:

LDDW .D1*A4++[1],A31:A30
|| LDDW .D2*B4++[1],B31:B30
NOP4
DADDSP .L1X A31:A30,B31:B30,A29:A28
NOP2
STDW .D1 A29:A28,*A6++[1]

Vers :

LDDW .D1*A4++[1],A31:A30
|| LDDW .D2*B4++[1],B31:B30
NOP4
DADDSP .L1X A31:A30,B31:B30,A29:A28
NOP3
STDW .D1 A29:A28,*A6++[1]

Ein Zyklus hinzufügen, um Konflikte von Einheiten gemäß der beschriebenen Planung zu vermeiden.

Der Code wird schließlich:

.global _somme_asm
_somme_asm:
SHRU .S2 B6,1,B0; [n/2] = ganzzahlige Teil von (n/2)
SUB.S2 B6, B0, B1; n-[n/2]
SUB.S2 B1,B0,B1; n-2[n/2] = 0 wenn N gerade ist, oder 1 wenn N ungerade ist

MVC .S2 B0,ILC


NOP3

SPLOOP2

LDDW .D1*A4++[1],A31:A30
|| LDDW .D2*B4++[1],B31:B30
NOP4
DADDSP .L1X A31:A30,B31:B30,A29:A28
NOP3
STDW .D1 A29:A28,*A6++[1]

SPKERNEL0,0
; Ende der Schleife

47
DSP C6000 BAHTAT Mounir

; Behandlung des verbleibenden Falls und Ende der Routine


[B1]LDW .D1*A4,A31
|| [B1]LDW .D2*B4,B31
NOP4
[B1]FADDSP .L1X A31,B31,A29
NOP3
[B1]STW .D1 A29,*A6

B .S2 B3
NOP5

Die SPKERNEL-Anweisung verfügt über 2 Operanden, notiert fstgetfcyc; die die Verzögerung angeben, die sofort danach auferlegt werden soll.
die Ausführung der Schleife, bevor die Ausführung der folgenden Anweisungen beginnt. Die Anzahl der Verzögerungszyklen
ist also : . Die Note :

SPKERNEL0,0

Geben Sie die Ausführungsanweisung "während des Epilogs" für den Code, der der SPKERNEL-Anweisung folgt. Der Erschöpfungscode des
Die Schleife [Epilog] "wird dann parallel ausgeführt" mit dem folgenden Code. Das gesagt, in unserem Beispiel erfolgt die Verarbeitung des
Der letzte verbleibende ungerade Fall wird parallel zum Epilog ausgeführt, wie unten skizziert:

Um mögliche Konflikte zwischen den Anweisungen von Epilog und dem externen Code zu vermeiden, wird empfohlen, zu warten.
fin entière von Epilog, bevor das Ausführungshandbuch an den folgenden Code übergeben wird. So wird eine Latenz von festgelegt:
"dynlen-ii" in den SPKERNEL-Einstellungen.

Mit dieser neuen Optimierung erreichen wir eine Anzahl von Zyklen der Ordnung von N, anstelle von 9N/2.
ist ein Beschleunigungsfaktor von dex4.5]

Um die Suche nach der Iterationsform zu erleichtern, die zwischen SPLOOP und SPKERNEL eingelegt werden kann, ohne zu verursachen.
Bei Konflikten zwischen Einheiten wird empfohlen, in erster Linie die Anweisungen auf einem Kernel-Fenster zu mappen/zu planen.
der Größe [ii] :

Achtung vor Konflikten beim Schreiben der Einheiten

Eine letzte wichtige Anmerkung, beim Analysieren des folgenden Codes:

CMATMPY .M1 A3:A2,A7:A6:A5:A4,A11:A10:A9:A8; DS=3


NOP
MPY .M1 A1,A2,A3; DS=1

48
DSP C6000 BAHTAT Mounir
NOP

Die Anweisung CMATMPY wird nach einer Latenz von 3 Zyklen in 4 Register schreiben, während MPY in 1 Register schreiben wird.
nach einer Latenz von 1 Zyklus. Der vorherige Code kann nicht korrekt funktionieren, da bei 5èmeZyklus, die Einheit .M1
versuchen, 160 Bit [5 Register] zu schreiben, während der Ausgangsport der Einheit .M nur 128 Bit hat [laut
die im oben genannten Dokument erwähnte Architektur

Andererseits der folgende Code:

CMPY .M1 A0,A1,A3:A2; DS=3


NOP
AVG2 .M1 A4,A5; DS=1
NOP

Wird ordnungsgemäß funktionieren, auch wenn bei 5èmeCycle, die Einheit .M1 wird das Ergebnis der 2 Anweisungen schreiben; da wir nicht
Stocke que96-Bit [3 Register]

Dies gilt für die Einheit .M, jedoch für die Einheiten .L oder .S, zwei unabhängige Schreibweisen von derselben .L
ou .S können nicht im selben Zyklus durchgeführt werden. Danach ist der folgende Code inkorrekt, selbst wenn .L1 verfügbar ist.
einen Ausgangsport von 64-Bit (INTSPU wird versuchen, in A5 zu schreiben, während DSPINTH versuchen wird, in A4 zu schreiben):

INTSPU .L1 A1,A5; DS=3


NOP
DSPINTH .L1 A3:A2,A4; DS=1
NOP

49

Das könnte Ihnen auch gefallen