Cours DSP Final
Cours DSP Final
2
3.5.4 Parallèlisme entre les itérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.6 Standard assembly : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
LISTE DES ACRONYMES
1
Chapitre 1
1.1 Introduction
Dans les années 1945, John Von Neumann définit des processeurs qui se distinguent de leurs prédéces-
seurs par le fait qu’ils disposent d’un programme composé d’instructions qui doivent être placées dans une
mémoire. Chaque instruction définit une action à réaliser sur des données. Auparavant, le système n’exé-
cutait qu’un seul programme câblé ; chaque exécution d’un nouveau programme nécessitait de le recâbler.
L’ENIAC (Electronic Numerical Integrator and Computer), un des premiers systèmes électroniques achevé
en 1946, est l’exemple type de ce genre de machine. Il occupait une surface de 167 m2 et consommait 150
Kilowatt. Sur cette machine, une multiplication était réalisée en 0,001 seconde ; de nos jours, une telle
opération demande 30 nanosecondes.
1
CHAPITRE 1. GÉNÉRALITÉS SUR LES PROCESSEURS
Une unité d’exécution : appelée aussi Unité Arithmétique et Logique (UAL), elle enclave un ensemble
de circuits arithmétiques et logiques qui permettent au processeur d’effectuer les opérations de calcul. Elle
renferme aussi un registre d’état qui effectue une mise à jour suite à l’exécution de chaque instruction.
Une unité de commande : Elle fait principalement appel à un séquenceur qui pilote le travail du
processeur. Ce dernier est épaulé par décodeur d’adresse, un registre d’instruction et un compteur ordinal
(Programm counter PC). Le PC pointe toujours sur l’adresse de la prochaine instruction à exécuter. Le
pilotage est cadencé au rythme du signal d’horloge qui définit les cycles du processeur.
Une unité de communication :C’est l’ensemble des bus de données, d’adresses et de contrôles qui
permettent à l’information de circuler entre les différents composants du processeur.
Un processeur est un composant actif. Il est principalement caractérisé par son architecture, sa fréquence
de calcul (Hz) et sa capacité d’adressage C@ (Octet) donnée par :
C@ = 2A .D (1.1)
A étant le nombre de bits du bus d’adresse du processeur et D le nombre de bits du bus de données du
processeur.
La mémoire est un module connecté au processeur pour couvrir sa capacité d’adressage. Elle est répartie
en une zone mémoire programme qui renferme les code et une deuxième zone mémoire données qui renferme
les données. Elle peut être constituée d’un ou de plusieurs blocs. Elle est organisée sous forme d’un tableau
de mots représentant une information qui peut être soit une instruction soit une donnée. Chaque mot
mémoire est une information identifiable de façon unique car il est désigné par une adresse qui marque
son rang dans le tableau mémoire.
Une mémoire est principalement caractérisée par son temps d’accès et sa taille T donnée par :
2AM étant le nombre de bits du bus d’adresse du boitier mémoire et DM le nombre de bits du bus de
données du boitier mémoire.
¯ qui doit être impérativement connectée à la masse pour
La mémoire est aussi caractérisée par sa pine CS
marquer l’état de marche du boitier.
Le microprocesseur qui dispose d’un seul bus d’adresse et un seul bus de donnée a une architecture Von
¯ du bus de contrôle va orienter le courant du bus
Newman, comme illustré par la figure 1.3. Le signal RW
de données : en mode de lecture (Read) ou en mode d’écriture (Write). Le bus d’adresse est par contre
orienté vers un seul sens : seul le processeur peut délivrer une adresse à la mémoire.
NB : La taille de la mémoire câblée au processeur ne peut pas dépasser sa capacité d’adressage. Pour
câbler plusieurs boitiers à un processeur on doit se fier à la cartographie mémoire qui constitue le cahier
de charge du système.
A titre d’exemple, on va considérer un processeur 8 bits de capacité d’adressage 1Koct. Pour représenter
son câblage avec des boitiers mémoires organisés comme suit, on doit effectuer méthodiquement les étapes
suivantes :
2. Repérer les bits de sélection puis extraire la table de vérité du décodeur d’adresse.
3. Représentez clairement le câblage entre les deux boitiers mémoires et le processeur.
Solution :
1. La cartographie mémoire : Suite à l’extraction de la cartographie mémoire, donnée par la figure
1.4, le bit A9 est repéré comme étant le bit de sélection. Par conséquent, de (A0 à A8) seront les
bits d’adresses à connecter avec les bus d’adresses des deux boitiers mémoires. En effet, toutes les
combinaisons de (A0 à A8) se référant aux cases mémoires du boitier M1 seront aussi réutiliser pour
sélectionner les cases mémoires du boitier M2. La seule différence c’est la valeur du bit A9.
2. Table de vérité du décodeur d’adresse : L’entrée du module décodeur d’adresse est le bit A9. Les
¯ et CS2.
sorties sont les bits de sélection des deux boitiers mémoires CS1 ¯ Pour générer la table de
vérité, voir tableau 1.1, on va extraire à partir de la cartographie mémoire, l’état des sorties pour les
différentes combinaisons que peut prendre l’entrée.
A9 ¯
CS1 ¯
CS1
0 0 1
1 0 1
3. Le câblage entre le microprocesseur et les deux boitiers mémoire est donné par la figure 1.5 :
Si on procède d’une autre manière, comme représenté par le tableau ci-dessous, 6 cycles seulement seront
nécessaires ce qui permettra une optimisation de 50% en consommation en temps d’exécution. L’exécution
de cette manière, représentée par la figure 1.6 s’appelle pipeline.
L’instant t , où les trois étages du pipeline sont fonctionnelles (F D E), est appelé instant d’amorçage.
A partir de cet instant on démarre le rythme d’exécution d’une instruction par cycle.
Le rectangle bleu de la figure 1.6 marque le fonctionnement simultané de deux étages du pipeline :
D1 : Le registre PC va envoyer l’adresse de I1 (@I1) sur le bus d’adresse par l’intermédiaire de l’unité de
commande qui émet un ordre de lecture. Le contenu de la case mémoire sélectionnée renfermant le code
opératoire de l’instruction (Opc I1) sera transféré via le bus de données vers le registre d’instruction pour
marquer la fin de F1. Pour décoder l’instruction I1 ( D1), le code opératoire qui définit l’opération I1 sera
transféré par l’unité de commande vers l’unité de traitement. Si l’instruction I1 nécessite une opérande,
l’adresse cette dernière (@ Opd1) sera envoyée par le décodeur de l’unité de commande sur le bus d’adresse
pour sélectionner la case cible. La valeur de l’opérande sera récupérée via le bus de données (Val Opd1)
et stockée dans un registre de donnée.
F2 : Au même instant, le pipeline procède à la recherche de l’instruction I2 (F2) : le registre PC va envoyer
l’adresse de I2 ( @I2 ) sur le bus d’adresse par l’intermédiaire de l’unité de commande qui émet un ordre
de lecture. Le contenu de la case mémoire sélectionnée (Opc I2) sera transféré via le bus de données vers
le registre d’instruction pour marquer la fin de F2. On peut clairement constater, Sur la figure 7, le conflit
entre F2 et D1. En effet, à un instant donné, le bus d’adresse affrontera un conflit entre le transfert de @I2
pour la phase F2 et @Opd1 pour la phase D1. De même, un deuxième conflit se présentera sur le bus de
données entre le transfert du code opératoire de l’instruction I1 Opd2 pour la phase F2 et la valeur de
l’opérande I1Val Opd1 pour la phase D1. La limitation de l’architecture Von Newman est principalement
due à la limitation de ses bus.
Figure 1.7 – Conflit des phases d’exécution sur l’architecture Von Newman
Cette limitation a permis de donner naissance à une autre architecture dite : Harvard. Cette dernière
affiche une séparation physique entre les bus de données et d’adresse connectés à la mémoire programme
et ceux connectés à la mémoire données, comme le montre la figure 1.8 .
Avec l’architecture Harvard on peut effectuer la phase F2 simultanément avec la phase D1 comme l’illustre
la figure 1.9. C’est ainsi que l’architecture Harvard permet l’exécution des instructions d’un code en pipeline
entrainant une accélération des temps d’exécutions.
Exemple :
Chaque processeur a son propre jeu d’instruction. Dans cet exemple, on procède à l’exécution du code C1
du tableau 2 constitué de six instructions assembleur indépendantes d’un processeur particulier.
’exécution pipeline du code C1, illustrée par la figure 1.10, montre qu’à l’instant t3, suite à l’exécution de
l’instruction I1 (E1), la valeur 2+3= 5 sera affectée au registre R3. Simultanément, on procède à l’étape
D2 qui consiste à décoder l’instruction « + » et à lire ses opérandes ( 5 + 4 ) et en même temps on
effectue on accomplit l’étape F3. Les trois étages du pipeline sont actifs donc l’amorçage du pipeline est
activé. Normalement, à partir de cet instant on a démarré le rythme d’exécution d’une instruction par
cycle. Arrivé à l’instant t4, E2 permet d’affecter au registre R6 la valeur 9 mais on ne peut pas effectuer
l’opération D3 simultanément car l’opérande R6 n’est pas encore prête à t4. Un cycle d’attente s’impose
(W) et le décodage sera décalé au cycle t5. Le bon fonctionnement du pipeline est altéré et le rythme
d’exécution d’une instruction par cycle est rompu. Cette défaillance est causée par la dépendance de
donnée ou d’adresse au niveau du code. Dans notre cas, en avançant l’instruction I4 (décrémentation du
conteur) avant l’instruction I3, on casse cette dépendance de données sans impacter le bon fonctionnement
du code. En arrivant à l’instant t8, l’exécution de l’instruction de branchement va recharger le registre
PC par l’adresse de l’instruction I1. Par conséquent, Au cycle t9 le pipeline sera altéré et on lancera le
F1 de l’instruction I1 au lieu d’exécuter l’instruction E6 dont la phase « fetch » F6 et « decode » D6 ont
été effectués. Ces derniers cycles seront considérés comme des cycles d’attentes. Quand le conteneur R8
sera nul, la condition du branchement ne sera plus valide, et l’opération E6 sera exécuter. Ce cas de figure
montre que les boucles altèrent gravement le bon fonctionnement du pipeline. Pour éviter ce problème, il
faut dérouler les boucles à très faible nombre d’itérations.
Pour résumer, on peut conclure que les problèmes peuvent causer le disfonctionnement du pipeline sont
principalement : la dépendance de données et d’adresse et les boucles répétitives.
NB : Toutes les étapes d’exécution d’une instruction : FETCH, DECODE et EXECUTE sont automati-
quement synchronisées par le séquenceur qui assure le bon déroulement des opérations. Le programmeur ne
peut pas intervenir, certes, mais il peut éviter les problèmes précédemment cités au niveau de la structure
de leurs codes
Les bus d’adresse et de données du 8086 sont multiplexés pour réduire la taille de son boitier. Qu’il s’agit
d’un cycle de lecture ou d’écriture, l’adresse sera envoyée en premier lieu sur de bus ADi pour sélectionner
la case mémoire en mode lecture ou en mode écriture. Ensuite, un transfert de données sera effectué sur le
bus ADi dans le sens de lecture ou l’écriture. Les opérations de lectures et d’écritures ne seront activées
que si la donnée est disponible sur le bus ADi , comme illustré par la figure 1.11 [1].
Avant de le câbler avec les boitiers mémoires il faut procéder au démultiplexage des signaux AD0 à AD15
et A16/S3 à A19/S6 en mémorisant l’adresse lorsque celle-ci est présente sur le bus A/D, à l’aide d’un
LATCH. Ce dernier se compose de 20 bascules D ayant pour entrées les 20 bits du bus de donnés-adresse
du 8086. Ces bascules sont cadencées par le signal ALE (Address Latch Enable ) du 8086, qui commande
la mémorisation de l’adresse générée par le microprocesseur : Si (ALE=1) c’est que l’adresse est disponible
sur le bus, sinon, elle sera mémorisée. Dans les deux cas de figure l’adresse sera récupérée à la sortie du
LATCH. Le bus de données sera directement connecté aux mémoires depuis les pins Adi du 8086 puisqu’il
ne serait sollicité que quand les actions de lecture ou d’écriture sont activées.[1]
Normalement, avec 16 bits de données et 20 bits d’adresse, le 8086 peut adresser jusqu’à 2 Mo, mais en
examinant son câblage avec les mémoires on peut comprendre la limitation de capacité d’adressage à 1
Mo de mémoire seulement. En effet la figure 12 montre que les boitiers mémoires connectés au 8086 ont
un bus de données de 8 bits. Le boitier pair sera connecté aux 8 bits de poids faible du bus de données
du 8086 et le boitier impair sera connecté aux 8bits de poids fort de son bus de données. En plus la pine
¯ du premier boitier mémoire est activée par le bit d’adresse de poids faible du 8086 A0, et le deuxième
CS
¯
boitier mémoire est sélectionné par la pine BHEdu 8086. Ainsi, ces deux boitiers peuvent être sélectionnés
simultanément, pour manipuler des données sur 16 bits, ou séparément pour traiter des données sur 8 bits.
La division physique du bus de données du 8086 a causé la limitation de sa capacité d’adressage à 1Mo
au lieu de 2Mo pour assurer sa compatibilité avec son prédécesseur, le processeur 8085.[1]
La figure 1.13 détaille l’architecture interne du processeur 8086 qui possède quatre registres auxiliaires
pour manipuler les données (AX, BX, CX, DX), des registres d’index (SI, DI, SP, BP), un registre
d’état (indicateur), un registre IP et des registres de segment (CS, DS, SS, ES). Ces derniers assurent
la gestion de la mémoire par la méthode dite « segmentation ». [1]
L’espace mémoire adressable par le 8086 est divisé en des zones mémoires de 64 Ko appelées segments.
Un segment est défini par son adresse de départ qui doit être un multiple de 16 (les 4 bits de poids faible
sont nuls) ce qui permet de représenter l’adresse d’un segment avec seulement ses 16 bit de poids fort,
les 4 bits de poids faible étant implicitement à 0. Ainsi, une case mémoire est représentée par le 8086 au
moyen de deux quantités sur 16 bits :
- Segment : Les 16 bits de poids fort de l’adresse de début du segment.
- Offset : Les 16 bits représentants le déplacement dans ce segment.
Ainsi, la représentation (segment : offset) définie l’adresse logique utilisée lors de la programmation.
L’adresse sur 20 bits A0 - A19, envoyée réellement par le microprocesseur 8086 sur le bus d’adresse, est
appelée adresse physique. L’architecture x86 est célèbre pour sa compatibilité ascendante, ce qui signifie
que les processeurs plus récents sont capables d’exécuter des programmes écrits pour les versions précé-
dentes de l’architecture. Cette compatibilité a été maintenue tout au long de l’évolution des processeurs
x86 jusqu’au processeur Xeon 64 bits.[1]
1.5 Conclusion
L’évolution des processeurs a été marquée par des progrès constants au fil des décennies, caractérisés par
une augmentation de la puissance de calcul, une amélioration de l’efficacité énergétique, des avancées dans
la technologie de fabrication, et l’intégration de fonctionnalités toujours plus avancées. Dans le chapitre
suivant on étudiera en détail l’architecture interne du processeur de traitement de signal numérique le
TMS320C6416 de la famille C6000 de TI.
2.1 Introduction
Au fil des années, les calculs numériques ont pris de l’ampleur par rapport aux calculs analogiques grâce
aux avantages qu’il offre tels que leur grande résistance aux bruits engendrés par les variations des tensions
d’alimentation, les variations de la température et les interférences électromagnétiques. En plus, le calcul
numérique affiche une indépendance par rapport aux tolérances de fabrication et assure un stockage des
données sans dégradation. Le calcul numérique offre aussi un contrôle absolu des données lors du traitement
avec une programmation haut niveau facile qui peut être effectuée en un temps de développement rapide.
14
CHAPITRE 2. ARCHITECTURE INTERNE DU DSP TMS320C6416
Les données sont représentées comme étant des nombres fractionnaires à virgule fixe, (exemple -1.0 à +1.0),
ou comme des entiers classiques. Cette représentation des nombres fractionnaires s’appuie sur la méthode
du «complément à deux». Elle permet facilement l’addition binaire de nombres positifs et négatifs.
Lors du codage des réels à virgule flottante, représenté par la figure 16, les données sont représentées
en utilisant une mantisse et un exposant. La représentation de ces nombres s’effectue selon la formule
suivante :
Généralement, la mantisse est un nombre fractionnaire (-1.0 à +1.0), et l’exposant est un entier indiquant
la place de la virgule en base 2.
Le DSP TMS320 C6416 avec sa fréquence de 720 MHz, peut exécuter jusqu’à 5760 MIPS. L’architecture
VelociTI.2 de ce DSP fournit huit unités d’exécution : deux multiplicateurs et six unités arithmétiques et
logiques (UAL). Ces unités fonctionnent en parallèle et peuvent effectuer jusqu’à huit instructions en un
seul cycle d’horloge. L’unité centrale de traitement du C64x (CPU), présentée dans Figure 2.5, se compose
de deux groupes de registres A et B à 32 bit, de huit unités fonctionnelles (L1, S1, M1, D1 et L2, S2, M2,
D2) et de deux chemins de données 1x et 2x. La mémoire interne du TMS320C6416 est de 1Moct. Elle est
configurable comme mémoire cache, ISRAM ou comme combinaison des deux [2].
pour les mémoires permettant d’accéder simultanément à deux blocs distincts de mémoire, la mémoire
des programmes et celle des données. Les mémoires caches sont caractérisées par leurs petites tailles. Elles
sont réparties en mémoire cache instruction destiné à loger des parties du code à exécuter et la mémoire
cache données configurable par l’utilisateur.
L1P : Le C6x contient une mémoire cache programme de taille 16Ko ayant un bus de données de 256 bits.
Donc, un paquet de fetch (PF) = 256 bits = 8*32 = 8 instructions.
- TAG (18 bits) : les 18 bits MSB de l’adresse sont stockés dans une mémoire tampon pour indiquer la
présence de la plage d’adresse désirée dans la cache ou non.
- Set Index (9 bits) : un set est une collection de ligne de trame. Dans un système d’adressage direct,
chaque set contient une seule ligne.
- Offset (5 bits) : l’offset dans une ligne permet l’adressage par octet dans un PF.
Exemple : A partir de l’adresse de l’instruction (0X80000250) on peut déterminer l’offset, le set et le tag.
- L1D : Le C64x contient une mémoire cache de données de16 Ko. Il y’a 128 ensembles contenant
chacun 2 lignes cache. Chaque ligne est formée de 64 octets.
- TAG (19 bits) : il représente les MSB de l’adresse. Il sera stocké dans une mémoire tampon pour
indiquer la présence de la plage d’adresse désirée dans le cache ou non (miss / hit).
- Set Index (7 bits) : un ensemble est une collection de ligne de trame. Dans un système d’adressage
associatif par ensemble de deux blocs, chaque ensemble contient deux lignes.
- Group (4 bits) : sélectionne le mot dans un ensemble qui contient les données désirées.
Les bus internes illustrés à la Figure 2.8 comprennent : Un bus d’adresses 32 bits pour les mémoires
programmes et un bus 256 bits, pour les mémoires données, qui accommode Huit unités fonctionnelles
à 32 bits pouvant opérer en parallèle. Un bus d’adresse à 32 bits pour les accès DMA qui va permettre
d’effectuer le transfert de données en parallèle avec les calculs du CPU.
Ce DSP est aussi doté d’un module de transfert DMA qui fonctionne simultanément avec le CPU puisqu’il
dispose de son propre bus de donnée et adresse. Pour réaliser un transfert DMA, on doit configurer un
des 64 canaux DMA disponibles. Chaque canal est contrôlé par un groupe de registres de paramètres
qui fixent : les options de transfert “OPT” décrits au Tableau 2.2, l’adresse source “SRC”, le compteur
du transfert “CNT”, l’adresse de destination “DST”, l’indexe “IDX” et l’adresse de liaison dans le cas de
transfert répétitif “RLD” comme détaillé par le tableau 2.1 [3].
assembleur optimisé à la main d’extension (.asm). La figure 23 illustre les phases d’évolution d’un fichier
source en un fichier exécutable d’extension (.out).
- LDH *A0++[2] entraine une incrémentation du pointeur de 4 octets (2x2octs) après le chargement
de 2octs.
- LDW *A0++[2] entraine une incrémentation du pointeur de 8 octets (2x4octs) après le chargement
de 4octs.
- LDDW *A0++[2] entraine une incrémentation du pointeur de 16 octets (2x8octs) après le chargement
de 8octs.
Si les données à traiter ne sont pas sur le même alignement, il faut utiliser LDNW ou LDNDW qui
peuvent lire une valeur de 32 bits, ou respectivement une valeur de 64 bits, à partir de n’importe quelle
limite d’octet. Ainsi, l’alignement sur une limite de 32 bits, ou respectivement de 64 bits, n’est pas requis.
Il est à noter que ce type de chargement de données ne tolère pas un deuxième accès à la mémoire en
parallèle même si l’autre UAL est disponible. L’autre unité .D peut être utilisée en parallèle, à condition
qu’elle ne fait pas appel un accès mémoire [4].
Le tableau 2.3 résume les différentes opérations arithmétiques et logiques simples que peut effectuer le
TMS320C6416. Chaque opération peut s’exécuter sur des UALs spécifiques. Je vais juste attirer l’attention
sur l’opération de multiplication. Cette opération est typique pour chaque processeur. En multipliant NBits
xNBits le résultat sera stocké sur 2NBits. Donc généralement le processeur procède à la troncature de NBits
ou bien à la concaténation entre deux registres pour sauvegarder le résultat. Pour le TMS320C6416, il
procède à la troncature des opérandes [4] :
MPY(U/US/SU) : cette instruction assure le multiplication des 16lsb x 16lsb des opérandes. Donc seuls
les 16Bits de poids faible des deux opérandes sont concernés par l’opération de multiplication.
MPYH(U/US/SU) : cette instruction assure le multiplication des 16msb x 16msb des opérandes. Donc
seuls les 16Bits de poids fort des deux opérandes sont concernés par l’opération de multiplication.
Le tableau 2.4 résume les différentes opérations arithmétiques et logiques de type SIMD (Single instruction
multiple data) du jeu d’instruction du TMS320C6416. Ces instructions procèdent par paquet et peuvent
exécuter un groupe de sous instructions adaptées au TNS. Les instructions SUBABS4 et DOTPU4 dé-
taillées si dessous sont très utilisées pour les applications TSN pour calculer le critère de comparaison et
pour les opérations de filtrage. SUBABS4 [4] : cette fonction effectue quatre valeurs absolues de différences
en un seul cycle sur 8bits séparément.
DOTPU4 [4] : cette fonction effectue la somme de quatre produits sur 8bits séparément, en un seul cycle.
Elle est très utilisée pour les opérations de filtrage ou pour reprendre des calculs sur des valeurs simples
après des instructions par paquet.
2.7 Conclusion
La programmation assembleur un DSP demeure une approche très intéressante. Etant donné que les per-
formances de traitement sont cruciales, l’assembleur est le seul langage qui permet d’utiliser toutes les
possibilités spécifiques du DSP. Dans le chapitre suivant on détaillera les principales techniques d’optimi-
sation assembleur.
3.1 Introduction
L’optimisation en langage d’assembleur implique l’amélioration des performances d’un programme en
ajustant le code source assembleur pour qu’il s’exécute de manière plus efficace. Les techniques courantes
d’optimisation en langage d’assembleur seront exploitées dans ce chapitre. Il faut commencer par effectuer
l’ordonnancement des instructions en réorganisant le code pour réduire les retards dus aux dépendances
de données et améliorer le flux d’exécution. Il est aussi nécessaire de maximiser l’utilisation des registres
pour éviter l’accès à la mémoire. Pour une meilleure performance, il sera indispensable de préparer un bon
design qui permettra d’exploiter le parallélisme d’instructions par paquet.
Comme tout processeur, 3 étages sont alloués à l’exécution des instructions : Fetch, Decode et execute. La
figure 3.2 illustre ces étages ainsi que le nombre de cycles nécessaire alloués.
27
CHAPITRE 3. TECHNIQUES D’OPTIMISATION ASSEMBLEUR
Le paquet fetch (PF), formé de 8 instructions, peut contenir 1 PE (si toutes les 8 instructions en //) ou
8 PE (pas d’inststructions en // ). Le LSB d’une instruction (p-bit) indique si la prochaine instruction
appartiennent au même PE (si 1) ou non (si 0). La phase Fetch du processus d’exécution d’une instruction
du DSP TMS320C6416, illustrée par la figure 3.3, nécessite quatre cycles :
PG : Génération de l’adresse par le CPU
PS : Transmission de l’adresse à la mémoire.
PW : Attente de l’accès à la mémoire.
PR : Réception et lecture du PF.
La phase de décodage d’une instruction du DSPTMS320C6416, illustré par la figure , se déroule en deux
cycles :
DP : Répartition des instructions d’un PF sous formes de PE.
DC : Décodage des différentes PEs.
La figure 3.7 établit le fait qu’un étage d’exécution d’une instruction du TMS320C6416 peut nécessiter de
un à six cycles, en fonction de l’instruction à exécuter.
La majorité des instructions s’exécutent en un seul cycle. Cependant quelques instructions dites longues
nécessitent plus de cycles :
- Les instructions de multiplication qui s’effectuent en deux phases (addition et décalage) nécessitent
deux cycles pour s’exécuter.
- D’autres instructions par paquet comme DOTPU4 nécessitent quatre cycles pour l’exécution.
- Les instructions de chargement et de stockage nécessitent un temps supplémentaire pour permettre
l’accès à la mémoire. Cinq cycles sont alloués pour la phase d’exécution de ces instructions.
- Les instructions de branchement vont entrainer une mise à jours de l’ordre d’exécution des instruc-
tions ce qui explique qu’ils nécessitent 6 cycles pour l’exécution.
Comme illustré par la figure 3.8,l ’algorithme SAD calcule la somme de la valeur absolu de la différence
entre un bloc source, d’adresse de début SRS et de taille N×N pixels, et d’une région de taille NxN pixels
dans image de référence de résolution CIF (Common Intermediate Format) (352×288) pixels. L’adresse
de début de cette région de l’image référence est REF.
Le code de la fonction SAD en langage C sur une vidéo de résolution CIF, pour N= 2, est donné par :
Si on exécute le programme sur le C64, il consommera 85 Cycles/SAD. Le résultat n’est pas satisfaisant,
voyant de prés le code assembleur généré par le compilateur du CCS. L’affectation des paramètres d’appel
aux registres interne est illustrée si dessous :
E1 E2 E3 E4 E5 E6 Résultat
MVK A20=0
MVK A14=2
MVK A12=2
LDBU A0=0xF001
LDBU LDBU A2=0xE001
SUB LDBU LDBU A12=1
B L1 LDBU LDBU
NOP B L1 LDBU LDBU
NOP B L1 LDBU
SUB B L1 A4=a0-b0
ABS B L1 A4 = |a0 − b0|
ADD B L1 A20 = |a0 − b0|
LDBU A0=0xF002
LDBU LDBU A2=0xE002
SUB LDBU LDBU A12=0
B L1 LDBU LDBU
NOP B L1 LDBU LDBU
NOP B L1 LDBU
SUB B L1 A4=a1-b1
ABS B L1 A4 = |a1 − b1|
ADD B L1 A20 = |a0 − b0| + |a1 − b1|
SUB A14=1
B L2
ADD B L2 A0=0xF160
NOP B L2
NOP B L2
NOP B L2
NOP B L2
MVK A12=2
LDBU A0=0xF161
LDBU LDBU A2=0xE003
SUB LDBU LDBU A12=1
B L1 LDBU LDBU
NOP B L1 LDBU LDBU
NOP B L1 LDBU
SUB B L1 A4=a2-b2
ABS B L1 A4 = |a2 − b2|
ADD B L1 A20 = |a0 − b0| + |a1 − b1| + |a2 − b2|
LDBU A0=0xF162
LDBU LDBU A2=0xE004
SUB LDBU LDBU A12=0
B L1 LDBU LDBU
NOP B L1 LDBU LDBU
NOP B L1 LDBU
SUB B L1 A4=a3-b3
ABS B L1 A4 = |a3 − b3|
ADD B L1 A20=|a0-b0|+| a1-b1|+|a2-b2|+|a3-b3|
SUB A14=0
B L2
ADD B L2 A0=0xF2C0
NOP B L2
NOP B L2
NOP B L2
NOP B L2
Return A20
E1 E2 E3 E4 E5 E6 Résultat
MVK A20=0
MVK A12=2
MVK L A11=0x01010101
MVK H A11=0x01010101
LDNW A0=0xF160
LDW LDNW A1=0xE004
NOP LDW LDNW
NOP LDW LDNW
NOP LDW LDNW 4|3|2|1
SUB LDW 3|2|1|0
B 1|1|1|1
SUBABS4
DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B A5=4
ADD B A20=4
LDNW A0=0xF2C0
LDW LDNW A1=0xE008
NOP LDW LDNW
NOP LDW LDNW
NOP LDW LDNW 8|7|6|5
SUB LDW 7|6|5|4
B 1|1|1|1
SUBABS4
DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B A5=4
ADD B A20=8
LDNW A0=0xF420
LDW LDNW A1=0xE00C
NOP LDW LDNW
NOP LDW LDNW
NOP LDW LDNW 12|11|10|9
SUB LDW 11|10|9|8
B 1|1|1|1
SUBABS4
DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B A5=4
ADD B A20=12
LDNW A0=0xF580
LDW LDNW A1=0xE010
NOP LDW LDNW
NOP LDW LDNW
NOP LDW LDNW 16|15|14|13
SUB LDW 15|14|13|12
B 1|1|1|1
SUBABS4
DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B
NOP DOTPU4 B A5=4
ADD B A20=16
E1 E2 E3 E4 E5 E6 Résultat
MVK A20=0
MVK A12=2
MVKL A11=0xXXXX0101
MVKH A11=0x01010101
MV B11=0x01010101
ADD B0=0xF2C0
ADD B1=0xE008
LDNW A0=0xF160
LDNW LDNW B0=0xF420
LDW LDNW LDNW A0=0xE004
LDW B0=0xE00C
SUB LDW LDNW A12=1
LDW LDNW
NOP LDW LDNW LDNW A2= 4|3|2|1
LDW
NOP LDW LDNW B2= 12|11|10|9
LDW
NOP LDW A3= 3|2|1|0
LDW B3= 11|10|9|8
B
SUBAB S4 A4= 1|1|1|1
SUBABS4 B4= 1|1|1|1
DOTPU4 B
DOTPU4
NOP DOTPU4 B
DOTPU4
NOP DOTPU4 B
DOTPU4
NOP DOTPU4 B A5= 4
DOTPU4 B5= 4
ADD B A20= 4
ADD B20= 4
LDNW A0=0xF2C0
LDNW LDNW B0=0xF580
LDW LDNW LDNW A0=0xE008
LDW B0=0xE010
SUB LDW LDNW LDNW A12=0
LDW
NOP LDW LDNW LDNW A2= 8|7|6|5
LDW
NOP LDW LDNW B2= 16|15|14|13
LDW
NOP LDW A3= 7|6|5|4
LDW B3= 15|14|13|12
B
SUBAB S4 A4= 1|1|1|1
SUBABS4 B4= 1|1|1|1
DOTPU4 B
DOTPU4
NOP DOTPU4 B
DOTPU4
NOP DOTPU4 B
DOTPU4
NOP DOTPU4 B A5= 4
DOTPU4 B5= 4
ADD B A20= 8
ADD B20= 8
ADD A20= 16
L’implementation de code .sa de la fonction SAD ayant comme variables d’entrées :Unsigned short SAD
(unsigned char *src, unsigned int srcPitch, unsigned char *ref, unsigned int refPitch, unsigned int height,
unsigned int width) est donnée par l’annexe. L’interprétation du son fichier SPI "Software pipeline in-
formation" du code (.sa), qui représente la distribution des instructions sur les UAL L, S, D et M des
deux unités A et B, montre que notre code est optimum. En effet, on constate un équilibre parfait de la
répartition des instructions entre les deux unités A et B. Le SPI montre que le kernel de la boucle s’exécute
sur trois cycles avec cinq itérations en parallèles.
Le SPI résume convenablement le code hand assembly représenté par la figure 3.11. Une phase d’initiali-
sation « EPILOG » est nécessaire pour créer les pointeurs, les données et des variables initiales. On peut
constater qu’ils sont bien répartis sur les deux unitésA et B du DSP. Le « KERNEL » de la boucle prend
trois cycles et atteint le maximum de huit instructions exécutées en parallèle en un seul cycle. Finalement,
la continuité des itérations lancées par le KERNEL achèvent leurs exécutions à la phase « PROLOG ».
Pour finir, il faut s’assurer que le code et les données d’une boucle de manière à tirer parti de la hiérarchie
de la mémoire cache et à minimiser les accès à la mémoire principale, qui sont généralement plus lents.
3.7 Conclusion
Il est important de noter que l’optimisation en langage d’assembleur peut être complexe et dépend forte-
ment de l’architecture matérielle cible. De plus, dans de nombreux cas, les compilateurs modernes peuvent
effectuer des optimisations automatiques, rendant la programmation en assembleur direct moins néces-
saire dans de nombreux scénarios. Avant de plonger dans l’assembleur, il est généralement recommandé de
profiler et d’optimiser le code à un niveau plus élevé. Justement, l’assembleur linéaire peut offrir un bon
compromis entre l’efficacité du code et l’effort de codage.
;***************************************************** *******************
.global _sad_sa64_16x16
_sad_sa64_16x16: .cproc A_src, B_srcPitch, A_ref, B_refPitch, A_height, B_width
En définissant les variables d’entrée, la première sera automatiquement affiliée à un registre de l’Unité
A, la deuxième à un registre de l’Unité B et ainsi de suite. Il est donc préférable d’intégrer l’unité
affiliée au registre au niveau de son nom. Un maximum de Dix variables d’entrée est toléré.
.no_mdep
Pour définir les registres des variables autres que les variables d’entrée, on utilise( .reg)
.reg B_src, B_ref, A_srcPitch, A_refPitch
;A side registers
.reg A_srcPix7654:A_srcPix3210, A_refPix7654:A_refPix3210
.reg A_absDiff3210, A_absDiff7654
.reg A_sumOfAbs, A_srcOffset, A_refOffset
.reg A_packSumOfAbsDiff0, A_packSumOfAbsDiff1
.reg A_const1111
;B side registers
.reg B_srcPix7654:B_srcPix3210, B_refPix7654:B_refPix3210
.reg B_absDiff3210, B_absDiff7654
.reg B_sumOfAbs, B_srcOffset, B_refOffset
.reg B_packSumOfAbsDiff0, B_packSumOfAbsDiff1
.reg B_const1111
.reg loopCntX, loopCntY
****************************************************************************
SHR.1x B_srcPitch, 3, A_srcPitch
SHR.2 B_srcPitch, 3, B_srcPitch
MV.1x B_refPitch, A_refPitch
ADD.2x A_src, 8, B_src
ADD.2x A_ref, 8, B_ref
ZERO.1 A_sumOfAbs
ZERO.2 B_sumOfAbs
MVKL.1 0x0101, A_const1111
MVKH.1 0x01010101, A_const1111
MV.2x A_const1111, B_const1111
SUB A_height, 2, loopCntY
loopY: .trip 16, 16, 16
LDDW.1 *A_src++[A_srcPitch], A_srcPix7654:A_srcPix3210
LDNDW.1 *A_ref++(A_refPitch), A_refPix7654:A_refPix3210
SUBABS4.1 A_srcPix3210, A_refPix3210, A_absDiff3210
SUBABS4.1 A_srcPix7654, A_refPix7654, A_absDiff7654
DOTPU4.1 A_absDiff3210, A_const1111, A_packSumOfAbsDiff0
DOTPU4.1 A_absDiff7654, A_const1111, A_packSumOfAbsDiff1
ADD.1 A_sumOfAbs, A_packSumOfAbsDiff0, A_sumOfAbs
ADD.1 A_sumOfAbs, A_packSumOfAbsDiff1, A_sumOfAbs
LDDW.2 *B_src++[B_srcPitch], B_srcPix7654:B_srcPix3210
LDNDW.2 *B_ref++(B_refPitch), B_refPix7654:B_refPix3210
SUBABS4.2 B_srcPix3210, B_refPix3210, B_absDiff3210
SUBABS4.2 B_srcPix7654, B_refPix7654, B_absDiff7654
DOTPU4.2 B_absDiff3210, B_const1111, B_packSumOfAbsDiff0
DOTPU4.2 B_absDiff7654, B_const1111, B_packSumOfAbsDiff1
ADD.2 B_sumOfAbs, B_packSumOfAbsDiff0, B_sumOfAbs
ADD.2 B_sumOfAbs, B_packSumOfAbsDiff1, B_sumOfAbs
BDEC loopY, loopCntY
ADD.1x A_sumOfAbs, B_sumOfAbs, A_sumOfAbs
.return A_sumOfAbs
.endproc
TD1 : Interface mémoire et microprocesseur
Exercice1 :
En examinant le câblage de la figure 1(a) et le contenu des cases mémoires adressées par le microprocesseur
figure 1, déterminez le contenu des boitiers mémoires M1 et M2 figure 1.
Figure 1 – Cablage
Exercice2 :
On considère le schéma suivant, représentant l’interconnexion d’un microprocesseur et de trois boîtiers
mémoires (on n’a pas représenté les lignes de lecture et d’écriture) :
46
1. Indiquer la capacité de chacun des boîtiers mémoire.
2. Donner la taille de la mémoire totale adressable par ce microprocesseur.
3. Donner, pour chaque boîtier mémoire, la plage d’adresses qu’il occupe dans l’espace d’adressage du
microprocesseur.
4. On désire ajouter à ce schéma un quatrième boîtier d’une capacité de 8koct. Comment doit-on
connecter ce boîtier afin qu’il occupe la plage d’adresse E000h-FFFFh ?
Exercice3 :
Soit un microprocesseur 32bits de capacité d’adressage 2Goct, représenter son câblage avec des boitiers
mémoire organisés comme suit :
Exercice1 :
L’implémentation assembleur de la fonction SumAbsDiff sur le DSP TMS 320C6416 est donnée par :
[Initialisation]
Loop: LDW *A2++, A4
|| LDW *B2++, B4
LDW *A0++[A20], A3
|| LDW *B0++[B20], B3
SUB A14,1,A14
NOPX
[A14 ] B Loop
|| SUBABS4 A3, A4,A5
|| SUBABS4 B3, B4,B5
DOTPU4 A5,A11, A6
|| DOTPU4 B5,B11, B6
NOPY
ADD A6,A10,A10
|| ADD B6, B10,B10
ADD A10, B10, A10
. return A10
48
Exercice2 :
1. Integrez les instructions necessaires à l’initialisation des registres A12, B0, B2, A30, B30,A11, B11.
2. Incorporez les NOPs necessaires au bon fonctionnement du code. N’oubliez pas de placer l’instruction
de branchement avec sa conditionet fixer toutes les UALs utilisées.
3. Effectuez l’exécution pas à pas de ce code en tenant compte des adresses et des valeurs des pixels.
4. Proposez un code optimal du sad 8x8.
Bibliographie
[1] D. J. Haggège, “Support de cours « microprocesseur » ; 4ème niveau génie electrique,” 2003.
[2] T. Instruments, “Tms320c6000 cpu and instruction set reference guide,” Document technique :
SPRU190D, Fevrier 2001.
[3] T. Instruments, “Writing dsp/bios device drivers for block i/o,” Document technique : SPRA802A,
Fevrier 2003.
[4] T. Instruments, “Tms320c6000 cpu and instruction set reference guide,” Document technique :
SPRU189F, October 2000.
[5] W. J. Vassiliadis S, Hakkennes E .A and P. G.G, “The sum absolute difference motion estimation
acceleator,” 1998.
51