Fonctionnement d'un ordinateur/Les processeurs de traitement du signal
Les processeurs de traitement du signal, sont des jeux d'instructions spécialement conçus pour travailler sur du son, de la vidéo, des images, ou toute autre forme de signal. Ils sont aussi appelés des DSP, abréviation de Digital Signal Processor. Le jeu d'instruction d'un DSP est assez spécial, car il est conçu pour des applications très spécifiques. Et la conséquence est que leur jeu d'instruction est complétement à part du reste, au point où leur donner un chapitre à part est une nécessité.
Contexte : le traitement temps réel d'un signal
[modifier | modifier le wikicode]Le traitement du signal regroupe tout ce qui traite de l'audio, de la vidéo, mais aussi d'autres formes de signaux plus difficiles à conceptualiser. Les cas d'utilisations les plus courant sont le traitement d'image (appareils photos), la compression et le filtrage vidéo, les cartes sons d'un ordinateur ou d'une console de jeu, les communications sans fil avec des périphériques, la téléphonie, et autres usages moins familiers (radars, imagerie médicale).
Le traitement de signal était autrefois réalisé par des composants purement analogiques. Les circuits analogiques de ce type étaient utilisés dans les anciennes radios, les chaines HI-FI, les télévisions, les magnétoscopes, et bien d'autres composants électroniques moins familiers. De nos jours, le signal est traité par des processeurs numériques. Un système audio/vidéo/autres fonctionne cependant encore avec des signaux analogiques. Simplement, il y a une conversion analogique vers numérique, un traitement par un DSP, puis une conversion numérique vers analogique.


Un flux de données échantillonné
[modifier | modifier le wikicode]Le signal sonore/vidéo/autre qui est capté est un signal analogique : il change en permanence, il n'a pas de fréquence définie. Mais ce signal est échantillonné, à savoir que l'on mesure sa valeur à une fréquence prédéterminée, appelée la fréquence d’échantillonnage. Par exemple, pour un signal sonore, la fréquence d’échantillonnage est de 44,1 kHz, 48 kHz, 96 kHz ou 192 kHz. Soit une mesure approximativement toutes les 22,6 µs, 20,83 µs, 10,4 µs, 5,2 µs. L'intensité sonore mesurée à un instant est appelée un échantillon sonore. Il existe un équivalent pour la vidéo : les échantillons sont les images à afficher à l'écran, il y en a une toutes les 1/24ème de secondes pour une vidéo à 24 FPS.

Les échantillons sont généralement accumulés dans une structure de donnée en mémoire RAM, appelée une file. Il s'agit d'un paquet d'échantillon classés par ordre d'arrivée (une structure de donnée de type FIFO). Elle a une taille finie, ce qui fait que le nombre d'échantillons est prédéfini à l'avance. Quand un échantillon est ajouté dans une FIFO pleine, la donnée la plus ancienne est éliminée (elle a déjà été traitée de toute façon).
Les FIFOs de ce type sont conçues à partir d'un tableau, auquel on a ajouté deux pointeurs : un pour la donnée la plus ancienne, un pour la plus récente. Pour le dire autrement, ces deux pointeurs correspondent au début de la file et à sa fin. Le début de la file correspond à l'endroit où l'on insère les nouvelles données. La fin de la file correspond à la donnée la plus ancienne en mémoire. À chaque ajout de donnée, on doit mettre à jour l'adresse de début de file. Lors d'une suppression, c'est l'adresse de fin de file qui doit être mise à jour.
Ce tableau a une taille fixe. Si jamais celui-ci se remplit jusqu'à la dernière case, (ici la cinquième), il se peut malgré tout qu'il reste de la place au début du tableau : des retraits de données ont libéré de la place. L'insertion continue alors au tout début du tableau. Cela demande de vérifier si l'on a atteint la fin du tableau à chaque insertion. De plus, en cas de débordement, si l'on arrive à la fin du tableau, l'adresse de la donnée la plus récemment ajoutée doit être remise à la bonne valeur : celle pointant sur le début du tableau. Tout cela fait pas mal de travail. Les DSPs ont des modes d'adressages spécialisés pour accéder à des données dans de telles files, comme on le verra plus bas.
Les algorithmes exécutés par un DSP
[modifier | modifier le wikicode]Un DSP exécute des algorithmes très précis : un algorithme de filtrage, un algorithme de transformée de Fourier rapide, un algorithme de Finite Impulse Response, des algorithmes de convolution, ou tout autre algorithme de traitement de signal. L'algorithme travaille sur un nombre fini d'échantillons, qui sont lus depuis la file décrite plus haut.
Le jeu d'instruction d'un DSP est optimisé pour les algorithmes de traitement de signal les plus courants. Aussi, pour comprendre le jeu d'instruction d'un DSP, nous n'avons pas le choix : il faut étudier quelques algorithmes de traitement de signal. Mais rassurez-vous, pas besoin d'aller dans le détail. Nous allons voir quelques algorithmes simples, et encore : nous allons les survoler, sans expliquer pourquoi et comment ils marchent.
L'exemple le plus utile pour l'étude des DSP est celui du filtre FIR (Finite Impulse Response). Celui-ci est assez simple sur le principe : on prend les N échantillons les plus récents, on les multiplie chacun par un coefficient, et on additionne le tout. La formule exacte ressemble à ceci :
- , avec le coefficient de l'échantillon à l'instant t-n.

Vous remarquerez que cet algorithme s'implémente avec une boucle, chaque itération faisant une multiplication suivie d'une addition. Si on suppose que les N échantillons sont mémorisés dans un tableau, et que les N coefficients sont dans un second tableau, alors le code devrait être le suivant :
int resultat = 0 ;
for (i=0 ; i < N ; ++i)
{
resultat += coefficient[i] * echantillons[i] ;
}
Et c'est une règle pour de nombreux algorithmes de traitement de signal : ils s'implémentent avec une boucle, qui parcourt un ou plusieurs tableaux/files, l'intérieur de la boucle faisant des calculs du type a * b + c.
Il est intéressant de regarder ce que donne le codé précédent, une fois compilé sur une architecture RISC. Un point important est que ce code manipule quatre variables par itération de boucle : les deux opérandes de la multiplication, le résultat de la multiplication, et la variable d'accumulation resultat. On va placer les deux opérandes dans les registres R0 et R1, le résultat de la multiplication dans le registre R2, et la variable resultat dans le registre R3. Le compteur de la boucle est mémorisé dans le registre R7.
Voici une sorte de pseudo-code ASM qui ressemble pas mal à ce que sortirait un compilateur, avec pas mal de simplifications de notations pour faire passer la pilule. Les commentaires indiquent qu'une étape de calcul d'adresse est réalisée, en utilisant plusieurs instructions.
// Calcul adresse coefficient
// Calcul adresse opérande
LOAD (adresse opérande N) -> R0 ;
LOAD (adresse coefficient N) -> R1 ;
MUL RO , R1 -> R2
ADD R2 , R3 -> R3
INC R7 ;
CMP R7 N ;
BRANCH adresse de début de la boucle ;
En clair, on charge les deux opérandes dans un registre, on multiplie, on additionne, puis on effectue de quoi gérer la boucle.
La transformée de Fourier rapide est un algorithme un peu plus complexe que le précédent, mais particulièrement utile en traitement de signal. Sans rentrer dans les détails d'implémentation, il agit sur des nombres complexes, qui sont composés d'une partie réelle et la partie imaginaire. Les parties entières et imaginaires sont encodés avec deux nombres entiers ou flottants. L'algorithme utilise un tableau de nombres complexes, qui est en pratique séparé en deux tableaux séparées, avec un tableau pour les parties réelles et un autre pour les parties imaginaires.
La transformée de Fourier rapide est composée d'une boucle qui applique à chaque itération une opération dite butterfly. Elle prend en entrée un nombre complexe, et fournit un autre nombre complexe en sortie. Elle applique des multiplications/additions sur deux variables qu'on appellera A et B. À chaque itération de boucle, l'opération butterfly exécute le calcul suivant :
Retenez bien que cette opération s'appelle l'opération butterfly, car les DSPs intègrent des optimisations dédiées pour cette opération.
Les contraintes dites temps réel
[modifier | modifier le wikicode]Le DSP exécute l'algorithme de traitement de signal entre deux arrivées d'échantillon. Précisément, le DSP est commandé par une interruption. Lorsqu'un nouvel échantillon est disponible, le CAN envoie une interruption au DSP pour le prévenir. Le DSP lit alors l'entrée correspondant au CAN et récupère cet échantillon dans un registre. Il met alors à jour la file des échantillons, met à jour le pointeur qui indique le début de la file. Puis il exécute l'algorithme de traitement de signal. Une fois terminé, il envoie le résultat au CNA.
Il y a donc un délai temporel très strict à respecter : le traitement doit être fini avant l'arrivée du prochain échantillon. Cette contrainte dite temps réel font qu'il est préférable de ne pas utiliser de mémoire virtuelle, d'interruptions, ou beaucoup d'autres fonctionnalités courantes sur les processeurs modernes. Par exemple, les branchements sont une source de problèmes pour le temps réel. Le temps d'exécution du code change selon que le branchement est pris ou non, les deux codes exécutés suivant que la condition est valide ou non ne faisaient pas forcément le même temps. En conséquence, les DSP incorporent des instructions à prédicats pour remplacer les branchements hors-boucles, et ajoutent des techniques pour accélérer les boucles.
La présence de caches est une autre source de problèmes dans les systèmes temps réel, car le temps d'exécution dépend de si les accès mémoire font des succès ou des défauts de cache. En conséquence, les premiers DSP commercialisés n'utilisaient pas de mémoire cache pour les données, et se limitent à des caches d'instructions. L'absence de cache est compensée l'usage de local store, dans lesquels des échantillons sont accumulés. Les local store sont souvent alimentés par des transferts DMA, qui font des copies local store vers mémoire RAM ou inversement. Les local store ne posent pas de problèmes pour le temps réel, car le programmeur sait à tout moment quelles sont les données présentes dans un local store, ce qui n'est pas le cas dans un cache.
De même, beaucoup d'optimisations posent problème avec le temps réel, parce qu'elles rendent le temps d'exécution plus variable. L'usage d'un pipeline est parfaitement possible, mais sous certaines conditions. La plus importante est qu'il faut utiliser l'émission dans l'ordre. Pas question d'utiliser d'exécution dans le désordre, de prédiction de branchement ou toute autre optimisation du genre. Dans les faits, presque tous les DSP commercialisés après les années 90 utilisent un pipeline. L'usage de l'émission multiple est parfaitement possible, et de nombreux DSP récents sont soit superscalaires, soit des CPU VLIW. La seconde solution est plus souvent utilisée, la compatibilité matérielle n'étant pas importante sur les DSPs.
Le jeu d'instruction d'un DSP
[modifier | modifier le wikicode]Les DSPs incorporent de nombreuses optimisations spécifiques, pour optimiser les algorithmes de traitement de signal. Et ces optimisations peuvent se comprendre assez facilement quand on analyse le code d'un filtre FIR. Pour rappel, il s'agit du code assembleur vu plus haut, que je reproduis ici :
// Calcul adresse coefficient
// Calcul adresse opérande
LOAD (adresse opérande N) -> R0 ;
LOAD (adresse coefficient N) -> R1 ;
MUL RO , R1 -> R2
ADD R2 , R3 -> R3
INC R7 ;
CMP R7 N ;
BRANCH adresse de début de la boucle ;
Il est intéressant d'étudier comment la boucle précédente peut être optimisée, avec un jeu d'instruction adapté, car ces optimisations se généralisent à tous les algorithmes de traitement de signal. Optimiser la boucle précédente demande d'optimiser plusieurs points : optimiser les calculs d'adresse, optimiser les lectures, optimiser les calculs arithmétiques, optimiser la boucle elle-même (les trois instructions de fin). Les DSPs incorporent des optimisations pour chaque point, voyons lesquelles.
L'optimisation des boucles sur un DSP
[modifier | modifier le wikicode]Premièrement, on doit réduire le temps passé dans les tests et branchements au minimum. Sans optimisations particulières, il faut incrémenter l'indice, faire la comparaison, et le branchement conditionnel. L'intérieur de la boucle consiste en deux lectures, une addition et une multiplication, soit quatre instructions. Si on fait les comptes, un peu moins de la moitié des instructions est passé à gérer la boucle FOR. Pour éviter cela, les DSP ont des instructions qui effectuent un test, un branchement et une mise à jour de l'indice en un cycle d'horloge. Le compteur de boucle, qui compte le nombre d'itérations restantes, est placé dans un registre dédié pour les compteurs de boucles.
Autre fonctionnalité : les instructions autorépétées, des instructions qui se répètent automatiquement tant qu'une certaine condition n'est pas remplie. L'instruction effectue le test, le branchement, et l’exécution de l'instruction proprement dite en un cycle d'horloge. Cela permet de gérer des boucles dont le corps se limite à une seule instruction. Cette fonctionnalité a parfois été améliorée en permettant d'effectuer cette répétition sur des suites d'instructions.
Les DSPs incorporent aussi des caches d'instructions, afin de gagner de précieux cycles d'horloge. En général, les caches d'instructions en question sont spécialisés dans l'exécution de petites boucles, qui tiennent entièrement dans le cache. Ils incorporent aussi des techniques de zero overhead looping, qui permet d'exécuter des boucles sans avoir à utiliser de branchements, ou presque.
Pour rappel, ces techniques délimitent les instructions dans le code avec une instruction REPEAT. Celle-ci précise que les N instructions suivantes doivent s'exécuter en boucle, N fois. Typiquement, elles permettent d'implémenter des boucles FOR dont le nombre d’exécution est fixe, ou du moins stocké dans un registre. La répétition de la boucle est contrôlée par un registre de boucle, qui mémorise le nombre de répétitions, et qui est décrémenté à chaque itération.
Une variante précise deux adresses, qui délimitent les instructions de la boucle : une adresse pour le début de la boucle, une adresse pour la fin. L'implémentation hardware est alors assez simple : quand le program counter atteint l'adresse de fin, il est réinitialisé à l'adresse de début.
Avec ces techniques, le code ASM d'un filtre FIR devient ceci :
LOOP N X // répète les X instructions suivantes, N fois
// Calcul adresse opérande
// Calcul adresse coefficient
LOAD (adresse opérande N) -> R0 ;
LOAD (adresse coefficient N) -> R1 ;
MUL RO , R1 -> R2
ADD R2 , R3 -> R3
Les opérations arithmétiques d'un DSP
[modifier | modifier le wikicode]Voyons maintenant quelles optimisations peuvent être réalisées pour les opérations arithmétiques. Le calcul à faire est en soi très simple : une multiplication suivie d'une addition. Aussi, vous ne serez pas étonnés d'apprendre que tous les DSP supportent les instructions Multiply and Add (MAD), qui effectuent une multiplication suivie d'une addition. Pour rappel, la première travaille sur des opérandes entiers, la seconde des opérandes flottants. Utiliser une instruction MAD simplifie donc la boucle, sans compter que cela fait économiser un registre, vu qu'on n'a pas besoin de stocker le résultat de la multiplication. Et non seulement les DSPs intègrent une instruction MAD, mais ils ont aussi une unité de calcul dédiée à cette opération.
Un autre point important est que l'addition sert juste à ajouter le produit à une variable temporaire. A chaque itération de la boucle, la variable est incrémentée avec le produit a*b. Il s'agit d'un calcul d'accumulation, qui se marie très bien avec la présence d'un registre accumulateur. Les DSPs incorporent donc un registre accumulateur dans l'unité de calcul MAD, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent un opérande depuis l'accumulateur et mémorisent leur résultat dedans. Il s'agit donc d'instructions MAD un peu spéciales, appelées multiply and accumulate (MAC) ou fused multiply and accumulate (FMAC). Nous parlerons d'instructions MAC dans ce qui suit.

Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci :
LOOP N X // répète les X instructions suivantes, N fois
// Calcul adresse opérande
// Calcul adresse coefficient
LOAD (adresse opérande N) -> R0 ;
LOAD (adresse coefficient N) -> R1 ;
MAC RO , R1
De nombreux DSPs utilisent deux accumulateurs, pour accélérer l'opération butterfly des transformées de Fourier rapides. Mieux que ça : ils intègrent une instruction de multiplication complexe qui effectuent deux opérations MAC, sur les deux accumulateurs, en une seule instruction.
Les instructions MAC n'ont besoin d'adresser que deux opérandes : celles de la multiplication. L'opérande de l'addition est dans un accumulateur adressé implicitement. Du moins, s'il y a un seul accumulateur. De nombreux DSPs ont entre 2 et 8 accumulateurs, même si la norme est à 2 accumulateurs. Dans ce cas, les accumulateurs étant séparés des autres registres, son adressage est un peu particulier. Les accumulateurs ont des numéros séparés des autres numéros/noms de registres.
| Opcode | Premier opérande | Second opérande | Opérande addition/résultat |
|---|---|---|---|
| Opcode | Numéro de registre ou adresse mémoire | Numéro de registre ou adresse mémoire | Numéro d'accumulateur |
| Un octet, environ | Variable | Variable | 1 à 3 bits, selon le nombre d'accumulateurs |
Les modes d'adressage d'un DSP
[modifier | modifier le wikicode]Une autre source d'optimisation est liée aux calculs d'adresse. Les échantillons ne sont pas stockés dans un tableau, mais dans une file. La différence n'est pas énorme, car les files sont souvent implémentées par des tableaux, associés à deux pointeurs : un qui donne la position de la donnée la plus ancienne, un autre pour la donnée la plus récente.

Le tableau commence à être rempli à partir de sa première case, d'indice 0. Les données accumulées ensuite sont ajoutées dans la case d'indice 12, puis 2, puis 3, etc. Les données devenues inutiles sont retirées de la FIFO, ce qui laisse des vides, qui peuvent être réutilisées par la suite. Quand on arrive à la fin du tableau, le remplissage recommence à partir du début du tableau, si des espaces vides ont été libérés. Voici un exemple :
Les DSP tendent à utiliser des files de taille fixe, ce qui fait que le remplissage ne s'arrête pas quand la file est pleine. À la place, le nouvel échantillon remplace l'échantillon le plus ancien. Il n'y a donc pas vraiment besoin d'utiliser deux pointeurs, car on est certain que la file sera pleine en permanence et que ce remplacement se fera sans douleur. Une file sur un DSP s'implémente donc en utilisant trois pointeurs : un pour l'adresse de départ du tableau en mémoire, un autre pour l'adresse de fin du tableau, et un pointeur qui pointe vers la donnée la plus ancienne/récente.

En clair, les files sont des tableaux dans lesquels la position des échantillons est décalée. La différence est mineure, mais elle fait que des calculs d'adresse sont requis pour déterminer à quel indice lire dans le tableau. Pour éviter cela, les DSPs intègrent des modes d'adressage spécialisés, conçus pour fonctionner au mieux avec les files mentionnées plus haut.
Déjà, les files sont implémentées avec des tableaux, ce qui fait que les modes d'adressages indicés sont une nécessité absolue. Déjà, les DSP supportent l'adressage "Base + Indice", qui permet de grandement simplifier les calculs d'adresse pour une file. L'adresse de base utilisée n'est pas l'adresse de base du tableau, mais celle de la donnée la plus récente ou la plus ancienne. L'idée est que l'échantillon le plus récent est celui d'indice zéro, le précédent celui d'indice 1, celui encore précédent est d'indice 2, etc. Les DSPs anciens/basiques étant des architectures à accumulateur, ils incorporent pour cela des registres d'indice, et éventuellement des registres d'adresse pour mémoriser l'adresse de base.
Une autre optimisation est l'usage de modes d'adressage avec post- ou pré-incrément/décrément. L'idée est que la lecture met à jour automatiquement l'indice utilisé, afin d'économiser une instruction d'incrémentation ou une addition. La lecture qui lit un opérande en mémoire RAM incrémente alors automatiquement l'indice utilisé dans l'adressage "Base + Indice".
Cependant, faire ainsi pose un petit problème : que faire quand on atteint la fin du tableau ? En théorie, on devrait reprendre au tout début du tableau. Mais l'adressage "Base + Indice" ne permet pas de faire cela automatiquement. Sans optimisations, on devrait faire un test et un branchement avant chaque lecture, pour gérer ce cas. Mais les DSPs incorporent un mode d'adressage spécialisé, qui permet de gérer automatiquement ce cas problématique, directement dans la lecture elle-même ! Il s'agit du mode d'adressage « modulo ». Si lors d'une incrémentation, on dépasse l'adresse de fin du tableau, l'adresse est réinitialisée pour pointer sur l'adresse de début du tableau. Il garantit que l'adresse reste dans la file et n'en déborde pas.
Suivant les DSP, le mode d'adressage modulo est géré différemment. La méthode la plus évidente utilise deux registres : un pour stocker l'adresse de début du tableau et un autre pour l'adresse de fin. Une solution alternative n'utilise pas l'adresse de fin, mais la taille/longueur du tableau. Cette dernière se marie bien avec des registres d'indices : la longueur du tableau est comparée avec l'indice courant, pour vérifier si l'adresse dépasse la fin du tableau.
Une seconde méthode utilise un registre « modulo », qui stocke la taille du tableau. Il est associé à un registre d'adresse pour l'adresse/indice de l’élément en cours. Vu que seule la taille du tableau est mémorisée, le processeur ne sait pas quelle est l'adresse de début du tableau, et doit donc ruser. La ruse ne fonctionne que pour des files/tableaux de petite taille. L'adresse est alors alignée sur un multiple de 64, 128, ou 256 octets. Cela permet ainsi de déduire l'adresse de début de la file : c'est le multiple de 64, 128, 256 strictement inférieur le plus proche de l'adresse manipulée.
Un DSP a souvent plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files. Il faut dire qu'un DSP exécute souvent plusieurs algorithmes de traitement de signal à la suite. Par exemple, il est possible d'avoir un filtre FIR suivi d'un filtre d'antialiasing, suivi d'un algorithme de Fast Fourier Transform, et ainsi de suite. Certains de ces algorithmes, comme la Fast Fourier Transform, se font en plusieurs étapes enchainées les unes à la suite des autres. Et chaque étape a son propre ensemble d'échantillons, donc sa propre file. Et le DSP peut gérer ces différentes files nativement.
Le mode d'adressage modulo semble assez spécialisé, mais sachez que les DSPs supportent des modes d'adressages encore plus spécialisés, utilisables seulement par un ou deux algorithmes triés sur le volet ! L'adressage à bits inversés (bit-reverse) a été inventé pour accélérer les algorithmes de calcul de transformée de Fourier rapide, un « calcul » très courant en traitement du signal. Cet algorithme lit des échantillons dans un tableau, et fournit des résultats dans un autre tableau. Seul problème, l'ordre des résultats dans le tableau d'arrivée est assez spécial. Par exemple, pour un tableau de 8 cases, les données arrivent dans cet ordre : 0, 4, 2, 6, 1, 5, 3, 7. L'ordre semble être totalement aléatoire. Mais il n'en est rien : regardons ces nombres une fois écrits en binaire, et comparons-les à l'ordre normal : 0, 1, 2, 3, 4, 5, 6, 7.
| Ordre normal | Ordre Fourier |
|---|---|
| 000 | 000 |
| 001 | 100 |
| 010 | 010 |
| 011 | 110 |
| 100 | 001 |
| 101 | 101 |
| 110 | 011 |
| 111 | 111 |
Comme vous le voyez, les bits de l'adresse Fourier sont inversés comparés aux bits de l'adresse normale. Inverser les bits d'une adresse peut être fait avec des opérations bit à bit, des décalages et rotations, mais cela prendrait beaucoup d'instructions. Il est possible d'imaginer une instruction REVERSE qui inverse les bits d'une adresse. Ce serait là une solution fort intéressante, que certains DSPs doivent sans doute implémenter. Mais beaucoup de DSPs préfèrent utiliser un mode d’adressage qui inverse tout ou partie des bits d'une adresse mémoire : l'adressage bit-reverse mentionné plus haut.
Une autre solution utilise un adressage indicé, mais qui calcule les adresses différemment. Il suffit, lorsqu'on ajoute un indice à l'adresse, de renverser la direction de propagation de la retenue lors de l'addition. Certains DSP disposent d'instructions pour faire ce genre de calculs.
L'architecture mémoire d'un DSP
[modifier | modifier le wikicode]Les DSPs ont une architecture mémoire particulière. Par architecture mémoire, je veux dire par là que leur hiérarchie mémoire est particulière. Déjà, ils n'ont généralement pas de caches de données, car celui-ci n'est pas compatible avec le temps réel. Par contre, ils tendent à compenser en utilisant des local store. Si un DSP ne possède généralement pas de cache pour les données, il a parfois un cache d'instructions pour accélérer l'exécution des boucles.
L'usage de registres spécialisés
[modifier | modifier le wikicode]Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Vous remarquerez que le code d'un filtre FIR n'utilise pas beaucoup de registres, et ce d'autant plus si on utilise des instructions MAD et un registre accumulateur. Et cela se généralise aux autres algorithmes de traitement de signal.
Mais surtout, les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand un opérande est lu par plusieurs instructions différentes. Dans ce cas, mieux vaut copier l'opérande dans un registre, au lieu de la relire plusieurs fois en mémoire RAM. La première utilisation a un cout, mais les suivantes sont amorties. Mais les algorithmes de traitement de signal ne réutilisent pas leurs opérandes. Quand un opérande est chargé depuis la mémoire RAM, elle sera utilisée une seule fois.
Un autre usage des registres est lié aux résultats des instructions. En général, le résultat d'une instruction est utilisé comme opérande par d'autres instructions, au moins une. Ainsi, il vaut mieux enregistrer ce résultat dans un registre, histoire que sa lecture en tant qu'opérande se fasse rapidement. Mais sur les DSPs, ce transfert à l'instruction suivante est le fait du registre accumulateur ! A lui seul, il prend en charge cette réutilisation des résultats. A la rigueur, pour certains algorithmes, un seul accumulateur n'est pas suffisant. Mais en utiliser 2 ou 3 accumulateurs suffit généralement à résoudre totalement ce problème.
Cependant, il y a plusieurs situations qui mériteraient d'utiliser des registres : les calculs d'adresse, ainsi que les compteurs de boucle. Mais pour ces deux cas, les DSPs préfèrent utiliser des registres spécialisés. Ils intègrent ainsi des registres pour les adresses, reliés à une unité de calcul d'adresse dédiée. Idem pour les compteurs de boucle, qui ont des registres dédiés, afin de simplifier l'implémentation du zero-overhead looping. Les registres d'un DSP ne correspondent donc à rien de familier à ce stade du cours, ils sont vraiment à part du reste.
Cette spécialisation des registres a de nombreuses conséquences. Certaines instructions ne sont utilisables que sur certains types de registres, et il en est de même pour les modes d'adressage. Certaines instructions d'accès mémoire peuvent prendre comme destination ou comme opérande un nombre limité de registres, les autres leur étant interdits. Cela permet de diminuer le nombre de bits nécessaire pour encoder l'instruction en binaire. Mais cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, qui ont du mal à utiliser correctement les modes d'adressages spécialisés, ainsi qu'à utiliser correctement les registres. Il n'est pas étonnant que les DSP aient longtemps été programmés en assembleur, et il n'est pas rare qu'ils le soient toujours.
Par contre, l'usage de registres spécialisés permet des optimisations très importantes. Les DSPs modernes sont capables de faire deux calculs d'adresse, une opération MAC, et un branchement de boucle en un seul cycle d'horloge. Le branchement est réalisé via zero overhead looping, les calculs d'adresse le sont via les modes d'adressage adéquats. Si l'indice de boucle, les adresses et opérandes étaient dans un banc de registre unique, alors celui-ci devrait avoir entre 5 et 10 de ports de lecture. En utilisant des bancs registres séparés, on peut se débrouiller avec seulement deux ports de lecture par banc de registre, voire moins : deux ports de lecture pour les registres d'opérandes, un registre d'indice de boucle séparé, deux-trois ports de lecture pour le banc de registre pour les adresses, etc.
La lecture des opérandes pour l'instruction MAC
[modifier | modifier le wikicode]Le reste de l'architecture mémoire d'un DSP est assez bizarre : ils utilisent des architectures Harvard, sont reliés à des mémoires multiports, n'ont pas de registres généraux, et j'en passe. Ces particularités visent à exécuter des instructions MAC rapidement. En théorie, les instructions utilisant un accumulateur sont de type load-op : un opérande est lu depuis l'accumulateur, l'autre depuis la mémoire RAM. Mais autant cela marche bien pour des instructions à deux opérandes, autant il y a un problème pour les instructions MAC. Vu que ce sont des instructions triadiques, il faut lire les deux opérandes de la multiplication depuis la mémoire RAM. Et ce n'est pas possible avec une architecture classique.
La solution la plus simple lit les deux opérandes un par un, depuis la mémoire RAM. Il s'agit d'une solution assez générale, qui marche aussi pour d'autres algorithmes que les filtres FIR. Et cela demande d'adapter le processeur pour gérer la situation.
La première solution ajoute un registre pour mémoriser un des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le registre T. Le registre T est adressé implicitement, tout comme l'accumulateur. Une instruction MAC a juste besoin de connaitre l'adresse de la seconde opérande. Cette solution a beaucoup été utilisée sur les tout premiers DSP, notamment ceux de la marque Texas Instrument. Elle est de moins en moins utilisée de nos jours.
LOOP N X // répète les X instructions suivantes, N fois
LOAD RA0 ; // copie dans le registre T, instruction avec mode d'adressage en post-incrément
MAC RA1 // instruction avec mode d'adressage en post-incrément

Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. Les DSPs avec des registres pour les opérandes se débrouillent donc avec moins d'une dizaine de registres, généralement 2 ou 4. La raison à cela est que les algorithmes de traitement de signal n'ont pas besoin de plus, comme on l'a dit plus haut. Il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes, mais cela ne sert pas très souvent. De plus, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur.
La majorité des DSPs n'utilise pas les deux solutions précédentes. A la place, ils préfèrent se passer de registres pour les opérandes. Les deux opérandes sont lus directement dans la mémoire RAM, en même temps ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire simultanés. L'instruction MAC est alors une pure instruction load-op, mais adaptée à l'usage de trois opérandes. En utilisant les modes d'adressage adéquats, l'instruction MAC fait tout : elle calcule les adresses modulo, lit des opérandes depuis la mémoire RAM/ROM, puis fait l'opération MAC. Avec cette solution, le code d'un filtre FIR devient le suivant. Vous remarquerez qu'il se résume à une instruction MAC et une instruction de zero overhead looping.
LOOP N X // répète les X instructions suivantes, N fois
MAD RA0 , RA1
La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle, ce qui implique d'utiliser une mémoire multiport, pour gérer nativement plusieurs accès par cycle. Cette solution a l'avantage de fonctionner pour d'autres algorithmes que les filtres FIR, et est en quelque sorte plus générale. Les deux solutions peuvent être utilisées en même temps. Par exemple, le DSP TMS320-C54x incorpore deux local store : un local store double port pour les opérandes, un deuxième pour écrire les résultats.

Une autre solution, qui marche parfaitement pour les filtres FIR, utilise deux mémoires séparées : une qui contient les échantillons, une autre pour les coefficients. Les deux RAMs peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. La mémoire pour les coefficients peut-être une mémoire ROM dédiée, et pas une mémoire RAM. Utiliser une RAM pour les coefficients est utile si le filtre change au cours du temps, mais une ROM suffit si elle peut mémoriser tous les coefficients nécessaires.

L'usage d'une architecture Harvard modifiée
[modifier | modifier le wikicode]Les solutions précédentes peuvent être améliorées, voire combinées, en utilisant une architecture Harvard modifiée. Pour rappel, une architecture Harvard permet de lire des constantes depuis la mémoire ROM. L'idée est que les coefficients d'un filtre FIR sont chargées depuis la mémoire ROM, et non la mémoire RAM. Et quand je dis la ROM, c'est sous-entendu : celle qui contient aussi le programme à exécuter.
La solution est praticable, car les algorithmes de traitement de signal sont assez courts et utilisent assez peu de coefficients (moins d'un millier), ce qui fait que le programme et les coefficients rentrent tous deux dans la mémoire ROM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiats sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport.
Ainsi, pour une instruction MAC d'un filtre FIR, un opérande est lu depuis la ROM, la seconde opérande est lue depuis la mémoire RAM, la troisième est lue depuis l'accumulateur, et le résultat est mémorisé dans l'accumulateur. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Typiquement, le DSP incorpore des registres d'adresse séparés pour la ROM et pour la RAM, avec des unités de calcul d'adresse séparées.
LOOP N X // répète les X instructions suivantes, N fois
MAC RA-ROM , RA-RAM // RA-ROM est le registre d'adresse pour la ROM, RA-RAM celui pour la RAM
Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtres FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, un des opérandes doit être constant et placé en ROM. Si jamais on souhaite utiliser une donnée variable, cette solution ne marche plus. Mais cela ne signifie pas que l'implémentation est impossible. Il suffit de rajouter le registre T ou les registres d'opérande vus plus haut, pour compenser.

Le contrôleur DMA intégré à un DSP
[modifier | modifier le wikicode]Un point important est que les écritures dans le local store ou la RAM ne passe pas par le DSP, histoire de lui économiser du travail. Les échantillons sont écrits dans le local store ou la RAM en utilisant le Direct Memory Access. Le DSP contient pour cela un contrôleur DMA, qui transfère les échantillons nécessaires du convertisseur analogique-numérique, vers la mémoire RAM et/ou le local store.
Il faut absolument éviter que le DSP et le contrôleur DMA se marchent sur les pieds. Pas question qu'ils accèdent en même temps à la mémoire RAM ou au local store. Et il faut éviter absolument que le contrôleur DMA monopolise la RAM et laisse le DSP patienter trop longtemps, idem pour le cas inverse. La majorité des DSPs intègre des techniques d'arbitrage du bus mémoire assez complexes. Une solution alternative, elle aussi très utilisée, dédie un port mémoire au contrôleur DMA. Le contrôleur DMA accède à la RAM via son propre port mémoire dédié, en même temps que le processeur, les deux peuvent faire un accès mémoire en même temps. Plus besoin d'arbitrer le bus mémoire.

L'instruction MAC et l'unité de calcul associée
[modifier | modifier le wikicode]Les DSP intègrent une unité de calcul dédiée pour l'instruction MAC. Elle contient un multiplieur, un additionneur, et un accumulateur, ainsi que d'autres circuits. Cette unité de calcul devrait en théorie être abordée dans une section sur la microarchitecture d'un DSP. Cependant, de nombreux détails de cette unité de calcul sont exposés au programmeur. Notamment, l'accumulateur a quelques particularités qui sont spécifiques au traitement de signal, que le programmeur voit. Voyons lesquelles.
Les accumulateurs larges et leurs Guard Bits
[modifier | modifier le wikicode]Les DSPs ont des besoins en termes de précision plus importants que sur un ordinateur classique. Il n'est pas acceptable de perdre en qualité d'image ou sonore, parce que le processeur a fait un arrondi un peu trop visible. Et ces arrondis ou troncatures sont très fréquents avec des registres généraux, alors qu'on peut les éviter avec des accumulateurs. Voyons comment.
Pour rappel, les multiplications donnent un résultat deux fois plus grand que leurs opérandes. Multipliez deux opérandes de 16 bits, le résultat en fera 32. Sur un ordinateur normal, les résultats sont tronqués pour rentrer dans les registres généraux. Par exemple, sur un processeur 32 bits, le résultat d'une multiplication est tronqué, on ne garde que les 32 bits de poids faible, en espérant qu'aucun débordement n'aura lieu. A la rigueur, certains processeurs permettent d'utiliser deux registres de 32 bits : un pour les 32 bits de poids faible du résultat, un autre pour les 32 bits de poids fort. Mais c'est assez rare. À l'opposé, les DSPs utilisent des accumulateurs de grande taille capables de mémoriser le résultat complet d'une multiplication.
Il faut noter que le problème a aussi lieu pour l'addition, après la multiplication. Pour une addition, le résultat fera un bit de plus que les opérandes : additionnez deux opérandes de 32 bits, le résultat en fera 33. Vu que le DSP effectue une série d'additions consécutives, le résultat final aura facilement une dizaine de bits en plus, parfois plus, le nombre exact dépendant des opérandes et du nombre d'itérations de la boucle. Pour éviter les débordements d'entiers liés à l'addition, les accumulateurs contiennent souvent 4 à 8 bits de plus que le résultat de la multiplication. Les bits supplémentaires sont appelés des guard bits.
Pour donner un exemple, les DSPs de 24 bits ont souvent des accumulateurs de 56 bits : 48 bits pour le résultat de la multiplication, plus 8 guard bits.

La présence de Guard bits fait que la gestion des débordements d'entier est fort différente sur les DSPs, comparé aux autres processeurs. De fait, les DSP utilisent souvent l'arithmétique saturée, car c'est assez naturel quand on manipule un signal qui peut... saturer ! Quand un signal sonore sature, cela veut dire que l'intensité sonore dépasse le maximum représentable. En clair, l'intensité sonore dépasse le maximum encodable avec un entier/flottant, il y a un débordement entier/flottant. Si on traitait ce débordement en ne conservant que les bits de poids faible du résultat, un son qui sature donnerait un son très faible, ce qui n'est pas le comportement attendu. Il est plus naturel de mettre le son à la valeur maximale représentable.
Les DSP les plus simples n'utilisent que l'arithmétique saturé, mais d'autres plus complexes permettent de configurer si on utilise l'arithmétique saturée ou non. Certains permettent d'activer et de désactiver l'arithmétique saturée, en modifiant un registre de configuration du processeur. D'autres fournissent chaque instruction de calcul en double : une en arithmétique modulaire, l'autre en arithmétique saturée.
Le décaleur pour les arrondis
[modifier | modifier le wikicode]L'accumulateur a donc une taille bien plus grande de celle des opérandes. Typiquement, les opérandes sont soit lues depuis la mémoire RAM, soit depuis des registres pour les opérandes. Dans les deux cas, l'accumulateur est plus large que les registres ou le bus de données. Lorsque les données quittent l'accumulateur, elles doivent donc être tronquées pour rentrer dans l'adresse/registre de destination. Pour cela, l'accumulateur est suivi par un barrel shifter, qui décale le résultat pour éliminer les bits en trop.

Le décaleur peut aussi prendre en charge d'arithmétique saturée. L'idée est que les circuits qui s'occupent de saturer les résultats sont intégrés avec le décaleur. Ces circuits regardent les guard bit sortant de l'accumulateur et modifient le résultat en fonction de ceux-ci. Si aucun guard bit n'est à zéro, alors il n'y a pas eu débordement d'entier et le décaleur fait son travail normalement. Mais si un seul guard bit est à 1, c'est signe qu'un débordement d'entier a eu lieu. Le résultat est alors remplacé par la valeur maximale supportée par le DSP (en dehors de l'accumulateur).
Un DSP contient souvent une unité MAC, une ALU entière, et un décaleur séparé. Un DSP doit en effet implémenter les instructions usuelles, sur des opérandes entiers. Et cela ne concerne pas que les additions/soustractions ou les opérations logiques : les décalages/rotations sont aussi de la partie. Les DSPs intègrent donc un barrel shifter, qui est séparé de l'unité MAC. Et c'est une source d'optimisation : le décaleur pour les arrondis est parfois fusionné avec le bareel shifter, pour économiser des circuits. En clair, le décaleur des arrondis est sorti de l'unité MAC et est une unité de calcul comme une autre. Mais ce n'est pas systématique et de nombreux DSPs préfèrent utiliser un mini-décaleur séparé du barel shifter, pour des raisons de performance.
L'implémentation matérielle de l'unité de calcul MAC
[modifier | modifier le wikicode]
L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur en un seul circuit. Rien de surprenant, nous savons qu'un multiplieur contient un additionneur multiopérande, on peut lui ajouter de quoi faire une addition entière en plus. Cependant, la plupart des DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux.
Le registre en sortie du multiplieur a une taille deux fois plus grande que les opérandes, pour mémoriser le résultat complet de la multiplication. Pour un DSP qui manipule des opérandes de 24 bits, le registre pour les multiplications fera 48 bits, soit le double. Pour donner un exemple, les DSP Blackfin+ géraient des opérandes de 32 bits, avaient un registre de 64 bits pour le résultat de la multiplication, et utilisaient des accumulateurs de 72 bits.

Faire ainsi a plusieurs avantages. Le premier est que cela permet de pipeliner l'unité de calcul MAC. Pendant que l'additionneur traite une instruction MAC, le multiplieur s'occupe de l'instruction MAC suivante. Les DSPs avec un pipeline peuvent ainsi profiter d'une hausse de performance assez conséquente. Mais au-delà de l'usage d'un pipeline, d'autres optimisations sont rendues possibles.
La première est que cela permet d'implémenter l'addition de manière assez simple. En effet, l'unité MAC peut parfois être modifiée de manière à supporter d'autres instructions que l’instruction MAC. Elles peuvent être utilisées pour faire des additions ou des multiplications seules. L'addition seule court-circuite le multiplieur, et envoie un opérande en entrée de l'additionneur. Pour cela, il faut intercaler un multiplexeur entre le multiplieur et l'additionneur. L'additionneur peut aussi être remplacé par une vraie unité de calcul entière, capable de faire des opérations bit à bit.

L'opérande de l'addition peut provenir de plusieurs endroits : soit d'un autre accumulateur, soit des registres d'opérandes, soit de la mémoire RAM. Si les deux opérandes sont dans deux accumulateurs, alors elles ont la même taille et sont envoyées en entrée de l'additionneur sans autre forme de procès. Mais si elles viennent des registres d'opérandes ou de la RAM, elles sont plus courtes que ce que prend en entrée l'accumulateur. Par exemple, prenons un DSP avec des opérandes 16 bits et un additionneur 40 bits. L'additionneur a une entrée reliée à l'accumulateur de 40 bits, l'autre entrée provient du multiplexeur et fait 32 bits. Un opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est un opérande de 16 bits et doit être convertie en 32 bits. Pour cela, il y a plusieurs solutions.
- La première utilise l'extension de signe, à savoir que l'opérande de 16 bits est étendue sur 32 de manière à conserver son signe.
- La seconde envoie l'opérande dans les 16 bits de poids fort en entrée de l'additionneur, les 16 bits de poids faible sont mis à zéro.
- Il est possible de concaténer deux registres d'opérande de 16 bits pour obtenir un opérande de 32 bits, soit la taille adéquate.
Les unités MAC flottantes
[modifier | modifier le wikicode]Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entiers, pas pour des opérandes flottants. Pour les opérandes flottants, la question des arrondis est un peu différente. L'opération MAC est composée d'une multiplication flottante, suivie d'une addition flottante. La question est alors la suivante : est-ce qu'il y a un arrondi entre les deux, avec la normalisation et autres subtilités propres aux nombres flottants ?
Pour gérer ce cas, il existe deux types d'instructions MAC : l'instruction MAC classique et l'instruction Fused MAC (FMAC). L'instruction MAC classique fait un arrondi entre les deux, l'instruction FMAC n'en fait pas. L'instruction donne des résultats plus précis, mais demande de travailler avec un résultat de multiplication plus grand de quelques bits, ce qui fait des circuits en plus.
Les DSP se classent en deux sous-types : ceux qui utilisent des nombres flottants et ceux qui utilisent des nombres à virgule fixe. Les premiers DSPs utilisaient la virgule fixe. Le cas classique était des DSP utilisant des opérandes de 24 bits : 16 pour la partie entière, 8 pour la partie fractionnaire. Notons que 24 bits était la norme pour encoder de l'audio sur des CD audio, ce qui fait que les DSPs de l'époque utilisaient cette précision. Par la suite, des DSP 16 et 32 bits sont apparus, puis des DSP flottants.
Les DSPs à virgule fixe disposent de mécanismes pour émuler des nombres flottants en utilisant des calculs entiers. Pour être plus précis, ils émulent des nombres flottants spéciaux, appelés nombres flottants par blocs (traduction du terme anglais block-floating point). L'idée est de manipuler des nombres flottants qui ont tous le même exposant, afin de simplifier les calculs. Si plusieurs nombres flottants ont le même exposant, alors les additionner demande de simplement additionner les mantisses, les multiplier demande de multiplier les mantisses. Du moins, c'est le cas en absence de débordement d'entier, mais les DSPs ont des protections pour ça, comme les guard bits et les accumulateurs larges.
Les DSPs émulent les calculs sur les flottants par blocs de la manière suivante. Les DSPs disposent d'une instruction EXP, qui calcule l'exposant et le mémorise dans un registre. Une fois l'exposant connu, ils normalisent les mantisses avec des décalages, de manière à ce que tous les opérandes aient le même exposant. Puis, ils additionnent ou multiplient les mantisses ensemble, avec des instructions MAC, MUL, ADD, SUB, DIV, etc. Une fois les calculs terminés, la mantisse du résultat est dans l'accumulateur. Elle est normalisée avec un décalage, réalisé par le barrel shifter, en fonction de l'exposant calculé.
La gestion des flottants par blocs peut être réalisée avec le décaleur vu plus haut, dans la section précédente. Les décalages nécessaires pour normaliser et décaler les mantisses sont réalisés par de décaleur.
Des exemples d'unités MAC
[modifier | modifier le wikicode]Le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, le registre T pour un opérande, et le registre P entre le multiplieur et l'additionneur.

Le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les deux accumulateurs faisaient 56 bits. Les registres d'opérandes, quant à eux, pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits.
L'unité MAC était capable de faire une opération MAC, une addition, ou une opération bit à bit. Pour cela, l'additionneur est précédé par une unité de calcul bit à bit, qui n'est pas utilisée quand le multiplieur l'est. En clair, l'unité bit à bit sert uniquement quand on charge les opérandes depuis les registres d'opérandes. L'additionneur est suivi par un circuit capable de faire des opérations de normalisation et d'arrondis.
Le circuit intègre deux décaleurs. Le premier est situé en sortie de l'accumulateur, et permet de traduire un résultat de 56 bits en un résultat de 24 bits. Il sert aussi pour des arrondis, des opérations de normalisation et bien d'autres. L'autre décaleur est lui entre l'accumulateur et l'additionneur. Il est beaucoup plus limité et ne peut que faire quatre opérations : ne rien faire, un décalage à gauche de 1 rang, un décalage à droite de 1 rang, mettre à zéro sa sortie.
L'unité MAC n'était pas pipelinée, elle faisait un calcul en un cycle. Par contre, il était possible de modifier les registres d'opérandes pendant qu'un calcul MAC était en cours. En entrée du multiplieur, il y avait deux registres non-adressabbles, qui mémorisaient les opérandes pendant un cycle d'horloge complet. Ainsi, il était possible d'écrire dans les registres d'entrée, sans pour autant que cela impacte le calcul en cours.

La microarchitecture d'un DSP
[modifier | modifier le wikicode]Il est intéressant de regarder comment la microarchitecture des DSPs a évoluée. Et c'est en lien avec l'évolution de leur jeu d'instruction. Les DSPs sont souvent classés en trois à cinq générations, mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Dans ce chapitre, nous allons juste séparer les DSPs en deux : les DSPs classiques, qu'on a détaillé plus haut, et les DSP récents qui intègrent des jeux d'instructions SIMD, VLIW ou superscalaires.
Sur les anciens DSP, les instructions s'exécutaient toutes en un seul cycle d'horloge et tendaient à faire pas mal de traitements assez complexes. De nos jours, les DSPs tendent à utiliser un pipeline, ce qui fait que la contrainte "1 cycle = une instruction" est battue en brèche.
La microarchitecture d'un DSP classique
[modifier | modifier le wikicode]Un DSP contient au minimum une unité de calcul MAC avec un barrel shifter. De nombreux DSPs incorporent aussi une ALU entière séparée du circuit MAC, qui est utilisée pour des additions/soustractions, opérations logiques et bit à bit. Le barrel shifter est lui utilisé pour tronquer le résultat en sortie de l'accumulateur, et pour gérer les nombres flottants par blocs, avec l'aide d'une unité dédiée aux exposants.
Les DSPs intègrent des unités de calcul spécialisées dans les calculs d'adresse, qui sont regroupées avec les registres d'adresse et d'indice. Elles implémentent l'adressage modulo et bit-reverse, ainsi que les modes d'adressages à post- ou pré-incrément/décrément, et les modes d'adressage usuels. Il y a un banc de registre pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. Il y a en général deux unités de calcul d'adresse, une par opérande.

Un DSP avec un registre T est forcément connecté à une mémoire simple port. Sa microarchitecture est des plus simples. De tels DSP n'ont généralement pas d'ALU entière séparée de l'unité MAC, car ces opérations sont réalisées dans l'unité MAC elle-même. L'additionneur est remplacé par une ALU entière, un MUX permet de court-circuiter le multiplieur. Ses interconnexions sont particulièrement simples.
- Nous omettrons les liaisons entre séquenceur et ALUs, utilisées pour les modes d'adressage immédiat (constantes immédiates).

Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci :

Si on suppose que le DSP utilise une architecture Harvard modifiée, et que les coefficients sont en mémoire ROM, l'intérieur du DSP devrait ressembler à ceci :

Les DSPs avec optimisations pour l'opération butterfly
[modifier | modifier le wikicode]Les DSPs précédents n'incorporent pas d'optimisations pour l'opération butterfly. En effet, celles-ci n'ont qu'un seul accumulateur, pas plus. Mais l'opération butterfly marche bien mieux avec deux accumulateurs. Aussi, de nombreux DSPs incorporent deux accumulateurs et quoi les utiliser au mieux. Dans le cas le plus simple, l'unité MAC est reliée à deux accumulateurs. Mais sur les DSPs Dual MAC, les deux accumulateurs sont couplés à deux unités MAC, avec une unité MAC par accumulateur. De tels DSPs peuvent faire deux opérations MAC par cycle d'horloge, souvent avec l'aide d'une instruction machine de multiplication/addition complexe.
Pour donner un exemple, prenons le DSP TMS320-C54x, qui dispose de deux accumulateur, mais n'est pas un DSP dual MAC. Il dispose de 6 unités de calcul : une unité MAC non-pipelinées, une ALU entière, un barrel shifter, deux unités de calcul d'adresse, et une unité de comparaison spécialisée pour l'algorithme de Viterbi qu'on ne détaillera pas ici. L'ALU entière et le barrel shifter gèrent des opérandes de 40 bits, l'unité MAC gère des opérandes de 17 bits (16 bits, plus un bit de signe) et un résultat de 40 bits. Pour supporter des calculs flottants, le processeur incorpore un circuit dédié à la gestion des exposants, qui s'occupe des opérations de normalisation, d'arrondis et autres.
Un détail important est que l'unité de calcul peut soit fonctionner comme une ALU unique de 40 bits, soit comme une ALU SIMD de 16 bits. Elle est capable de faire deux opérations sur deux opérandes de 16 bits en même temps.
Pour le reste, les interconnexions entre ces unités de calcul sont très complexes. C'est presque comme si tout était connecté à avec tout le reste. Le DSP TMS320-C54x a deux accumulateurs, qui peuvent recevoir le résultat de l'unité MAC ou de l'ALU entière. Les deux accumulateurs envoient leur contenu en entrée de toutes les ALU, sauf les AGU. Le barrel shifter envoie son résultat soit en mémoire RAM, soit en entrée de l'ALU entière.
Le TMS320-C54x dispose de trois bus de données, pour lire deux opérandes et écrire un résultat en un seul cycle d'horloge. Ils sont appelés CB, DB et EB, les deux bus CB et DB sont en lecture, le bus EB est un bus d'écriture. Les deux bus CB et DB envoient des opérandes à l'unité MAC, l'ALU entière et le barrel shifter. Pour le bus EB des écritures, il n'est accesible qu'à travers le barrel shifter. Ce qui est logique : lors de l'enregistrement en mémoire, un résultat de 40 bits doit être converti en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible.

Les DSPs VLIW, SIMD et superscalaires
[modifier | modifier le wikicode]Les DSPs de seconde génération et au-delà, incorporent plusieurs unités de calcul MAC. De plus, celles-ci sont pipelinées pour augmenter le nombre d'opérations exécutées par cycle d'horloge. Pour les alimenter, le processeur dispose de trois méthodes : soit utiliser un jeu d'instruction VLIW, soit être un processeur superscalaire, soit utiliser des instructions SIMD. Les trois solutions sont utilisées, suivant le DSP. Et il n'est pas rare que l'on ait des DSPs qui mélangent SIMD et VLIW, ou encore SIMD et superscalarité.
Les DSP les plus simples sont les DSP dual MAC, au nom assez parlent. Ils incorporent deux multiplieurs, voire deux unités de calcul FMAC, qui peuvent fonctionner en même temps. Des exemples de DSPs de ce type sont le Lucent DSP 16xxx ou le TMS C55x de Texas Instrument. Le Lucent DSP 16xxx contenait deux multiplieurs de 16 bits, couplés à une ALU entière et un additionneur trois-opérandes. Cela parait bizarre de ne pas avoir utilisé deux ALU entières, mais cela permet d'économiser un peu de circuit de remplacer une seconde ALU par un additionneur. L'ensemble permettait de faire deux opérations MAC par cycle. Il y avait aussi une unité de manipulation de bit, sans compter de nombreux circuits décaleurs intercalés en entrée et sortie des multiplieurs.

Mais les unités MAC ne sont pas seules au monde, il faut aussi tenir compte des ALU entières et du barrel shifter. Les DSP dual MAC basiques ne les dupliquent pas, mais d'autre le font. Pour donner un exemple, le Texas Instruments TMS320 C62x incorpore deux multiplieurs, deux ALU entières, deux unités de calcul qui regroupent une ALU entière avec un barrel shifter, et deux unités de calcul d'adresse. Le tout est associé à deux bancs de registres contenant 32 registres de 32 bits chacun. Pour les exploiter, le processeur utilisait un jeu d'instruction VLIW, où chaque faisceau VLIW regroupait 8 instructions, chacune allant sur une des 8 unité.
L'ADI TigerSHARC est un processeur qui mélange SIMD et VLIW. L'idée est qu'il peut exécuter un même faisceau VLIW sur deux paquets de données. Pour cela, le processeur contient deux chemins de données identiques, qui exécutent le même instruction VLIW, mais sur des données différentes. Chaque chemin de données contient 32 registres, une unité mémoire (avec calcul d'adresse), un barrel shifter, une ALU entière et un circuit MAC. Un faisceau VLIW encode 4 instructions : une instruction LOAD/STORE, une opération MAC, une opération de calcul entière et un décalage.
Les DSP incorporent aussi ce que j'ai appelé du SWAR (SIMD Within A Register) dans le chapitre sur le parallélisme de données. L'idée est de configurer un additionneur 32 bits pour faire deux additions 16 bits à la fois. Les ALU entières des DSPs incorporent cette optimisation. Les DSPs ayant souvent des ALU entières de 40 bits, cela permet d'implémenter deux additions de 16 bits, avec des guard bit pour chaque addition. Les guard bits sont répartis équitablement entre les deux additions 16 bits. Concrètement, le résultat de 40 bits est composé de deux résultats de 20 bits, avec 4 guard bits, les deux résultats étant concaténés.






